feat(lib): switch to non-blocking (asynchronous) IO

BREAKING CHANGE: This breaks a lot of the Client and Server APIs.
  Check the documentation for how Handlers can be used for asynchronous
  events.
This commit is contained in:
Sean McArthur
2016-05-03 20:45:43 -07:00
parent 1ec56fe6b6
commit d35992d019
65 changed files with 5599 additions and 5023 deletions

219
src/client/connect.rs Normal file
View File

@@ -0,0 +1,219 @@
use std::collections::hash_map::{HashMap, Entry};
use std::hash::Hash;
use std::fmt;
use std::io;
use std::net::SocketAddr;
use rotor::mio::tcp::TcpStream;
use url::Url;
use net::{HttpStream, HttpsStream, Transport, SslClient};
use super::dns::Dns;
use super::Registration;
/// A connector creates a Transport to a remote address..
pub trait Connect {
/// Type of Transport to create
type Output: Transport;
/// The key used to determine if an existing socket can be used.
type Key: Eq + Hash + Clone;
/// Returns the key based off the Url.
fn key(&self, &Url) -> Option<Self::Key>;
/// Connect to a remote address.
fn connect(&mut self, &Url) -> io::Result<Self::Key>;
/// Returns a connected socket and associated host.
fn connected(&mut self) -> Option<(Self::Key, io::Result<Self::Output>)>;
#[doc(hidden)]
fn register(&mut self, Registration);
}
type Scheme = String;
type Port = u16;
/// A connector for the `http` scheme.
pub struct HttpConnector {
dns: Option<Dns>,
threads: usize,
resolving: HashMap<String, Vec<(&'static str, String, u16)>>,
}
impl HttpConnector {
/// Set the number of resolver threads.
///
/// Default is 4.
pub fn threads(mut self, threads: usize) -> HttpConnector {
debug_assert!(self.dns.is_none(), "setting threads after Dns is created does nothing");
self.threads = threads;
self
}
}
impl Default for HttpConnector {
fn default() -> HttpConnector {
HttpConnector {
dns: None,
threads: 4,
resolving: HashMap::new(),
}
}
}
impl fmt::Debug for HttpConnector {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("HttpConnector")
.field("threads", &self.threads)
.field("resolving", &self.resolving)
.finish()
}
}
impl Connect for HttpConnector {
type Output = HttpStream;
type Key = (&'static str, String, u16);
fn key(&self, url: &Url) -> Option<Self::Key> {
if url.scheme() == "http" {
Some((
"http",
url.host_str().expect("http scheme must have host").to_owned(),
url.port().unwrap_or(80),
))
} else {
None
}
}
fn connect(&mut self, url: &Url) -> io::Result<Self::Key> {
debug!("Http::connect({:?})", url);
if let Some(key) = self.key(url) {
let host = url.host_str().expect("http scheme must have a host");
self.dns.as_ref().expect("dns workers lost").resolve(host);
self.resolving.entry(host.to_owned()).or_insert(Vec::new()).push(key.clone());
Ok(key)
} else {
Err(io::Error::new(io::ErrorKind::InvalidInput, "scheme must be http"))
}
}
fn connected(&mut self) -> Option<(Self::Key, io::Result<HttpStream>)> {
let (host, addr) = match self.dns.as_ref().expect("dns workers lost").resolved() {
Ok(res) => res,
Err(_) => return None
};
debug!("Http::resolved <- ({:?}, {:?})", host, addr);
match self.resolving.entry(host) {
Entry::Occupied(mut entry) => {
let resolved = entry.get_mut().remove(0);
if entry.get().is_empty() {
entry.remove();
}
let port = resolved.2;
match addr {
Ok(addr) => {
Some((resolved, TcpStream::connect(&SocketAddr::new(addr, port))
.map(HttpStream)))
},
Err(e) => Some((resolved, Err(e)))
}
}
_ => {
trace!("^-- resolved but not in hashmap?");
return None
}
}
}
fn register(&mut self, reg: Registration) {
self.dns = Some(Dns::new(reg.notify, 4));
}
}
/// A connector that can protect HTTP streams using SSL.
#[derive(Debug, Default)]
pub struct HttpsConnector<S: SslClient> {
http: HttpConnector,
ssl: S
}
impl<S: SslClient> HttpsConnector<S> {
/// Create a new connector using the provided SSL implementation.
pub fn new(s: S) -> HttpsConnector<S> {
HttpsConnector {
http: HttpConnector::default(),
ssl: s,
}
}
}
impl<S: SslClient> Connect for HttpsConnector<S> {
type Output = HttpsStream<S::Stream>;
type Key = (&'static str, String, u16);
fn key(&self, url: &Url) -> Option<Self::Key> {
let scheme = match url.scheme() {
"http" => "http",
"https" => "https",
_ => return None
};
Some((
scheme,
url.host_str().expect("http scheme must have host").to_owned(),
url.port_or_known_default().expect("http scheme must have a port"),
))
}
fn connect(&mut self, url: &Url) -> io::Result<Self::Key> {
debug!("Https::connect({:?})", url);
if let Some(key) = self.key(url) {
let host = url.host_str().expect("http scheme must have a host");
self.http.dns.as_ref().expect("dns workers lost").resolve(host);
self.http.resolving.entry(host.to_owned()).or_insert(Vec::new()).push(key.clone());
Ok(key)
} else {
Err(io::Error::new(io::ErrorKind::InvalidInput, "scheme must be http or https"))
}
}
fn connected(&mut self) -> Option<(Self::Key, io::Result<Self::Output>)> {
self.http.connected().map(|(key, res)| {
let res = res.and_then(|http| {
if key.0 == "https" {
self.ssl.wrap_client(http, &key.1)
.map(HttpsStream::Https)
.map_err(|e| match e {
::Error::Io(e) => e,
e => io::Error::new(io::ErrorKind::Other, e)
})
} else {
Ok(HttpsStream::Http(http))
}
});
(key, res)
})
}
fn register(&mut self, reg: Registration) {
self.http.register(reg);
}
}
#[cfg(not(any(feature = "openssl", feature = "security-framework")))]
#[doc(hidden)]
pub type DefaultConnector = HttpConnector;
#[cfg(all(feature = "openssl", not(feature = "security-framework")))]
#[doc(hidden)]
pub type DefaultConnector = HttpsConnector<::net::Openssl>;
#[cfg(feature = "security-framework")]
#[doc(hidden)]
pub type DefaultConnector = HttpsConnector<::net::SecureTransportClient>;
#[doc(hidden)]
pub type DefaultTransport = <DefaultConnector as Connect>::Output;
fn _assert_defaults() {
fn _assert<T, U>() where T: Connect<Output=U>, U: Transport {}
_assert::<DefaultConnector, DefaultTransport>();
}

84
src/client/dns.rs Normal file
View File

@@ -0,0 +1,84 @@
use std::io;
use std::net::{IpAddr, ToSocketAddrs};
use std::thread;
use ::spmc;
use http::channel;
pub struct Dns {
tx: spmc::Sender<String>,
rx: channel::Receiver<Answer>,
}
pub type Answer = (String, io::Result<IpAddr>);
impl Dns {
pub fn new(notify: (channel::Sender<Answer>, channel::Receiver<Answer>), threads: usize) -> Dns {
let (tx, rx) = spmc::channel();
for _ in 0..threads {
work(rx.clone(), notify.0.clone());
}
Dns {
tx: tx,
rx: notify.1,
}
}
pub fn resolve<T: Into<String>>(&self, hostname: T) {
self.tx.send(hostname.into()).expect("Workers all died horribly");
}
pub fn resolved(&self) -> Result<Answer, channel::TryRecvError> {
self.rx.try_recv()
}
}
fn work(rx: spmc::Receiver<String>, notify: channel::Sender<Answer>) {
thread::spawn(move || {
let mut worker = Worker::new(rx, notify);
let rx = worker.rx.as_ref().expect("Worker lost rx");
let notify = worker.notify.as_ref().expect("Worker lost notify");
while let Ok(host) = rx.recv() {
debug!("resolve {:?}", host);
let res = match (&*host, 80).to_socket_addrs().map(|mut i| i.next()) {
Ok(Some(addr)) => (host, Ok(addr.ip())),
Ok(None) => (host, Err(io::Error::new(io::ErrorKind::Other, "no addresses found"))),
Err(e) => (host, Err(e))
};
if let Err(_) = notify.send(res) {
break;
}
}
worker.shutdown = true;
});
}
struct Worker {
rx: Option<spmc::Receiver<String>>,
notify: Option<channel::Sender<Answer>>,
shutdown: bool,
}
impl Worker {
fn new(rx: spmc::Receiver<String>, notify: channel::Sender<Answer>) -> Worker {
Worker {
rx: Some(rx),
notify: Some(notify),
shutdown: false,
}
}
}
impl Drop for Worker {
fn drop(&mut self) {
if !self.shutdown {
trace!("Worker.drop panicked, restarting");
work(self.rx.take().expect("Worker lost rx"),
self.notify.take().expect("Worker lost notify"));
} else {
trace!("Worker.drop shutdown, closing");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,240 +0,0 @@
use std::borrow::Cow;
use std::io;
use std::net::{SocketAddr, Shutdown};
use std::time::Duration;
use method::Method;
use net::{NetworkConnector, HttpConnector, NetworkStream, SslClient};
#[cfg(all(feature = "openssl", not(feature = "security-framework")))]
pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, ::net::Openssl> {
Proxy {
connector: HttpConnector,
proxy: proxy,
ssl: Default::default()
}
}
#[cfg(feature = "security-framework")]
pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, ::net::Openssl> {
Proxy {
connector: HttpConnector,
proxy: proxy,
ssl: Default::default()
}
}
#[cfg(not(any(feature = "openssl", feature = "security-framework")))]
pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, self::no_ssl::Plaintext> {
Proxy {
connector: HttpConnector,
proxy: proxy,
ssl: self::no_ssl::Plaintext,
}
}
pub struct Proxy<C, S>
where C: NetworkConnector + Send + Sync + 'static,
C::Stream: NetworkStream + Send + Clone,
S: SslClient<C::Stream> {
pub connector: C,
pub proxy: (Cow<'static, str>, u16),
pub ssl: S,
}
impl<C, S> NetworkConnector for Proxy<C, S>
where C: NetworkConnector + Send + Sync + 'static,
C::Stream: NetworkStream + Send + Clone,
S: SslClient<C::Stream> {
type Stream = Proxied<C::Stream, S::Stream>;
fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result<Self::Stream> {
use httparse;
use std::io::{Read, Write};
use ::version::HttpVersion::Http11;
trace!("{:?} proxy for '{}://{}:{}'", self.proxy, scheme, host, port);
match scheme {
"http" => {
self.connector.connect(self.proxy.0.as_ref(), self.proxy.1, "http")
.map(Proxied::Normal)
},
"https" => {
let mut stream = try!(self.connector.connect(self.proxy.0.as_ref(), self.proxy.1, "http"));
trace!("{:?} CONNECT {}:{}", self.proxy, host, port);
try!(write!(&mut stream, "{method} {host}:{port} {version}\r\nHost: {host}:{port}\r\n\r\n",
method=Method::Connect, host=host, port=port, version=Http11));
try!(stream.flush());
let mut buf = [0; 1024];
let mut n = 0;
while n < buf.len() {
n += try!(stream.read(&mut buf[n..]));
let mut headers = [httparse::EMPTY_HEADER; 10];
let mut res = httparse::Response::new(&mut headers);
if try!(res.parse(&buf[..n])).is_complete() {
let code = res.code.expect("complete parsing lost code");
if code >= 200 && code < 300 {
trace!("CONNECT success = {:?}", code);
return self.ssl.wrap_client(stream, host)
.map(Proxied::Tunneled)
} else {
trace!("CONNECT response = {:?}", code);
return Err(::Error::Status);
}
}
}
Err(::Error::TooLarge)
},
_ => Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid scheme").into())
}
}
}
#[derive(Debug)]
pub enum Proxied<T1, T2> {
Normal(T1),
Tunneled(T2)
}
#[cfg(test)]
impl<T1, T2> Proxied<T1, T2> {
pub fn into_normal(self) -> Result<T1, Self> {
match self {
Proxied::Normal(t1) => Ok(t1),
_ => Err(self)
}
}
pub fn into_tunneled(self) -> Result<T2, Self> {
match self {
Proxied::Tunneled(t2) => Ok(t2),
_ => Err(self)
}
}
}
impl<T1: NetworkStream, T2: NetworkStream> io::Read for Proxied<T1, T2> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
Proxied::Normal(ref mut t) => io::Read::read(t, buf),
Proxied::Tunneled(ref mut t) => io::Read::read(t, buf),
}
}
}
impl<T1: NetworkStream, T2: NetworkStream> io::Write for Proxied<T1, T2> {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match *self {
Proxied::Normal(ref mut t) => io::Write::write(t, buf),
Proxied::Tunneled(ref mut t) => io::Write::write(t, buf),
}
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
match *self {
Proxied::Normal(ref mut t) => io::Write::flush(t),
Proxied::Tunneled(ref mut t) => io::Write::flush(t),
}
}
}
impl<T1: NetworkStream, T2: NetworkStream> NetworkStream for Proxied<T1, T2> {
#[inline]
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
match *self {
Proxied::Normal(ref mut s) => s.peer_addr(),
Proxied::Tunneled(ref mut s) => s.peer_addr()
}
}
#[inline]
fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
match *self {
Proxied::Normal(ref inner) => inner.set_read_timeout(dur),
Proxied::Tunneled(ref inner) => inner.set_read_timeout(dur)
}
}
#[inline]
fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
match *self {
Proxied::Normal(ref inner) => inner.set_write_timeout(dur),
Proxied::Tunneled(ref inner) => inner.set_write_timeout(dur)
}
}
#[inline]
fn close(&mut self, how: Shutdown) -> io::Result<()> {
match *self {
Proxied::Normal(ref mut s) => s.close(how),
Proxied::Tunneled(ref mut s) => s.close(how)
}
}
}
#[cfg(not(any(feature = "openssl", feature = "security-framework")))]
mod no_ssl {
use std::io;
use std::net::{Shutdown, SocketAddr};
use std::time::Duration;
use net::{SslClient, NetworkStream};
pub struct Plaintext;
#[derive(Clone)]
pub enum Void {}
impl io::Read for Void {
#[inline]
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
match *self {}
}
}
impl io::Write for Void {
#[inline]
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
match *self {}
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
match *self {}
}
}
impl NetworkStream for Void {
#[inline]
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
match *self {}
}
#[inline]
fn set_read_timeout(&self, _dur: Option<Duration>) -> io::Result<()> {
match *self {}
}
#[inline]
fn set_write_timeout(&self, _dur: Option<Duration>) -> io::Result<()> {
match *self {}
}
#[inline]
fn close(&mut self, _how: Shutdown) -> io::Result<()> {
match *self {}
}
}
impl<T: NetworkStream + Send + Clone> SslClient<T> for Plaintext {
type Stream = Void;
fn wrap_client(&self, _stream: T, _host: &str) -> ::Result<Self::Stream> {
Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid scheme").into())
}
}
}

View File

@@ -1,177 +1,52 @@
//! Client Requests
use std::marker::PhantomData;
use std::io::{self, Write};
use std::time::Duration;
use url::Url;
use method::Method;
use header::Headers;
use header::Host;
use net::{NetworkStream, NetworkConnector, DefaultConnector, Fresh, Streaming};
use version;
use client::{Response, get_host_and_port};
use http::RequestHead;
use method::Method;
use uri::RequestUri;
use version::HttpVersion;
use http::{HttpMessage, RequestHead};
use http::h1::Http11Message;
/// A client request to a remote server.
/// The W type tracks the state of the request, Fresh vs Streaming.
pub struct Request<W> {
/// The target URI for this request.
pub url: Url,
/// The HTTP version of this request.
pub version: version::HttpVersion,
message: Box<HttpMessage>,
headers: Headers,
method: Method,
_marker: PhantomData<W>,
#[derive(Debug)]
pub struct Request<'a> {
head: &'a mut RequestHead
}
impl<W> Request<W> {
impl<'a> Request<'a> {
/// Read the Request Url.
#[inline]
pub fn uri(&self) -> &RequestUri { &self.head.subject.1 }
/// Readthe Request Version.
#[inline]
pub fn version(&self) -> &HttpVersion { &self.head.version }
/// Read the Request headers.
#[inline]
pub fn headers(&self) -> &Headers { &self.headers }
pub fn headers(&self) -> &Headers { &self.head.headers }
/// Read the Request method.
#[inline]
pub fn method(&self) -> Method { self.method.clone() }
pub fn method(&self) -> &Method { &self.head.subject.0 }
/// Set the write timeout.
/// Set the Method of this request.
#[inline]
pub fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
self.message.set_write_timeout(dur)
}
/// Set the read timeout.
#[inline]
pub fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
self.message.set_read_timeout(dur)
}
}
impl Request<Fresh> {
/// Create a new `Request<Fresh>` that will use the given `HttpMessage` for its communication
/// with the server. This implies that the given `HttpMessage` instance has already been
/// properly initialized by the caller (e.g. a TCP connection's already established).
pub fn with_message(method: Method, url: Url, message: Box<HttpMessage>)
-> ::Result<Request<Fresh>> {
let mut headers = Headers::new();
{
let (host, port) = try!(get_host_and_port(&url));
headers.set(Host {
hostname: host.to_owned(),
port: Some(port),
});
}
Ok(Request::with_headers_and_message(method, url, headers, message))
}
#[doc(hidden)]
pub fn with_headers_and_message(method: Method, url: Url, headers: Headers, message: Box<HttpMessage>)
-> Request<Fresh> {
Request {
method: method,
headers: headers,
url: url,
version: version::HttpVersion::Http11,
message: message,
_marker: PhantomData,
}
}
/// Create a new client request.
pub fn new(method: Method, url: Url) -> ::Result<Request<Fresh>> {
let conn = DefaultConnector::default();
Request::with_connector(method, url, &conn)
}
/// Create a new client request with a specific underlying NetworkStream.
pub fn with_connector<C, S>(method: Method, url: Url, connector: &C)
-> ::Result<Request<Fresh>> where
C: NetworkConnector<Stream=S>,
S: Into<Box<NetworkStream + Send>> {
let stream = {
let (host, port) = try!(get_host_and_port(&url));
try!(connector.connect(host, port, url.scheme())).into()
};
Request::with_message(method, url, Box::new(Http11Message::with_stream(stream)))
}
/// Consume a Fresh Request, writing the headers and method,
/// returning a Streaming Request.
pub fn start(mut self) -> ::Result<Request<Streaming>> {
let head = match self.message.set_outgoing(RequestHead {
headers: self.headers,
method: self.method,
url: self.url,
}) {
Ok(head) => head,
Err(e) => {
let _ = self.message.close_connection();
return Err(From::from(e));
}
};
Ok(Request {
method: head.method,
headers: head.headers,
url: head.url,
version: self.version,
message: self.message,
_marker: PhantomData,
})
}
pub fn set_method(&mut self, method: Method) { self.head.subject.0 = method; }
/// Get a mutable reference to the Request headers.
#[inline]
pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers }
pub fn headers_mut(&mut self) -> &mut Headers { &mut self.head.headers }
}
impl Request<Streaming> {
/// Completes writing the request, and returns a response to read from.
///
/// Consumes the Request.
pub fn send(self) -> ::Result<Response> {
Response::with_message(self.url, self.message)
}
}
impl Write for Request<Streaming> {
#[inline]
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
match self.message.write(msg) {
Ok(n) => Ok(n),
Err(e) => {
let _ = self.message.close_connection();
Err(e)
}
}
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
match self.message.flush() {
Ok(r) => Ok(r),
Err(e) => {
let _ = self.message.close_connection();
Err(e)
}
}
}
pub fn new(head: &mut RequestHead) -> Request {
Request { head: head }
}
#[cfg(test)]
mod tests {
/*
use std::io::Write;
use std::str::from_utf8;
use url::Url;
@@ -311,4 +186,5 @@ mod tests {
.get_ref().downcast_ref::<MockStream>().unwrap()
.is_closed);
}
*/
}

View File

@@ -1,82 +1,57 @@
//! Client Responses
use std::io::{self, Read};
use url::Url;
use header;
use net::NetworkStream;
use http::{self, RawStatus, ResponseHead, HttpMessage};
use http::h1::Http11Message;
//use net::NetworkStream;
use http::{self, RawStatus};
use status;
use version;
pub fn new(incoming: http::ResponseHead) -> Response {
trace!("Response::new");
let status = status::StatusCode::from_u16(incoming.subject.0);
debug!("version={:?}, status={:?}", incoming.version, status);
debug!("headers={:?}", incoming.headers);
Response {
status: status,
version: incoming.version,
headers: incoming.headers,
status_raw: incoming.subject,
}
}
/// A response for a client request to a remote server.
#[derive(Debug)]
pub struct Response {
/// The status from the server.
pub status: status::StatusCode,
/// The headers from the server.
pub headers: header::Headers,
/// The HTTP version of this response from the server.
pub version: version::HttpVersion,
/// The final URL of this response.
pub url: Url,
status: status::StatusCode,
headers: header::Headers,
version: version::HttpVersion,
status_raw: RawStatus,
message: Box<HttpMessage>,
}
impl Response {
/// Get the headers from the server.
#[inline]
pub fn headers(&self) -> &header::Headers { &self.headers }
/// Creates a new response from a server.
pub fn new(url: Url, stream: Box<NetworkStream + Send>) -> ::Result<Response> {
trace!("Response::new");
Response::with_message(url, Box::new(Http11Message::with_stream(stream)))
}
/// Creates a new response received from the server on the given `HttpMessage`.
pub fn with_message(url: Url, mut message: Box<HttpMessage>) -> ::Result<Response> {
trace!("Response::with_message");
let ResponseHead { headers, raw_status, version } = match message.get_incoming() {
Ok(head) => head,
Err(e) => {
let _ = message.close_connection();
return Err(From::from(e));
}
};
let status = status::StatusCode::from_u16(raw_status.0);
debug!("version={:?}, status={:?}", version, status);
debug!("headers={:?}", headers);
Ok(Response {
status: status,
version: version,
headers: headers,
url: url,
status_raw: raw_status,
message: message,
})
}
/// Get the status from the server.
#[inline]
pub fn status(&self) -> &status::StatusCode { &self.status }
/// Get the raw status code and reason.
#[inline]
pub fn status_raw(&self) -> &RawStatus {
&self.status_raw
}
}
pub fn status_raw(&self) -> &RawStatus { &self.status_raw }
impl Read for Response {
/// Get the final URL of this response.
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self.message.read(buf) {
Err(e) => {
let _ = self.message.close_connection();
Err(e)
}
r => r
}
}
//pub fn url(&self) -> &Url { &self.url }
/// Get the HTTP version of this response from the server.
#[inline]
pub fn version(&self) -> &version::HttpVersion { &self.version }
}
/*
impl Drop for Response {
fn drop(&mut self) {
// if not drained, theres old bits in the Reader. we can't reuse this,
@@ -94,9 +69,11 @@ impl Drop for Response {
}
}
}
*/
#[cfg(test)]
mod tests {
/*
use std::io::{self, Read};
use url::Url;
@@ -230,4 +207,5 @@ mod tests {
assert!(Response::new(url, Box::new(stream)).is_err());
}
*/
}