Merge pull request #775 from hyperium/774-proxy-host
fix(client): fix Host header when using a Proxy
This commit is contained in:
@@ -20,7 +20,20 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = Client::new();
|
let client = match env::var("HTTP_PROXY") {
|
||||||
|
Ok(mut proxy) => {
|
||||||
|
// parse the proxy, message if it doesn't make sense
|
||||||
|
let mut port = 80;
|
||||||
|
if let Some(colon) = proxy.rfind(':') {
|
||||||
|
port = proxy[colon + 1..].parse().unwrap_or_else(|e| {
|
||||||
|
panic!("HTTP_PROXY is malformed: {:?}, port parse error: {}", proxy, e);
|
||||||
|
});
|
||||||
|
proxy.truncate(colon);
|
||||||
|
}
|
||||||
|
Client::with_http_proxy(proxy, port)
|
||||||
|
},
|
||||||
|
_ => Client::new()
|
||||||
|
};
|
||||||
|
|
||||||
let mut res = client.get(&*url)
|
let mut res = client.get(&*url)
|
||||||
.header(Connection::close())
|
.header(Connection::close())
|
||||||
|
|||||||
@@ -71,10 +71,12 @@ use method::Method;
|
|||||||
use net::{NetworkConnector, NetworkStream};
|
use net::{NetworkConnector, NetworkStream};
|
||||||
use Error;
|
use Error;
|
||||||
|
|
||||||
|
use self::proxy::tunnel;
|
||||||
pub use self::pool::Pool;
|
pub use self::pool::Pool;
|
||||||
pub use self::request::Request;
|
pub use self::request::Request;
|
||||||
pub use self::response::Response;
|
pub use self::response::Response;
|
||||||
|
|
||||||
|
mod proxy;
|
||||||
pub mod pool;
|
pub mod pool;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
@@ -90,7 +92,7 @@ pub struct Client {
|
|||||||
redirect_policy: RedirectPolicy,
|
redirect_policy: RedirectPolicy,
|
||||||
read_timeout: Option<Duration>,
|
read_timeout: Option<Duration>,
|
||||||
write_timeout: Option<Duration>,
|
write_timeout: Option<Duration>,
|
||||||
proxy: Option<(Cow<'static, str>, Cow<'static, str>, u16)>
|
proxy: Option<(Cow<'static, str>, u16)>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Client {
|
impl fmt::Debug for Client {
|
||||||
@@ -116,6 +118,15 @@ impl Client {
|
|||||||
Client::with_connector(Pool::new(config))
|
Client::with_connector(Pool::new(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_http_proxy<H>(host: H, port: u16) -> Client
|
||||||
|
where H: Into<Cow<'static, str>> {
|
||||||
|
let host = host.into();
|
||||||
|
let proxy = tunnel((host.clone(), port));
|
||||||
|
let mut client = Client::with_connector(Pool::with_connector(Default::default(), proxy));
|
||||||
|
client.proxy = Some((host, port));
|
||||||
|
client
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new client with a specific connector.
|
/// Create a new client with a specific connector.
|
||||||
pub fn with_connector<C, S>(connector: C) -> Client
|
pub fn with_connector<C, S>(connector: C) -> Client
|
||||||
where C: NetworkConnector<Stream=S> + Send + Sync + 'static, S: NetworkStream + Send {
|
where C: NetworkConnector<Stream=S> + Send + Sync + 'static, S: NetworkStream + Send {
|
||||||
@@ -148,12 +159,6 @@ impl Client {
|
|||||||
self.write_timeout = dur;
|
self.write_timeout = dur;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a proxy for requests of this Client.
|
|
||||||
pub fn set_proxy<S, H>(&mut self, scheme: S, host: H, port: u16)
|
|
||||||
where S: Into<Cow<'static, str>>, H: Into<Cow<'static, str>> {
|
|
||||||
self.proxy = Some((scheme.into(), host.into(), port));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a Get request.
|
/// Build a Get request.
|
||||||
pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
|
pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
|
||||||
self.request(Method::Get, url)
|
self.request(Method::Get, url)
|
||||||
@@ -271,13 +276,12 @@ impl<'a> RequestBuilder<'a> {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut req = {
|
let mut req = {
|
||||||
let (scheme, host, port) = match client.proxy {
|
let (host, port) = try!(get_host_and_port(&url));
|
||||||
Some(ref proxy) => (proxy.0.as_ref(), proxy.1.as_ref(), proxy.2),
|
let mut message = try!(client.protocol.new_message(&host, port, url.scheme()));
|
||||||
None => {
|
if url.scheme() == "http" && client.proxy.is_some() {
|
||||||
let hp = try!(get_host_and_port(&url));
|
message.set_proxied(true);
|
||||||
(url.scheme(), hp.0, hp.1)
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut headers = match headers {
|
let mut headers = match headers {
|
||||||
Some(ref headers) => headers.clone(),
|
Some(ref headers) => headers.clone(),
|
||||||
None => Headers::new(),
|
None => Headers::new(),
|
||||||
@@ -286,7 +290,6 @@ impl<'a> RequestBuilder<'a> {
|
|||||||
hostname: host.to_owned(),
|
hostname: host.to_owned(),
|
||||||
port: Some(port),
|
port: Some(port),
|
||||||
});
|
});
|
||||||
let message = try!(client.protocol.new_message(&host, port, scheme));
|
|
||||||
Request::with_headers_and_message(method.clone(), url.clone(), headers, message)
|
Request::with_headers_and_message(method.clone(), url.clone(), headers, message)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -460,6 +463,7 @@ impl Default for RedirectPolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn get_host_and_port(url: &Url) -> ::Result<(&str, u16)> {
|
fn get_host_and_port(url: &Url) -> ::Result<(&str, u16)> {
|
||||||
let host = match url.host_str() {
|
let host = match url.host_str() {
|
||||||
Some(host) => host,
|
Some(host) => host,
|
||||||
@@ -479,8 +483,9 @@ mod tests {
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use header::Server;
|
use header::Server;
|
||||||
use http::h1::Http11Message;
|
use http::h1::Http11Message;
|
||||||
use mock::{MockStream};
|
use mock::{MockStream, MockSsl};
|
||||||
use super::{Client, RedirectPolicy};
|
use super::{Client, RedirectPolicy};
|
||||||
|
use super::proxy::Proxy;
|
||||||
use super::pool::Pool;
|
use super::pool::Pool;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@@ -505,24 +510,61 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_proxy() {
|
fn test_proxy() {
|
||||||
use super::pool::PooledStream;
|
use super::pool::PooledStream;
|
||||||
|
type MessageStream = PooledStream<super::proxy::Proxied<MockStream, MockStream>>;
|
||||||
mock_connector!(ProxyConnector {
|
mock_connector!(ProxyConnector {
|
||||||
b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
|
b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
|
||||||
});
|
});
|
||||||
let mut client = Client::with_connector(Pool::with_connector(Default::default(), ProxyConnector));
|
let tunnel = Proxy {
|
||||||
client.set_proxy("http", "example.proxy", 8008);
|
connector: ProxyConnector,
|
||||||
|
proxy: ("example.proxy".into(), 8008),
|
||||||
|
ssl: MockSsl,
|
||||||
|
};
|
||||||
|
let mut client = Client::with_connector(Pool::with_connector(Default::default(), tunnel));
|
||||||
|
client.proxy = Some(("example.proxy".into(), 8008));
|
||||||
let mut dump = vec![];
|
let mut dump = vec![];
|
||||||
client.get("http://127.0.0.1/foo/bar").send().unwrap().read_to_end(&mut dump).unwrap();
|
client.get("http://127.0.0.1/foo/bar").send().unwrap().read_to_end(&mut dump).unwrap();
|
||||||
|
|
||||||
{
|
let box_message = client.protocol.new_message("127.0.0.1", 80, "http").unwrap();
|
||||||
let box_message = client.protocol.new_message("example.proxy", 8008, "http").unwrap();
|
let message = box_message.downcast::<Http11Message>().unwrap();
|
||||||
let message = box_message.downcast::<Http11Message>().unwrap();
|
let stream = message.into_inner().downcast::<MessageStream>().unwrap().into_inner().into_normal().unwrap();;
|
||||||
let stream = message.into_inner().downcast::<PooledStream<MockStream>>().unwrap().into_inner();
|
|
||||||
let s = ::std::str::from_utf8(&stream.write).unwrap();
|
|
||||||
let request_line = "GET http://127.0.0.1/foo/bar HTTP/1.1\r\n";
|
|
||||||
assert_eq!(&s[..request_line.len()], request_line);
|
|
||||||
assert!(s.contains("Host: example.proxy:8008\r\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let s = ::std::str::from_utf8(&stream.write).unwrap();
|
||||||
|
let request_line = "GET http://127.0.0.1/foo/bar HTTP/1.1\r\n";
|
||||||
|
assert!(s.starts_with(request_line), "{:?} doesn't start with {:?}", s, request_line);
|
||||||
|
assert!(s.contains("Host: 127.0.0.1\r\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_proxy_tunnel() {
|
||||||
|
use super::pool::PooledStream;
|
||||||
|
type MessageStream = PooledStream<super::proxy::Proxied<MockStream, MockStream>>;
|
||||||
|
|
||||||
|
mock_connector!(ProxyConnector {
|
||||||
|
b"HTTP/1.1 200 OK\r\n\r\n",
|
||||||
|
b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
|
||||||
|
});
|
||||||
|
let tunnel = Proxy {
|
||||||
|
connector: ProxyConnector,
|
||||||
|
proxy: ("example.proxy".into(), 8008),
|
||||||
|
ssl: MockSsl,
|
||||||
|
};
|
||||||
|
let mut client = Client::with_connector(Pool::with_connector(Default::default(), tunnel));
|
||||||
|
client.proxy = Some(("example.proxy".into(), 8008));
|
||||||
|
let mut dump = vec![];
|
||||||
|
client.get("https://127.0.0.1/foo/bar").send().unwrap().read_to_end(&mut dump).unwrap();
|
||||||
|
|
||||||
|
let box_message = client.protocol.new_message("127.0.0.1", 443, "https").unwrap();
|
||||||
|
let message = box_message.downcast::<Http11Message>().unwrap();
|
||||||
|
let stream = message.into_inner().downcast::<MessageStream>().unwrap().into_inner().into_tunneled().unwrap();
|
||||||
|
|
||||||
|
let s = ::std::str::from_utf8(&stream.write).unwrap();
|
||||||
|
let connect_line = "CONNECT 127.0.0.1:443 HTTP/1.1\r\nHost: 127.0.0.1:443\r\n\r\n";
|
||||||
|
assert_eq!(&s[..connect_line.len()], connect_line);
|
||||||
|
|
||||||
|
let s = &s[connect_line.len()..];
|
||||||
|
let request_line = "GET /foo/bar HTTP/1.1\r\n";
|
||||||
|
assert_eq!(&s[..request_line.len()], request_line);
|
||||||
|
assert!(s.contains("Host: 127.0.0.1\r\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ impl<C: NetworkConnector<Stream=S>, S: NetworkStream + Send> NetworkConnector fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A Stream that will try to be returned to the Pool when dropped.
|
/// A Stream that will try to be returned to the Pool when dropped.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct PooledStream<S> {
|
pub struct PooledStream<S> {
|
||||||
inner: Option<PooledStreamInner<S>>,
|
inner: Option<PooledStreamInner<S>>,
|
||||||
is_closed: bool,
|
is_closed: bool,
|
||||||
|
|||||||
240
src/client/proxy.rs
Normal file
240
src/client/proxy.rs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -268,16 +268,15 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_proxy() {
|
fn test_proxy() {
|
||||||
let url = Url::parse("http://example.dom").unwrap();
|
let url = Url::parse("http://example.dom").unwrap();
|
||||||
let proxy_url = Url::parse("http://pro.xy").unwrap();
|
|
||||||
let mut req = Request::with_connector(
|
let mut req = Request::with_connector(
|
||||||
Get, proxy_url, &mut MockConnector
|
Get, url, &mut MockConnector
|
||||||
).unwrap();
|
).unwrap();
|
||||||
req.url = url;
|
req.message.set_proxied(true);
|
||||||
let bytes = run_request(req);
|
let bytes = run_request(req);
|
||||||
let s = from_utf8(&bytes[..]).unwrap();
|
let s = from_utf8(&bytes[..]).unwrap();
|
||||||
let request_line = "GET http://example.dom/ HTTP/1.1";
|
let request_line = "GET http://example.dom/ HTTP/1.1";
|
||||||
assert_eq!(&s[..request_line.len()], request_line);
|
assert_eq!(&s[..request_line.len()], request_line);
|
||||||
assert!(s.contains("Host: pro.xy"));
|
assert!(s.contains("Host: example.dom"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use url::Position as UrlPosition;
|
|||||||
|
|
||||||
use buffer::BufReader;
|
use buffer::BufReader;
|
||||||
use Error;
|
use Error;
|
||||||
use header::{Headers, Host, ContentLength, TransferEncoding};
|
use header::{Headers, ContentLength, TransferEncoding};
|
||||||
use header::Encoding::Chunked;
|
use header::Encoding::Chunked;
|
||||||
use method::{Method};
|
use method::{Method};
|
||||||
use net::{NetworkConnector, NetworkStream};
|
use net::{NetworkConnector, NetworkStream};
|
||||||
@@ -91,6 +91,7 @@ impl Stream {
|
|||||||
/// An implementation of the `HttpMessage` trait for HTTP/1.1.
|
/// An implementation of the `HttpMessage` trait for HTTP/1.1.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Http11Message {
|
pub struct Http11Message {
|
||||||
|
is_proxied: bool,
|
||||||
method: Option<Method>,
|
method: Option<Method>,
|
||||||
stream: Wrapper<Stream>,
|
stream: Wrapper<Stream>,
|
||||||
}
|
}
|
||||||
@@ -131,6 +132,7 @@ impl HttpMessage for Http11Message {
|
|||||||
io::ErrorKind::Other,
|
io::ErrorKind::Other,
|
||||||
"")));
|
"")));
|
||||||
let mut method = None;
|
let mut method = None;
|
||||||
|
let is_proxied = self.is_proxied;
|
||||||
self.stream.map_in_place(|stream: Stream| -> Stream {
|
self.stream.map_in_place(|stream: Stream| -> Stream {
|
||||||
let stream = match stream {
|
let stream = match stream {
|
||||||
Stream::Idle(stream) => stream,
|
Stream::Idle(stream) => stream,
|
||||||
@@ -144,17 +146,10 @@ impl HttpMessage for Http11Message {
|
|||||||
let mut stream = BufWriter::new(stream);
|
let mut stream = BufWriter::new(stream);
|
||||||
|
|
||||||
{
|
{
|
||||||
let uri = match head.headers.get::<Host>() {
|
let uri = if is_proxied {
|
||||||
Some(host)
|
head.url.as_ref()
|
||||||
if Some(&*host.hostname) == head.url.host_str()
|
} else {
|
||||||
&& host.port == head.url.port_or_known_default() => {
|
&head.url[UrlPosition::BeforePath..UrlPosition::AfterQuery]
|
||||||
&head.url[UrlPosition::BeforePath..UrlPosition::AfterQuery]
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
trace!("url and host header dont match, using absolute uri form");
|
|
||||||
head.url.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let version = version::HttpVersion::Http11;
|
let version = version::HttpVersion::Http11;
|
||||||
@@ -365,6 +360,11 @@ impl HttpMessage for Http11Message {
|
|||||||
try!(self.get_mut().close(Shutdown::Both));
|
try!(self.get_mut().close(Shutdown::Both));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_proxied(&mut self, val: bool) {
|
||||||
|
self.is_proxied = val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Http11Message {
|
impl Http11Message {
|
||||||
@@ -401,6 +401,7 @@ impl Http11Message {
|
|||||||
/// the peer.
|
/// the peer.
|
||||||
pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message {
|
pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message {
|
||||||
Http11Message {
|
Http11Message {
|
||||||
|
is_proxied: false,
|
||||||
method: None,
|
method: None,
|
||||||
stream: Wrapper::new(Stream::new(stream)),
|
stream: Wrapper::new(Stream::new(stream)),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ pub trait HttpMessage: Write + Read + Send + Any + Typeable + Debug {
|
|||||||
fn close_connection(&mut self) -> ::Result<()>;
|
fn close_connection(&mut self) -> ::Result<()>;
|
||||||
/// Returns whether the incoming message has a body.
|
/// Returns whether the incoming message has a body.
|
||||||
fn has_body(&self) -> bool;
|
fn has_body(&self) -> bool;
|
||||||
|
/// Called when the Client wishes to use a Proxy.
|
||||||
|
fn set_proxied(&mut self, val: bool) {
|
||||||
|
// default implementation so as to not be a breaking change.
|
||||||
|
warn!("default set_proxied({:?})", val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpMessage {
|
impl HttpMessage {
|
||||||
|
|||||||
12
src/mock.rs
12
src/mock.rs
@@ -12,7 +12,7 @@ use solicit::http::frame::{SettingsFrame, Frame};
|
|||||||
use solicit::http::connection::{HttpConnection, EndStream, DataChunk};
|
use solicit::http::connection::{HttpConnection, EndStream, DataChunk};
|
||||||
|
|
||||||
use header::Headers;
|
use header::Headers;
|
||||||
use net::{NetworkStream, NetworkConnector};
|
use net::{NetworkStream, NetworkConnector, SslClient};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MockStream {
|
pub struct MockStream {
|
||||||
@@ -315,3 +315,13 @@ impl NetworkConnector for MockHttp2Connector {
|
|||||||
Ok(self.streams.borrow_mut().remove(0))
|
Ok(self.streams.borrow_mut().remove(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct MockSsl;
|
||||||
|
|
||||||
|
impl<T: NetworkStream + Send + Clone> SslClient<T> for MockSsl {
|
||||||
|
type Stream = T;
|
||||||
|
fn wrap_client(&self, stream: T, _host: &str) -> ::Result<T> {
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
64
src/net.rs
64
src/net.rs
@@ -6,7 +6,7 @@ use std::net::{SocketAddr, ToSocketAddrs, TcpStream, TcpListener, Shutdown};
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
pub use self::openssl::Openssl;
|
pub use self::openssl::{Openssl, OpensslClient};
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -423,22 +423,22 @@ pub trait Ssl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An abstraction to allow any SSL implementation to be used with client-side HttpsStreams.
|
/// An abstraction to allow any SSL implementation to be used with client-side HttpsStreams.
|
||||||
pub trait SslClient {
|
pub trait SslClient<T: NetworkStream + Send + Clone = HttpStream> {
|
||||||
/// The protected stream.
|
/// The protected stream.
|
||||||
type Stream: NetworkStream + Send + Clone;
|
type Stream: NetworkStream + Send + Clone;
|
||||||
/// Wrap a client stream with SSL.
|
/// Wrap a client stream with SSL.
|
||||||
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream>;
|
fn wrap_client(&self, stream: T, host: &str) -> ::Result<Self::Stream>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An abstraction to allow any SSL implementation to be used with server-side HttpsStreams.
|
/// An abstraction to allow any SSL implementation to be used with server-side HttpsStreams.
|
||||||
pub trait SslServer {
|
pub trait SslServer<T: NetworkStream + Send + Clone = HttpStream> {
|
||||||
/// The protected stream.
|
/// The protected stream.
|
||||||
type Stream: NetworkStream + Send + Clone;
|
type Stream: NetworkStream + Send + Clone;
|
||||||
/// Wrap a server stream with SSL.
|
/// Wrap a server stream with SSL.
|
||||||
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream>;
|
fn wrap_server(&self, stream: T) -> ::Result<Self::Stream>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Ssl> SslClient for S {
|
impl<S: Ssl> SslClient<HttpStream> for S {
|
||||||
type Stream = <S as Ssl>::Stream;
|
type Stream = <S as Ssl>::Stream;
|
||||||
|
|
||||||
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream> {
|
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream> {
|
||||||
@@ -446,7 +446,7 @@ impl<S: Ssl> SslClient for S {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Ssl> SslServer for S {
|
impl<S: Ssl> SslServer<HttpStream> for S {
|
||||||
type Stream = <S as Ssl>::Stream;
|
type Stream = <S as Ssl>::Stream;
|
||||||
|
|
||||||
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream> {
|
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream> {
|
||||||
@@ -566,28 +566,35 @@ impl<S: SslServer + Clone> NetworkListener for HttpsListener<S> {
|
|||||||
|
|
||||||
/// A connector that can protect HTTP streams using SSL.
|
/// A connector that can protect HTTP streams using SSL.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct HttpsConnector<S: SslClient> {
|
pub struct HttpsConnector<S: SslClient, C: NetworkConnector = HttpConnector> {
|
||||||
ssl: S
|
ssl: S,
|
||||||
|
connector: C,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: SslClient> HttpsConnector<S> {
|
impl<S: SslClient> HttpsConnector<S, HttpConnector> {
|
||||||
/// Create a new connector using the provided SSL implementation.
|
/// Create a new connector using the provided SSL implementation.
|
||||||
pub fn new(s: S) -> HttpsConnector<S> {
|
pub fn new(s: S) -> HttpsConnector<S, HttpConnector> {
|
||||||
HttpsConnector { ssl: s }
|
HttpsConnector::with_connector(s, HttpConnector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: SslClient> NetworkConnector for HttpsConnector<S> {
|
impl<S: SslClient, C: NetworkConnector> HttpsConnector<S, C> {
|
||||||
|
/// Create a new connector using the provided SSL implementation.
|
||||||
|
pub fn with_connector(s: S, connector: C) -> HttpsConnector<S, C> {
|
||||||
|
HttpsConnector { ssl: s, connector: connector }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: SslClient, C: NetworkConnector<Stream=HttpStream>> NetworkConnector for HttpsConnector<S, C> {
|
||||||
type Stream = HttpsStream<S::Stream>;
|
type Stream = HttpsStream<S::Stream>;
|
||||||
|
|
||||||
fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result<Self::Stream> {
|
fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result<Self::Stream> {
|
||||||
let addr = &(host, port);
|
let stream = try!(self.connector.connect(host, port, "http"));
|
||||||
if scheme == "https" {
|
if scheme == "https" {
|
||||||
debug!("https scheme");
|
debug!("https scheme");
|
||||||
let stream = HttpStream(try!(TcpStream::connect(addr)));
|
|
||||||
self.ssl.wrap_client(stream, host).map(HttpsStream::Https)
|
self.ssl.wrap_client(stream, host).map(HttpsStream::Https)
|
||||||
} else {
|
} else {
|
||||||
HttpConnector.connect(host, port, scheme).map(HttpsStream::Http)
|
Ok(HttpsStream::Http(stream))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -638,6 +645,31 @@ mod openssl {
|
|||||||
pub context: Arc<SslContext>
|
pub context: Arc<SslContext>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A client-specific implementation of OpenSSL.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct OpensslClient(SslContext);
|
||||||
|
|
||||||
|
impl Default for OpensslClient {
|
||||||
|
fn default() -> OpensslClient {
|
||||||
|
OpensslClient(SslContext::new(SslMethod::Sslv23).unwrap_or_else(|e| {
|
||||||
|
// if we cannot create a SslContext, that's because of a
|
||||||
|
// serious problem. just crash.
|
||||||
|
panic!("{}", e)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T: NetworkStream + Send + Clone> super::SslClient<T> for OpensslClient {
|
||||||
|
type Stream = SslStream<T>;
|
||||||
|
|
||||||
|
fn wrap_client(&self, stream: T, host: &str) -> ::Result<Self::Stream> {
|
||||||
|
let ssl = try!(Ssl::new(&self.0));
|
||||||
|
try!(ssl.set_hostname(host));
|
||||||
|
SslStream::connect(ssl, stream).map_err(From::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Openssl {
|
impl Default for Openssl {
|
||||||
fn default() -> Openssl {
|
fn default() -> Openssl {
|
||||||
Openssl {
|
Openssl {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use std::thread;
|
|||||||
use time::now_utc;
|
use time::now_utc;
|
||||||
|
|
||||||
use header;
|
use header;
|
||||||
use http::h1::{CR, LF, LINE_ENDING, HttpWriter};
|
use http::h1::{LINE_ENDING, HttpWriter};
|
||||||
use http::h1::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
|
use http::h1::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
|
||||||
use status;
|
use status;
|
||||||
use net::{Fresh, Streaming};
|
use net::{Fresh, Streaming};
|
||||||
@@ -82,8 +82,7 @@ impl<'a, W: Any> Response<'a, W> {
|
|||||||
|
|
||||||
fn write_head(&mut self) -> io::Result<Body> {
|
fn write_head(&mut self) -> io::Result<Body> {
|
||||||
debug!("writing head: {:?} {:?}", self.version, self.status);
|
debug!("writing head: {:?} {:?}", self.version, self.status);
|
||||||
try!(write!(&mut self.body, "{} {}{}{}", self.version, self.status,
|
try!(write!(&mut self.body, "{} {}\r\n", self.version, self.status));
|
||||||
CR as char, LF as char));
|
|
||||||
|
|
||||||
if !self.headers.has::<header::Date>() {
|
if !self.headers.has::<header::Date>() {
|
||||||
self.headers.set(header::Date(header::HttpDate(now_utc())));
|
self.headers.set(header::Date(header::HttpDate(now_utc())));
|
||||||
|
|||||||
Reference in New Issue
Block a user