#![deny(missing_docs)]
extern crate bytes;
#[macro_use]
extern crate futures;
extern crate hyper;
#[cfg(test)]
extern crate hyper_tls;
extern crate native_tls;
extern crate tokio_core;
extern crate tokio_io;
extern crate tokio_tls;
mod tunnel;
mod stream;
use std::any::Any;
use std::fmt;
use std::io;
use futures::Future;
use hyper::Uri;
use hyper::client::Service;
use hyper::header::{Header, Headers, ProxyAuthorization, Scheme};
use native_tls::TlsConnector;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_tls::TlsConnectorExt;
use stream::ProxyStream;
pub enum Intercept {
All,
Http,
Https,
Custom(Box<Fn(&Uri) -> bool>),
}
impl fmt::Debug for Intercept {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Intercept::All => write!(f, "All"),
Intercept::Http => write!(f, "Http"),
Intercept::Https => write!(f, "Https"),
Intercept::Custom(_) => write!(f, "Custom"),
}
}
}
impl Intercept {
fn matches(&self, uri: &Uri) -> bool {
match (self, uri.scheme()) {
(&Intercept::All, _)
| (&Intercept::Http, Some("http"))
| (&Intercept::Https, Some("https")) => true,
(&Intercept::Custom(ref f), _) => f(uri),
_ => false,
}
}
}
pub struct Proxy<C> {
intercept: Intercept,
headers: Headers,
uri: Uri,
connector: C,
tls: Option<TlsConnector>,
}
impl<C> Proxy<C> {
pub fn new(connector: C, intercept: Intercept, uri: Uri) -> Result<Self, io::Error> {
let tls = TlsConnector::builder()
.and_then(|b| b.build())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(Proxy {
intercept: intercept,
uri: uri,
headers: Headers::new(),
connector: connector,
tls: Some(tls),
})
}
pub fn unsecured(connector: C, intercept: Intercept, uri: Uri) -> Self {
Proxy {
intercept: intercept,
uri: uri,
headers: Headers::new(),
connector: connector,
tls: None,
}
}
pub fn set_authorization<S: Scheme + Any>(&mut self, scheme: S) {
self.headers.set(ProxyAuthorization(scheme));
}
pub fn set_header<H: Header>(&mut self, header: H) {
self.headers.set(header);
}
}
impl<C> Service for Proxy<C>
where
C: Service<Request = Uri, Error = io::Error> + 'static,
C::Future: 'static,
<C::Future as Future>::Item: AsyncRead + AsyncWrite + 'static,
{
type Request = Uri;
type Response = ProxyStream<C::Response>;
type Error = io::Error;
type Future = Box<Future<Item = ProxyStream<C::Response>, Error = Self::Error>>;
fn call(&self, uri: Uri) -> Self::Future {
if self.intercept.matches(&uri) {
let host = uri.host().unwrap().to_owned();
let port = uri.port().unwrap_or(443);
let tunnel = tunnel::Tunnel::new(&host, port, &self.headers);
let proxy_stream = self.connector
.call(self.uri.clone())
.and_then(move |io| tunnel.with_stream(io));
match self.tls.as_ref() {
Some(tls) => {
let tls = tls.clone();
Box::new(proxy_stream.and_then(move |tunneled| {
tls.connect_async(&host, tunneled)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
.map(|s| ProxyStream::Secured(s))
}))
}
None => Box::new(proxy_stream.map(|s| ProxyStream::Regular(s))),
}
} else {
Box::new(self.connector.call(uri).map(|s| ProxyStream::Regular(s)))
}
}
}