Skip to content

Commit

Permalink
feat(client): impl Sync for Client
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
seanmonstar committed Jun 12, 2015
1 parent d7fa961 commit 64e47b4
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 22 deletions.
2 changes: 1 addition & 1 deletion examples/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fn main() {
}
};

let mut client = Client::new();
let client = Client::new();

let mut res = client.get(&*url)
.header(Connection::close())
Expand Down
2 changes: 1 addition & 1 deletion examples/client_http2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
46 changes: 35 additions & 11 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -23,14 +23,38 @@
//!
//! ```no_run
//! # use hyper::Client;
//! let mut client = Client::new();
//! let client = Client::new();
//!
//! let res = client.post("http://example.domain")
//! .body("foo=bar")
//! .send()
//! .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;
Expand Down Expand Up @@ -61,7 +85,7 @@ use http::h1::Http11Protocol;
///
/// Clients can handle things such as: redirect policy, connection pooling.
pub struct Client {
protocol: Box<Protocol + Send>,
protocol: Box<Protocol + Send + Sync>,
redirect_policy: RedirectPolicy,
}

Expand All @@ -79,12 +103,12 @@ impl Client {

/// Create a new client with a specific connector.
pub fn with_connector<C, S>(connector: C) -> Client
where C: NetworkConnector<Stream=S> + Send + 'static, S: NetworkStream + Send {
where C: NetworkConnector<Stream=S> + 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<P: Protocol + Send + 'static>(protocol: P) -> Client {
pub fn with_protocol<P: Protocol + Send + Sync + 'static>(protocol: P) -> Client {
Client {
protocol: Box::new(protocol),
redirect_policy: Default::default()
Expand All @@ -102,33 +126,33 @@ impl Client {
}

/// Build a Get request.
pub fn get<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U> {
pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder<U> {
self.request(Method::Get, url)
}

/// Build a Head request.
pub fn head<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U> {
pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder<U> {
self.request(Method::Head, url)
}

/// Build a Post request.
pub fn post<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U> {
pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder<U> {
self.request(Method::Post, url)
}

/// Build a Put request.
pub fn put<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U> {
pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder<U> {
self.request(Method::Put, url)
}

/// Build a Delete request.
pub fn delete<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U> {
pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder<U> {
self.request(Method::Delete, url)
}


/// Build a new request using this Client.
pub fn request<U: IntoUrl>(&mut self, method: Method, url: U) -> RequestBuilder<U> {
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder<U> {
RequestBuilder {
client: self,
method: method,
Expand Down
8 changes: 4 additions & 4 deletions src/http/h1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,17 +275,17 @@ impl Http11Protocol {
/// Creates a new `Http11Protocol` instance that will use the given `NetworkConnector` for
/// establishing HTTP connections.
pub fn with_connector<C, S>(c: C) -> Http11Protocol
where C: NetworkConnector<Stream=S> + Send + 'static,
where C: NetworkConnector<Stream=S> + Send + Sync + 'static,
S: NetworkStream + Send {
Http11Protocol {
connector: Connector(Box::new(ConnAdapter(c))),
}
}
}

struct ConnAdapter<C: NetworkConnector + Send>(C);
struct ConnAdapter<C: NetworkConnector + Send + Sync>(C);

impl<C: NetworkConnector<Stream=S> + Send, S: NetworkStream + Send> NetworkConnector for ConnAdapter<C> {
impl<C: NetworkConnector<Stream=S> + Send + Sync, S: NetworkStream + Send> NetworkConnector for ConnAdapter<C> {
type Stream = Box<NetworkStream + Send>;
#[inline]
fn connect(&self, host: &str, port: u16, scheme: &str)
Expand All @@ -298,7 +298,7 @@ impl<C: NetworkConnector<Stream=S> + Send, S: NetworkStream + Send> NetworkConne
}
}

struct Connector(Box<NetworkConnector<Stream=Box<NetworkStream + Send>> + Send>);
struct Connector(Box<NetworkConnector<Stream=Box<NetworkStream + Send>> + Send + Sync>);

impl NetworkConnector for Connector {
type Stream = Box<NetworkStream + Send>;
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,8 @@ fn _assert_send<T: Send>() {
_assert_send::<client::Request<net::Fresh>>();
_assert_send::<client::Response>();
}

#[allow(unconditional_recursion)]
fn _assert_sync<T: Sync>() {
_assert_sync::<Client>();
}
8 changes: 4 additions & 4 deletions src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,12 @@ impl NetworkConnector for MockConnector {
///
/// Otherwise, it behaves the same as `MockConnector`.
pub struct ChannelMockConnector {
calls: Sender<String>,
calls: Mutex<Sender<String>>,
}

impl ChannelMockConnector {
pub fn new(calls: Sender<String>) -> ChannelMockConnector {
ChannelMockConnector { calls: calls }
ChannelMockConnector { calls: Mutex::new(calls) }
}
}

Expand All @@ -158,13 +158,13 @@ impl NetworkConnector for ChannelMockConnector {
#[inline]
fn connect(&self, _host: &str, _port: u16, _scheme: &str)
-> ::Result<MockStream> {
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();
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ impl NetworkStream for HttpStream {
pub struct HttpConnector(pub Option<ContextVerifier>);

/// A method that can set verification methods on an SSL context
pub type ContextVerifier = Box<Fn(&mut SslContext) -> () + Send>;
pub type ContextVerifier = Box<Fn(&mut SslContext) -> () + Send + Sync>;

impl NetworkConnector for HttpConnector {
type Stream = HttpStream;
Expand Down

0 comments on commit 64e47b4

Please sign in to comment.