From 64e47b4bbd0433065a059804adeb2b4a2d72f327 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Fri, 12 Jun 2015 11:19:54 -0700 Subject: [PATCH] feat(client): impl Sync for Client Connector::connect already used &self, and so would require synchronization to be handled per connector anyway. Adding Sync to the Client allows users to setup config for a Client once, such as using a single connection Pool, and then making requests across multiple threads. Closes #254 BREAKING CHANGE: Connectors and Protocols passed to the `Client` must now also have a `Sync` bounds, but this shouldn't break default usage. --- examples/client.rs | 2 +- examples/client_http2.rs | 2 +- src/client/mod.rs | 46 ++++++++++++++++++++++++++++++---------- src/http/h1.rs | 8 +++---- src/lib.rs | 5 +++++ src/mock.rs | 8 +++---- src/net.rs | 2 +- 7 files changed, 51 insertions(+), 22 deletions(-) diff --git a/examples/client.rs b/examples/client.rs index daca4c27d0..6d6a9384c7 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -20,7 +20,7 @@ fn main() { } }; - let mut client = Client::new(); + let client = Client::new(); let mut res = client.get(&*url) .header(Connection::close()) diff --git a/examples/client_http2.rs b/examples/client_http2.rs index 677dc7534c..c12f4567f9 100644 --- a/examples/client_http2.rs +++ b/examples/client_http2.rs @@ -21,7 +21,7 @@ fn main() { } }; - let mut client = Client::with_protocol(h2::new_protocol()); + let client = Client::with_protocol(h2::new_protocol()); // `Connection: Close` is not a valid header for HTTP/2, but the client handles it gracefully. let mut res = client.get(&*url) diff --git a/src/client/mod.rs b/src/client/mod.rs index a8ba789a88..27aa52e533 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -9,7 +9,7 @@ //! //! ```no_run //! # use hyper::Client; -//! let mut client = Client::new(); +//! let client = Client::new(); //! //! let res = client.get("http://example.domain").send().unwrap(); //! assert_eq!(res.status, hyper::Ok); @@ -23,7 +23,7 @@ //! //! ```no_run //! # use hyper::Client; -//! let mut client = Client::new(); +//! let client = Client::new(); //! //! let res = client.post("http://example.domain") //! .body("foo=bar") @@ -31,6 +31,30 @@ //! .unwrap(); //! assert_eq!(res.status, hyper::Ok); //! ``` +//! +//! # Sync +//! +//! The `Client` implements `Sync`, so you can share it among multiple threads +//! and make multiple requests simultaneously. +//! +//! ```no_run +//! # use hyper::Client; +//! use std::sync::Arc; +//! use std::thread; +//! +//! // Note: an Arc is used here because `thread::spawn` creates threads that +//! // can outlive the main thread, so we must use reference counting to keep +//! // the Client alive long enough. Scoped threads could skip the Arc. +//! let client = Arc::new(Client::new()); +//! let clone1 = client.clone(); +//! let clone2 = client.clone(); +//! thread::spawn(move || { +//! clone1.get("http://example.domain").send().unwrap(); +//! }); +//! thread::spawn(move || { +//! clone2.post("http://example.domain/post").body("foo=bar").send().unwrap(); +//! }); +//! ``` use std::default::Default; use std::io::{self, copy, Read}; use std::iter::Extend; @@ -61,7 +85,7 @@ use http::h1::Http11Protocol; /// /// Clients can handle things such as: redirect policy, connection pooling. pub struct Client { - protocol: Box, + protocol: Box, redirect_policy: RedirectPolicy, } @@ -79,12 +103,12 @@ impl Client { /// Create a new client with a specific connector. pub fn with_connector(connector: C) -> Client - where C: NetworkConnector + Send + 'static, S: NetworkStream + Send { + where C: NetworkConnector + Send + Sync + 'static, S: NetworkStream + Send { Client::with_protocol(Http11Protocol::with_connector(connector)) } /// Create a new client with a specific `Protocol`. - pub fn with_protocol(protocol: P) -> Client { + pub fn with_protocol(protocol: P) -> Client { Client { protocol: Box::new(protocol), redirect_policy: Default::default() @@ -102,33 +126,33 @@ impl Client { } /// Build a Get request. - pub fn get(&mut self, url: U) -> RequestBuilder { + pub fn get(&self, url: U) -> RequestBuilder { self.request(Method::Get, url) } /// Build a Head request. - pub fn head(&mut self, url: U) -> RequestBuilder { + pub fn head(&self, url: U) -> RequestBuilder { self.request(Method::Head, url) } /// Build a Post request. - pub fn post(&mut self, url: U) -> RequestBuilder { + pub fn post(&self, url: U) -> RequestBuilder { self.request(Method::Post, url) } /// Build a Put request. - pub fn put(&mut self, url: U) -> RequestBuilder { + pub fn put(&self, url: U) -> RequestBuilder { self.request(Method::Put, url) } /// Build a Delete request. - pub fn delete(&mut self, url: U) -> RequestBuilder { + pub fn delete(&self, url: U) -> RequestBuilder { self.request(Method::Delete, url) } /// Build a new request using this Client. - pub fn request(&mut self, method: Method, url: U) -> RequestBuilder { + pub fn request(&self, method: Method, url: U) -> RequestBuilder { RequestBuilder { client: self, method: method, diff --git a/src/http/h1.rs b/src/http/h1.rs index babe15d2d8..070b220bb5 100644 --- a/src/http/h1.rs +++ b/src/http/h1.rs @@ -275,7 +275,7 @@ impl Http11Protocol { /// Creates a new `Http11Protocol` instance that will use the given `NetworkConnector` for /// establishing HTTP connections. pub fn with_connector(c: C) -> Http11Protocol - where C: NetworkConnector + Send + 'static, + where C: NetworkConnector + Send + Sync + 'static, S: NetworkStream + Send { Http11Protocol { connector: Connector(Box::new(ConnAdapter(c))), @@ -283,9 +283,9 @@ impl Http11Protocol { } } -struct ConnAdapter(C); +struct ConnAdapter(C); -impl + Send, S: NetworkStream + Send> NetworkConnector for ConnAdapter { +impl + Send + Sync, S: NetworkStream + Send> NetworkConnector for ConnAdapter { type Stream = Box; #[inline] fn connect(&self, host: &str, port: u16, scheme: &str) @@ -298,7 +298,7 @@ impl + Send, S: NetworkStream + Send> NetworkConne } } -struct Connector(Box> + Send>); +struct Connector(Box> + Send + Sync>); impl NetworkConnector for Connector { type Stream = Box; diff --git a/src/lib.rs b/src/lib.rs index 5d12dbbc43..1f51008a18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -198,3 +198,8 @@ fn _assert_send() { _assert_send::>(); _assert_send::(); } + +#[allow(unconditional_recursion)] +fn _assert_sync() { + _assert_sync::(); +} diff --git a/src/mock.rs b/src/mock.rs index a924af18e8..15d132fb0d 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -144,12 +144,12 @@ impl NetworkConnector for MockConnector { /// /// Otherwise, it behaves the same as `MockConnector`. pub struct ChannelMockConnector { - calls: Sender, + calls: Mutex>, } impl ChannelMockConnector { pub fn new(calls: Sender) -> ChannelMockConnector { - ChannelMockConnector { calls: calls } + ChannelMockConnector { calls: Mutex::new(calls) } } } @@ -158,13 +158,13 @@ impl NetworkConnector for ChannelMockConnector { #[inline] fn connect(&self, _host: &str, _port: u16, _scheme: &str) -> ::Result { - self.calls.send("connect".into()).unwrap(); + self.calls.lock().unwrap().send("connect".into()).unwrap(); Ok(MockStream::new()) } #[inline] fn set_ssl_verifier(&mut self, _verifier: ContextVerifier) { - self.calls.send("set_ssl_verifier".into()).unwrap(); + self.calls.lock().unwrap().send("set_ssl_verifier".into()).unwrap(); } } diff --git a/src/net.rs b/src/net.rs index 0e3580d0bb..513d80e11b 100644 --- a/src/net.rs +++ b/src/net.rs @@ -317,7 +317,7 @@ impl NetworkStream for HttpStream { pub struct HttpConnector(pub Option); /// A method that can set verification methods on an SSL context -pub type ContextVerifier = Box () + Send>; +pub type ContextVerifier = Box () + Send + Sync>; impl NetworkConnector for HttpConnector { type Stream = HttpStream;