feat(client): add a new Client struct with super powers

- Includes ergonomic traits like IntoUrl and IntoBody, allowing easy
usage.
- Client can have a RedirectPolicy.
- Client can have a SslVerifier.

Updated benchmarks for client. (Disabled rust-http client bench since it
hangs.)
This commit is contained in:
Sean McArthur
2014-10-17 14:59:01 -07:00
parent 9e99c57fa8
commit 8c83a3358e
13 changed files with 505 additions and 89 deletions

View File

@@ -42,18 +42,18 @@ Client:
```rust
fn main() {
// Create a client.
let mut client = Client::new();
// Creating an outgoing request.
let mut req = Request::get(Url::parse("http://www.gooogle.com/").unwrap()).unwrap();
let mut res = client.get("http://www.gooogle.com/")
// set a header
.header(Connection(vec![Close]))
// let 'er go!
.send();
// Setting a header.
req.headers_mut().set(Connection(vec![Close]));
// Start the Request, writing headers and starting streaming.
let res = req.start().unwrap()
// Send the Request.
.send().unwrap()
// Read the Response.
.read_to_string().unwrap();
let body = res.read_to_string().unwrap();
println!("Response: {}", res);
}
@@ -64,22 +64,20 @@ fn main() {
[Client Bench:](./benches/client.rs)
```
running 3 tests
test bench_curl ... bench: 298416 ns/iter (+/- 132455)
test bench_http ... bench: 292725 ns/iter (+/- 167575)
test bench_hyper ... bench: 222819 ns/iter (+/- 86615)
test bench_curl ... bench: 400253 ns/iter (+/- 143539)
test bench_hyper ... bench: 181703 ns/iter (+/- 46529)
test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured
test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured
```
[Mock Client Bench:](./benches/client_mock_tcp.rs)
```
running 3 tests
test bench_mock_curl ... bench: 25254 ns/iter (+/- 2113)
test bench_mock_http ... bench: 43585 ns/iter (+/- 1206)
test bench_mock_hyper ... bench: 27153 ns/iter (+/- 2227)
test bench_mock_curl ... bench: 53987 ns/iter (+/- 1735)
test bench_mock_http ... bench: 43569 ns/iter (+/- 1409)
test bench_mock_hyper ... bench: 20996 ns/iter (+/- 1742)
test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured
```

View File

@@ -8,6 +8,10 @@ extern crate test;
use std::fmt::{mod, Show};
use std::io::net::ip::Ipv4Addr;
use hyper::server::{Request, Response, Server};
use hyper::method::Method::Get;
use hyper::header::Headers;
use hyper::Client;
use hyper::client::RequestBuilder;
fn listen() -> hyper::server::Listening {
let server = Server::http(Ipv4Addr(127, 0, 0, 1), 0);
@@ -22,9 +26,10 @@ macro_rules! try_return(
}
}})
fn handle(_: Request, res: Response) {
fn handle(_r: Request, res: Response) {
static BODY: &'static [u8] = b"Benchmarking hyper vs others!";
let mut res = try_return!(res.start());
try_return!(res.write(b"Benchmarking hyper vs others!"))
try_return!(res.write(BODY))
try_return!(res.end());
}
@@ -41,7 +46,7 @@ fn bench_curl(b: &mut test::Bencher) {
.exec()
.unwrap()
});
listening.close().unwrap()
listening.close().unwrap();
}
#[deriving(Clone)]
@@ -67,17 +72,17 @@ fn bench_hyper(b: &mut test::Bencher) {
let mut listening = listen();
let s = format!("http://{}/", listening.socket);
let url = s.as_slice();
let mut client = Client::new();
let mut headers = Headers::new();
headers.set(Foo);
b.iter(|| {
let mut req = hyper::client::Request::get(hyper::Url::parse(url).unwrap()).unwrap();
req.headers_mut().set(Foo);
req.start().unwrap()
.send().unwrap()
.read_to_string().unwrap()
client.get(url).header(Foo).send().unwrap().read_to_string().unwrap();
});
listening.close().unwrap()
}
/*
doesn't handle keep-alive properly...
#[bench]
fn bench_http(b: &mut test::Bencher) {
let mut listening = listen();
@@ -92,9 +97,10 @@ fn bench_http(b: &mut test::Bencher) {
// cant unwrap because Err contains RequestWriter, which does not implement Show
let mut res = match req.read_response() {
Ok(res) => res,
Err(..) => panic!("http response failed")
Err((_, ioe)) => panic!("http response failed = {}", ioe)
};
res.read_to_string().unwrap();
});
listening.close().unwrap()
}
*/

View File

@@ -9,12 +9,13 @@ use test::Bencher;
use std::io::net::ip::{SocketAddr, Ipv4Addr};
use http::server::Server;
use hyper::method::Method::Get;
use hyper::server::{Request, Response};
static PHRASE: &'static [u8] = b"Benchmarking hyper vs others!";
fn request(url: hyper::Url) {
let req = hyper::client::Request::get(url).unwrap();
let req = hyper::client::Request::new(Get, url).unwrap();
req.start().unwrap().send().unwrap().read_to_string().unwrap();
}

View File

@@ -4,8 +4,7 @@ use std::os;
use std::io::stdout;
use std::io::util::copy;
use hyper::Url;
use hyper::client::Request;
use hyper::Client;
fn main() {
let args = os::args();
@@ -17,26 +16,17 @@ fn main() {
}
};
let url = match Url::parse(args[1].as_slice()) {
Ok(url) => {
println!("GET {}...", url)
url
},
Err(e) => panic!("Invalid URL: {}", e)
};
let url = &*args[1];
let mut client = Client::new();
let req = match Request::get(url) {
Ok(req) => req,
let mut res = match client.get(url).send() {
Ok(res) => res,
Err(err) => panic!("Failed to connect: {}", err)
};
let mut res = req
.start().unwrap() // failure: Error writing Headers
.send().unwrap(); // failure: Error reading Response head.
println!("Response: {}", res.status);
println!("{}", res.headers);
println!("Headers:\n{}", res.headers);
match copy(&mut res, &mut stdout()) {
Ok(..) => (),
Err(e) => panic!("Stream failure: {}", e)

View File

@@ -1,7 +1,399 @@
//! HTTP Client
//!
//! # Usage
//!
//! The `Client` API is designed for most people to make HTTP requests.
//! It utilizes the lower level `Request` API.
//!
//! ```no_run
//! use hyper::Client;
//!
//! let mut client = Client::new();
//!
//! let mut res = client.get("http://example.domain").send().unwrap();
//! assert_eq!(res.status, hyper::Ok);
//! ```
//!
//! The returned value from is a `Response`, which provides easy access
//! to the `status`, the `headers`, and the response body via the `Writer`
//! trait.
use std::default::Default;
use std::io::IoResult;
use std::io::util::copy;
use std::iter::Extend;
use url::UrlParser;
use url::ParseError as UrlError;
use openssl::ssl::VerifyCallback;
use header::{Headers, Header, HeaderFormat};
use header::common::{ContentLength, Location};
use method::Method;
use net::{NetworkConnector, NetworkStream, HttpConnector};
use status::StatusClass::Redirection;
use {Url, Port, HttpResult};
use HttpError::HttpUriError;
pub use self::request::Request;
pub use self::response::Response;
pub mod request;
pub mod response;
/// A Client to use additional features with Requests.
///
/// Clients can handle things such as: redirect policy.
pub struct Client<C> {
connector: C,
redirect_policy: RedirectPolicy,
}
impl Client<HttpConnector> {
/// Create a new Client.
pub fn new() -> Client<HttpConnector> {
Client::with_connector(HttpConnector(None))
}
/// Set the SSL verifier callback for use with OpenSSL.
pub fn set_ssl_verifier(&mut self, verifier: VerifyCallback) {
self.connector = HttpConnector(Some(verifier));
}
}
impl<C: NetworkConnector<S>, S: NetworkStream> Client<C> {
/// Create a new client with a specific connector.
pub fn with_connector(connector: C) -> Client<C> {
Client {
connector: connector,
redirect_policy: Default::default()
}
}
/// Set the RedirectPolicy.
pub fn set_redirect_policy(&mut self, policy: RedirectPolicy) {
self.redirect_policy = policy;
}
/// Execute a Get request.
pub fn get<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U, C, S> {
self.request(Method::Get, url)
}
/// Execute a Head request.
pub fn head<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U, C, S> {
self.request(Method::Head, url)
}
/// Execute a Post request.
pub fn post<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U, C, S> {
self.request(Method::Post, url)
}
/// Execute a Put request.
pub fn put<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U, C, S> {
self.request(Method::Put, url)
}
/// Execute a Delete request.
pub fn delete<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U, C, S> {
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, C, S> {
RequestBuilder {
client: self,
method: method,
url: url,
body: None,
headers: None,
}
}
}
/// Options for an individual Request.
///
/// One of these will be built for you if you use one of the convenience
/// methods, such as `get()`, `post()`, etc.
pub struct RequestBuilder<'a, U: IntoUrl, C: NetworkConnector<S> + 'a, S: NetworkStream> {
client: &'a mut Client<C>,
url: U,
headers: Option<Headers>,
method: Method,
body: Option<Body<'a>>,
}
impl<'a, U: IntoUrl, C: NetworkConnector<S>, S: NetworkStream> RequestBuilder<'a, U, C, S> {
/// Set a request body to be sent.
pub fn body<B: IntoBody<'a>>(mut self, body: B) -> RequestBuilder<'a, U, C, S> {
self.body = Some(body.into_body());
self
}
/// Add additional headers to the request.
pub fn headers(mut self, headers: Headers) -> RequestBuilder<'a, U, C, S> {
self.headers = Some(headers);
self
}
/// Add an individual new header to the request.
pub fn header<H: Header + HeaderFormat>(mut self, header: H) -> RequestBuilder<'a, U, C, S> {
{
let mut headers = match self.headers {
Some(ref mut h) => h,
None => {
self.headers = Some(Headers::new());
self.headers.as_mut().unwrap()
}
};
headers.set(header);
}
self
}
/// Execute this request and receive a Response back.
pub fn send(self) -> HttpResult<Response> {
let RequestBuilder { client, method, url, headers, body } = self;
let mut url = try!(url.into_url());
debug!("client.request {} {}", method, url);
let can_have_body = match &method {
&Method::Get | &Method::Head => false,
_ => true
};
let mut body = if can_have_body {
body.map(|b| b.into_body())
} else {
None
};
loop {
let mut req = try!(Request::with_connector(method.clone(), url.clone(), &mut client.connector));
headers.as_ref().map(|headers| req.headers_mut().extend(headers.iter()));
match (can_have_body, body.as_ref()) {
(true, Some(ref body)) => match body.size() {
Some(size) => req.headers_mut().set(ContentLength(size)),
None => (), // chunked, Request will add it automatically
},
(true, None) => req.headers_mut().set(ContentLength(0)),
_ => () // neither
}
let mut streaming = try!(req.start());
body.take().map(|mut rdr| copy(&mut rdr, &mut streaming));
let res = try!(streaming.send());
if res.status.class() != Redirection {
return Ok(res)
}
debug!("redirect code {} for {}", res.status, url);
let loc = {
// punching borrowck here
let loc = match res.headers.get::<Location>() {
Some(&Location(ref loc)) => {
Some(UrlParser::new().base_url(&url).parse(loc[]))
}
None => {
debug!("no Location header");
// could be 304 Not Modified?
None
}
};
match loc {
Some(r) => r,
None => return Ok(res)
}
};
url = match loc {
Ok(u) => {
inspect!("Location", u)
},
Err(e) => {
debug!("Location header had invalid URI: {}", e);
return Ok(res);
}
};
match client.redirect_policy {
// separate branches because they cant be one
RedirectPolicy::FollowAll => (), //continue
RedirectPolicy::FollowIf(cond) if cond(&url) => (), //continue
_ => return Ok(res),
}
}
}
}
/// A helper trait to allow overloading of the body parameter.
pub trait IntoBody<'a> {
/// Consumes self into an instance of `Body`.
fn into_body(self) -> Body<'a>;
}
/// The target enum for the IntoBody trait.
pub enum Body<'a> {
/// A Reader does not necessarily know it's size, so it is chunked.
ChunkedBody(&'a mut (Reader + 'a)),
/// For Readers that can know their size, like a `File`.
SizedBody(&'a mut (Reader + 'a), uint),
/// A String has a size, and uses Content-Length.
BufBody(&'a [u8] , uint),
}
impl<'a> Body<'a> {
fn size(&self) -> Option<uint> {
match *self {
Body::SizedBody(_, len) | Body::BufBody(_, len) => Some(len),
_ => None
}
}
}
impl<'a> Reader for Body<'a> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
match *self {
Body::ChunkedBody(ref mut r) => r.read(buf),
Body::SizedBody(ref mut r, _) => r.read(buf),
Body::BufBody(ref mut r, _) => r.read(buf),
}
}
}
// To allow someone to pass a `Body::SizedBody()` themselves.
impl<'a> IntoBody<'a> for Body<'a> {
#[inline]
fn into_body(self) -> Body<'a> {
self
}
}
impl<'a> IntoBody<'a> for &'a [u8] {
#[inline]
fn into_body(self) -> Body<'a> {
Body::BufBody(self, self.len())
}
}
impl<'a> IntoBody<'a> for &'a str {
#[inline]
fn into_body(self) -> Body<'a> {
self.as_bytes().into_body()
}
}
impl<'a, R: Reader> IntoBody<'a> for &'a mut R {
#[inline]
fn into_body(self) -> Body<'a> {
Body::ChunkedBody(self)
}
}
/// A helper trait to convert common objects into a Url.
pub trait IntoUrl {
/// Consumes the object, trying to return a Url.
fn into_url(self) -> Result<Url, UrlError>;
}
impl IntoUrl for Url {
fn into_url(self) -> Result<Url, UrlError> {
Ok(self)
}
}
impl<'a> IntoUrl for &'a str {
fn into_url(self) -> Result<Url, UrlError> {
Url::parse(self)
}
}
/// Behavior regarding how to handle redirects within a Client.
#[deriving(Copy, Clone)]
pub enum RedirectPolicy {
/// Don't follow any redirects.
FollowNone,
/// Follow all redirects.
FollowAll,
/// Follow a redirect if the contained function returns true.
FollowIf(fn(&Url) -> bool),
}
impl Default for RedirectPolicy {
fn default() -> RedirectPolicy {
RedirectPolicy::FollowAll
}
}
fn get_host_and_port(url: &Url) -> HttpResult<(String, Port)> {
let host = match url.serialize_host() {
Some(host) => host,
None => return Err(HttpUriError(UrlError::EmptyHost))
};
debug!("host={}", host);
let port = match url.port_or_default() {
Some(port) => port,
None => return Err(HttpUriError(UrlError::InvalidPort))
};
debug!("port={}", port);
Ok((host, port))
}
#[cfg(test)]
mod tests {
use header::common::Server;
use super::{Client, RedirectPolicy};
use url::Url;
mock_connector!(MockRedirectPolicy {
"http://127.0.0.1" => "HTTP/1.1 301 Redirect\r\n\
Location: http://127.0.0.2\r\n\
Server: mock1\r\n\
\r\n\
"
"http://127.0.0.2" => "HTTP/1.1 302 Found\r\n\
Location: https://127.0.0.3\r\n\
Server: mock2\r\n\
\r\n\
"
"https://127.0.0.3" => "HTTP/1.1 200 OK\r\n\
Server: mock3\r\n\
\r\n\
"
})
#[test]
fn test_redirect_followall() {
let mut client = Client::with_connector(MockRedirectPolicy);
client.set_redirect_policy(RedirectPolicy::FollowAll);
let res = client.get("http://127.0.0.1").send().unwrap();
assert_eq!(res.headers.get(), Some(&Server("mock3".into_string())));
}
#[test]
fn test_redirect_dontfollow() {
let mut client = Client::with_connector(MockRedirectPolicy);
client.set_redirect_policy(RedirectPolicy::FollowNone);
let res = client.get("http://127.0.0.1").send().unwrap();
assert_eq!(res.headers.get(), Some(&Server("mock1".into_string())));
}
#[test]
fn test_redirect_followif() {
fn follow_if(url: &Url) -> bool {
!url.serialize()[].contains("127.0.0.3")
}
let mut client = Client::with_connector(MockRedirectPolicy);
client.set_redirect_policy(RedirectPolicy::FollowIf(follow_if));
let res = client.get("http://127.0.0.1").send().unwrap();
assert_eq!(res.headers.get(), Some(&Server("mock2".into_string())));
}
}

View File

@@ -8,12 +8,11 @@ use method::Method::{Get, Post, Delete, Put, Patch, Head, Options};
use header::Headers;
use header::common::{mod, Host};
use net::{NetworkStream, NetworkConnector, HttpConnector, Fresh, Streaming};
use HttpError::HttpUriError;
use http::{HttpWriter, LINE_ENDING};
use http::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
use version;
use HttpResult;
use client::Response;
use client::{Response, get_host_and_port};
/// A client request to a remote server.
@@ -42,23 +41,14 @@ impl<W> Request<W> {
impl Request<Fresh> {
/// Create a new client request.
pub fn new(method: method::Method, url: Url) -> HttpResult<Request<Fresh>> {
let mut conn = HttpConnector;
let mut conn = HttpConnector(None);
Request::with_connector(method, url, &mut conn)
}
/// Create a new client request with a specific underlying NetworkStream.
pub fn with_connector<C: NetworkConnector<S>, S: NetworkStream>(method: method::Method, url: Url, connector: &mut C) -> HttpResult<Request<Fresh>> {
debug!("{} {}", method, url);
let host = match url.serialize_host() {
Some(host) => host,
None => return Err(HttpUriError)
};
debug!("host={}", host);
let port = match url.port_or_default() {
Some(port) => port,
None => return Err(HttpUriError)
};
debug!("port={}", port);
let (host, port) = try!(get_host_and_port(&url));
let stream: S = try!(connector.connect(host[], port, &*url.scheme));
let stream = ThroughWriter(BufferedWriter::new(box stream as Box<NetworkStream + Send>));
@@ -80,30 +70,37 @@ impl Request<Fresh> {
/// Create a new GET request.
#[inline]
#[deprecated = "use hyper::Client"]
pub fn get(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Get, url) }
/// Create a new POST request.
#[inline]
#[deprecated = "use hyper::Client"]
pub fn post(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Post, url) }
/// Create a new DELETE request.
#[inline]
#[deprecated = "use hyper::Client"]
pub fn delete(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Delete, url) }
/// Create a new PUT request.
#[inline]
#[deprecated = "use hyper::Client"]
pub fn put(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Put, url) }
/// Create a new PATCH request.
#[inline]
#[deprecated = "use hyper::Client"]
pub fn patch(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Patch, url) }
/// Create a new HEAD request.
#[inline]
#[deprecated = "use hyper::Client"]
pub fn head(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Head, url) }
/// Create a new OPTIONS request.
#[inline]
#[deprecated = "use hyper::Client"]
pub fn options(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Options, url) }
/// Consume a Fresh Request, writing the headers and method,

View File

@@ -38,7 +38,7 @@ impl Response {
debug!("{} {}", version, status);
let headers = try!(header::Headers::from_raw(&mut stream));
debug!("{}", headers);
debug!("Headers: [\n{}]", headers);
let body = if headers.has::<TransferEncoding>() {
match headers.get::<TransferEncoding>() {

View File

@@ -30,7 +30,7 @@ pub mod common;
///
/// This trait represents the construction and identification of headers,
/// and contains trait-object unsafe methods.
pub trait Header: Any + Send + Sync {
pub trait Header: Clone + Any + Send + Sync {
/// Returns the name of the header field this belongs to.
///
/// The market `Option` is to hint to the type system which implementation

View File

@@ -7,6 +7,7 @@ use std::num::from_u16;
use std::str::{mod, SendStr};
use url::Url;
use url::ParseError as UrlError;
use method;
use status::StatusCode;
@@ -234,6 +235,7 @@ impl<W: Writer> Writer for HttpWriter<W> {
ThroughWriter(ref mut w) => w.write(msg),
ChunkedWriter(ref mut w) => {
let chunk_size = msg.len();
debug!("chunked write, size = {}", chunk_size);
try!(write!(w, "{:X}{}{}", chunk_size, CR as char, LF as char));
try!(w.write(msg));
w.write(LINE_ENDING)
@@ -419,7 +421,7 @@ pub fn read_uri<R: Reader>(stream: &mut R) -> HttpResult<uri::RequestUri> {
break;
},
CR | LF => {
return Err(HttpUriError)
return Err(HttpUriError(UrlError::InvalidCharacter))
},
b => s.push(b as char)
}
@@ -431,27 +433,14 @@ pub fn read_uri<R: Reader>(stream: &mut R) -> HttpResult<uri::RequestUri> {
if s.as_slice().starts_with("/") {
Ok(AbsolutePath(s))
} else if s.as_slice().contains("/") {
match Url::parse(s.as_slice()) {
Ok(u) => Ok(AbsoluteUri(u)),
Err(_e) => {
debug!("URL err {}", _e);
Err(HttpUriError)
}
}
Ok(AbsoluteUri(try!(Url::parse(s.as_slice()))))
} else {
let mut temp = "http://".to_string();
temp.push_str(s.as_slice());
match Url::parse(temp.as_slice()) {
Ok(_u) => {
try!(Url::parse(temp.as_slice()));
todo!("compare vs u.authority()");
Ok(Authority(s))
}
Err(_e) => {
debug!("URL err {}", _e);
Err(HttpUriError)
}
}
}
}

View File

@@ -138,6 +138,7 @@ extern crate mucell;
pub use std::io::net::ip::{SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr, Port};
pub use mimewrapper::mime;
pub use url::Url;
pub use client::Client;
pub use method::Method::{Get, Head, Post, Delete};
pub use status::StatusCode::{Ok, BadRequest, NotFound};
pub use server::Server;
@@ -181,6 +182,10 @@ macro_rules! inspect(
})
)
#[cfg(test)]
#[macro_escape]
mod mock;
pub mod client;
pub mod method;
pub mod header;
@@ -191,7 +196,6 @@ pub mod status;
pub mod uri;
pub mod version;
#[cfg(test)] mod mock;
mod mimewrapper {
/// Re-exporting the mime crate, for convenience.
@@ -208,7 +212,7 @@ pub enum HttpError {
/// An invalid `Method`, such as `GE,T`.
HttpMethodError,
/// An invalid `RequestUri`, such as `exam ple.domain`.
HttpUriError,
HttpUriError(url::ParseError),
/// An invalid `HttpVersion`, such as `HTP/1.1`
HttpVersionError,
/// An invalid `Header`.
@@ -223,7 +227,7 @@ impl Error for HttpError {
fn description(&self) -> &str {
match *self {
HttpMethodError => "Invalid Method specified",
HttpUriError => "Invalid Request URI specified",
HttpUriError(_) => "Invalid Request URI specified",
HttpVersionError => "Invalid HTTP version specified",
HttpHeaderError => "Invalid Header provided",
HttpStatusError => "Invalid Status provided",
@@ -234,6 +238,7 @@ impl Error for HttpError {
fn cause(&self) -> Option<&Error> {
match *self {
HttpIoError(ref error) => Some(error as &Error),
HttpUriError(ref error) => Some(error as &Error),
_ => None,
}
}
@@ -245,6 +250,12 @@ impl FromError<IoError> for HttpError {
}
}
impl FromError<url::ParseError> for HttpError {
fn from_error(err: url::ParseError) -> HttpError {
HttpUriError(err)
}
}
//FIXME: when Opt-in Built-in Types becomes a thing, we can force these structs
//to be Send. For now, this has the compiler do a static check.
fn _assert_send<T: Send>() {

View File

@@ -73,3 +73,35 @@ impl NetworkConnector<MockStream> for MockConnector {
Ok(MockStream::new())
}
}
/// new connectors must be created if you wish to intercept requests.
macro_rules! mock_connector (
($name:ident {
$($url:expr => $res:expr)*
}) => (
struct $name;
impl ::net::NetworkConnector<::mock::MockStream> for $name {
fn connect(&mut self, host: &str, port: u16, scheme: &str) -> ::std::io::IoResult<::mock::MockStream> {
use std::collections::HashMap;
debug!("MockStream::connect({}, {}, {})", host, port, scheme);
let mut map = HashMap::new();
$(map.insert($url, $res);)*
let key = format!("{}://{}", scheme, host);
// ignore port for now
match map.find(&&*key) {
Some(res) => Ok(::mock::MockStream {
write: ::std::io::MemWriter::new(),
read: ::std::io::MemReader::new(res.to_string().into_bytes())
}),
None => panic!("{} doesn't know url {}", stringify!($name), key)
}
}
}
)
)

View File

@@ -11,7 +11,8 @@ use std::mem::{mod, transmute, transmute_copy};
use std::raw::{mod, TraitObject};
use uany::UncheckedBoxAnyDowncast;
use openssl::ssl::{SslStream, SslContext, Ssl};
use openssl::ssl::{Ssl, SslStream, SslContext, VerifyCallback};
use openssl::ssl::SslVerifyMode::SslVerifyPeer;
use openssl::ssl::SslMethod::Sslv23;
use openssl::ssl::error::{SslError, StreamError, OpenSslErrors, SslSessionClosed};
@@ -239,7 +240,7 @@ impl NetworkStream for HttpStream {
/// A connector that will produce HttpStreams.
#[allow(missing_copy_implementations)]
pub struct HttpConnector;
pub struct HttpConnector(pub Option<VerifyCallback>);
impl NetworkConnector<HttpStream> for HttpConnector {
fn connect(&mut self, host: &str, port: Port, scheme: &str) -> IoResult<HttpStream> {
@@ -252,12 +253,11 @@ impl NetworkConnector<HttpStream> for HttpConnector {
"https" => {
debug!("https scheme");
let stream = try!(TcpStream::connect(addr));
let context = try!(SslContext::new(Sslv23).map_err(lift_ssl_error));
let mut context = try!(SslContext::new(Sslv23).map_err(lift_ssl_error));
self.0.as_ref().map(|cb| context.set_verify(SslVerifyPeer, Some(*cb)));
let ssl = try!(Ssl::new(&context).map_err(lift_ssl_error));
debug!("ssl set_hostname = {}", host);
try!(ssl.set_hostname(host).map_err(lift_ssl_error));
debug!("ssl set_hostname done");
let stream = try!(SslStream::new_from(ssl, stream).map_err(lift_ssl_error));
let stream = try!(SslStream::new(&context, stream).map_err(lift_ssl_error));
Ok(Https(stream))
},
_ => {

View File

@@ -105,8 +105,8 @@ impl<L: NetworkListener<S, A>, S: NetworkStream, A: NetworkAcceptor<S>> Server<L
};
keep_alive = match (req.version, req.headers.get::<Connection>()) {
(Http10, Some(conn)) if !conn.0.contains(&KeepAlive) => false,
(Http11, Some(conn)) if conn.0.contains(&Close) => false,
(Http10, Some(conn)) if !conn.contains(&KeepAlive) => false,
(Http11, Some(conn)) if conn.contains(&Close) => false,
_ => true
};
res.version = req.version;