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:
219
src/client/connect.rs
Normal file
219
src/client/connect.rs
Normal 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
84
src/client/dns.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
1048
src/client/mod.rs
1048
src/client/mod.rs
File diff suppressed because it is too large
Load Diff
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user