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.
This commit is contained in:
Sean McArthur
2015-06-12 11:19:54 -07:00
parent d7fa961a79
commit 64e47b4bbd
7 changed files with 51 additions and 22 deletions

View File

@@ -20,7 +20,7 @@ fn main() {
}
};
let mut client = Client::new();
let client = Client::new();
let mut res = client.get(&*url)
.header(Connection::close())

View File

@@ -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)

View File

@@ -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 + Send>,
protocol: Box<Protocol + Send + Sync>,
redirect_policy: RedirectPolicy,
}
@@ -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()
@@ -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,

View File

@@ -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, 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))),
@@ -283,9 +283,9 @@ impl Http11Protocol {
}
}
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)
@@ -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>;

View File

@@ -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>();
}

View File

@@ -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) }
}
}
@@ -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();
}
}

View File

@@ -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;