diff --git a/Cargo.toml b/Cargo.toml index 5de2d4f..1276b28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,14 @@ homepage = "https://github.com/ctz/hyper-rustls" repository = "https://github.com/ctz/hyper-rustls" [dependencies] -futures = "0.1.13" -hyper = "0.11" +ct-logs = "0.3" +futures = "0.1.21" +http = "0.1" +hyper = "0.12" rustls = "0.12" tokio-core = "0.1.7" tokio-io = "0.1.1" -tokio-proto = "0.1" -tokio-rustls = { version = "0.5", features = [ "tokio-proto" ] } -tokio-service = "0.1.0" +tokio-rustls = "0.6" +tokio-tcp = "0.1" webpki = "0.18.0-alpha" -webpki-roots = "0.14" -ct-logs = "0.3" +webpki-roots = "0.14" \ No newline at end of file diff --git a/examples/client.rs b/examples/client.rs index ff9522a..d1cff44 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -8,9 +8,8 @@ extern crate tokio_core; use futures::{Future, Stream}; use hyper::{client, Uri}; -use std::{env, io, fs}; -use std::io::Write; use std::str::FromStr; +use std::{env, fs, io}; fn main() { // First parameter is target URL (mandatory) @@ -32,30 +31,31 @@ fn main() { None => None, }; - let mut core = tokio_core::reactor::Core::new().unwrap(); - let handle = core.handle(); let https = match ca { Some(ref mut rd) => { - let mut http = client::HttpConnector::new(4, &handle); + let mut http = client::HttpConnector::new(4); http.enforce_http(false); let mut tls = rustls::ClientConfig::new(); tls.root_store.add_pem_file(rd).unwrap(); hyper_rustls::HttpsConnector::from((http, tls)) } - None => hyper_rustls::HttpsConnector::new(4, &handle), + None => hyper_rustls::HttpsConnector::new(4), }; - let client = client::Client::configure().connector(https).build(&handle); + let client: client::Client<_, hyper::Body> = client::Client::builder().build(https); - let work = client.get(url).and_then(|res| { - println!("Status: {}", res.status()); - println!("Headers:\n{}", res.headers()); - res.body().for_each(|chunk| { - ::std::io::stdout().write_all(&chunk).map(|_| ()).map_err( - From::from, - ) + let fut = client + .get(url) + .inspect(|res| { + println!("Status:\n{}", res.status()); + println!("Headers:\n{:#?}", res.headers()); }) - }); - if let Err(err) = core.run(work) { + .and_then(|res| res.into_body().concat2()) + .inspect(|body| { + println!("Body:\n{}", String::from_utf8_lossy(&body)); + }); + + let mut core = tokio_core::reactor::Core::new().unwrap(); + if let Err(err) = core.run(fut) { println!("FAILED: {}", err); std::process::exit(1) } diff --git a/examples/server.rs b/examples/server.rs index bb9871c..8fa322c 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -3,72 +3,76 @@ extern crate futures; extern crate hyper; extern crate rustls; -extern crate tokio_proto; +extern crate tokio_core; extern crate tokio_rustls; +extern crate tokio_tcp; -use futures::future::FutureResult; -use hyper::header::ContentLength; -use hyper::server::{Http, Service, Request, Response}; -use hyper::{Get, Post, StatusCode}; -use tokio_rustls::proto; +use futures::future; +use futures::Stream; +use hyper::rt::Future; +use hyper::service::service_fn; +use hyper::{Body, Method, Request, Response, Server, StatusCode}; use rustls::internal::pemfile; +use std::{env, fs, io, sync}; +use tokio_rustls::ServerConfigExt; static INDEX: &'static [u8] = b"Try POST /echo\n"; -#[derive(Clone, Copy)] -struct Echo; +type ResponseFuture = Box, Error = hyper::Error> + Send>; -impl Service for Echo { - type Request = Request; - type Response = Response; - type Error = hyper::Error; - type Future = FutureResult; - - fn call(&self, req: Request) -> Self::Future { - futures::future::ok(match (req.method(), req.path()) { - (&Get, "/") | (&Get, "/echo") => { - Response::new() - .with_header(ContentLength(INDEX.len() as u64)) - .with_body(INDEX) - } - (&Post, "/echo") => { - let mut res = Response::new(); - if let Some(len) = req.headers().get::() { - res.headers_mut().set(len.clone()); - } - res.with_body(req.body()) - } - _ => Response::new().with_status(StatusCode::NotFound), - }) - } +fn echo(req: Request) -> ResponseFuture { + let mut response = Response::new(Body::empty()); + match (req.method(), req.uri().path()) { + (&Method::GET, "/") => { + *response.body_mut() = Body::from(INDEX); + } + (&Method::POST, "/echo") => { + *response.body_mut() = req.into_body(); + } + _ => { + *response.status_mut() = StatusCode::NOT_FOUND; + } + }; + Box::new(future::ok(response)) } fn load_certs(filename: &str) -> Vec { - let certfile = std::fs::File::open(filename).expect("cannot open certificate file"); - let mut reader = std::io::BufReader::new(certfile); + let certfile = fs::File::open(filename).expect("cannot open certificate file"); + let mut reader = io::BufReader::new(certfile); pemfile::certs(&mut reader).unwrap() } fn load_private_key(filename: &str) -> rustls::PrivateKey { - let keyfile = std::fs::File::open(filename).expect("cannot open private key file"); - let mut reader = std::io::BufReader::new(keyfile); + let keyfile = fs::File::open(filename).expect("cannot open private key file"); + let mut reader = io::BufReader::new(keyfile); let keys = pemfile::rsa_private_keys(&mut reader).unwrap(); assert!(keys.len() == 1); keys[0].clone() } fn main() { - let port = match std::env::args().nth(1) { + let port = match env::args().nth(1) { Some(ref p) => p.to_owned(), None => "1337".to_owned(), }; let addr = format!("127.0.0.1:{}", port).parse().unwrap(); - let certs = load_certs("examples/sample.pem"); - let key = load_private_key("examples/sample.rsa"); - let mut cfg = rustls::ServerConfig::new(rustls::NoClientAuth::new()); - cfg.set_single_cert(certs, key); - let tls = proto::Server::new(Http::new(), std::sync::Arc::new(cfg)); - let tcp = tokio_proto::TcpServer::new(tls, addr); + + let tls_cfg = { + let certs = load_certs("examples/sample.pem"); + let key = load_private_key("examples/sample.rsa"); + let mut cfg = rustls::ServerConfig::new(rustls::NoClientAuth::new()); + cfg.set_single_cert(certs, key); + sync::Arc::new(cfg) + }; + + let tcp = tokio_tcp::TcpListener::bind(&addr).unwrap(); println!("Starting to serve on https://{}.", addr); - tcp.serve(|| Ok(Echo {})); + let tls = tcp.incoming().and_then(|s| tls_cfg.accept_async(s)); + let fut = Server::builder(tls).serve(|| service_fn(echo)); + + let mut core = tokio_core::reactor::Core::new().unwrap(); + if let Err(err) = core.run(fut) { + println!("FAILED: {}", err); + std::process::exit(1) + } } diff --git a/src/connector.rs b/src/connector.rs index f2539ac..c9b42cf 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -1,30 +1,29 @@ +use ct_logs; use futures::{Future, Poll}; +use hyper::client::connect::{self, Connect}; use hyper::client::HttpConnector; -use hyper::Uri; use rustls::ClientConfig; -use std::{fmt, io}; use std::sync::Arc; -use stream::MaybeHttpsStream; -use tokio_core::reactor::Handle; +use std::{fmt, io}; use tokio_rustls::ClientConfigExt; -use tokio_service::Service; -use webpki::{DNSName, DNSNameRef}; +use webpki::DNSNameRef; use webpki_roots; -use ct_logs; + +use stream::MaybeHttpsStream; /// A Connector for the `https` scheme. #[derive(Clone)] -pub struct HttpsConnector { - http: HttpConnector, +pub struct HttpsConnector { + http: T, tls_config: Arc, } -impl HttpsConnector { +impl HttpsConnector { /// Construct a new `HttpsConnector`. /// /// Takes number of DNS worker threads. - pub fn new(threads: usize, handle: &Handle) -> HttpsConnector { - let mut http = HttpConnector::new(threads, handle); + pub fn new(threads: usize) -> Self { + let mut http = HttpConnector::new(threads); http.enforce_http(false); let mut config = ClientConfig::new(); config @@ -38,14 +37,14 @@ impl HttpsConnector { } } -impl fmt::Debug for HttpsConnector { +impl fmt::Debug for HttpsConnector { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("HttpsConnector").finish() } } -impl From<(HttpConnector, ClientConfig)> for HttpsConnector { - fn from(args: (HttpConnector, ClientConfig)) -> HttpsConnector { +impl From<(T, ClientConfig)> for HttpsConnector { + fn from(args: (T, ClientConfig)) -> Self { HttpsConnector { http: args.0, tls_config: Arc::new(args.1), @@ -53,54 +52,48 @@ impl From<(HttpConnector, ClientConfig)> for HttpsConnector { } } -impl Service for HttpsConnector { - type Request = Uri; - type Response = MaybeHttpsStream; +impl Connect for HttpsConnector +where + T: Connect, + T::Transport: 'static, + T::Future: 'static, +{ + type Transport = MaybeHttpsStream; type Error = io::Error; - type Future = HttpsConnecting; + type Future = HttpsConnecting; - fn call(&self, uri: Uri) -> Self::Future { - let is_https = uri.scheme() == Some("https"); - let host: DNSName = match uri.host() { - Some(host) => match DNSNameRef::try_from_ascii_str(host) { - Ok(host) => host.into(), - Err(err) => { - return HttpsConnecting(Box::new(::futures::future::err(io::Error::new( - io::ErrorKind::InvalidInput, - format!("invalid url: {:?}", err), - )))) - } - }, - None => { - return HttpsConnecting(Box::new(::futures::future::err(io::Error::new( - io::ErrorKind::InvalidInput, - "invalid url, missing host", - )))) - } - }; - let connecting = self.http.call(uri); + fn connect(&self, dst: connect::Destination) -> Self::Future { + let is_https = dst.scheme() == "https"; - HttpsConnecting(if is_https { - let tls = self.tls_config.clone(); - Box::new( + if !is_https { + let connecting = self.http.connect(dst); + let fut = Box::new(connecting.map(|(tcp, conn)| (MaybeHttpsStream::Http(tcp), conn))); + HttpsConnecting(fut) + } else { + let connecting = self.http.connect(dst.clone()); + let cfg = self.tls_config.clone(); + let fut = Box::new( connecting - .and_then(move |tcp| { - tls.connect_async(host.as_ref(), tcp) + .and_then(move |(tcp, conn)| { + let dnsname = DNSNameRef::try_from_ascii_str(dst.host()).unwrap(); + cfg.connect_async(dnsname, tcp) + .and_then(|tls| Ok((MaybeHttpsStream::Https(tls), conn))) .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) }) - .map(|tls| MaybeHttpsStream::Https(tls)) .map_err(|e| io::Error::new(io::ErrorKind::Other, e)), - ) - } else { - Box::new(connecting.map(|tcp| MaybeHttpsStream::Http(tcp))) - }) + ); + HttpsConnecting(fut) + } } } -pub struct HttpsConnecting(Box>); +/// A Future representing work to connect to a URL, and a TLS handshake. +pub struct HttpsConnecting( + Box, connect::Connected), Error = io::Error> + Send>, +); -impl Future for HttpsConnecting { - type Item = MaybeHttpsStream; +impl Future for HttpsConnecting { + type Item = (MaybeHttpsStream, connect::Connected); type Error = io::Error; fn poll(&mut self) -> Poll { @@ -108,7 +101,7 @@ impl Future for HttpsConnecting { } } -impl fmt::Debug for HttpsConnecting { +impl fmt::Debug for HttpsConnecting { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.pad("HttpsConnecting") } diff --git a/src/lib.rs b/src/lib.rs index 30d78c6..b81afde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,30 +9,30 @@ //! extern crate hyper_rustls; //! extern crate tokio_core; //! -//! use hyper::{Client, Uri}; +//! use hyper::{Body, Client, StatusCode, Uri}; //! use tokio_core::reactor; //! //! fn main() { //! let mut core = reactor::Core::new().unwrap(); //! let url = ("https://hyper.rs").parse().unwrap(); +//! let https = hyper_rustls::HttpsConnector::new(4); //! -//! let client = hyper::Client::configure() -//! .connector(hyper_rustls::HttpsConnector::new(4, &core.handle())) -//! .build(&core.handle()); +//! let client: Client<_, hyper::Body> = Client::builder().build(https); //! //! let res = core.run(client.get(url)).unwrap(); -//! assert_eq!(res.status(), hyper::Ok); +//! assert_eq!(res.status(), StatusCode::OK); //! } //! ``` extern crate ct_logs; extern crate futures; +extern crate http; extern crate hyper; extern crate rustls; extern crate tokio_core; extern crate tokio_io; extern crate tokio_rustls; -extern crate tokio_service; +extern crate tokio_tcp; extern crate webpki; extern crate webpki_roots; diff --git a/src/stream.rs b/src/stream.rs index d0aff69..f74e6fd 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,22 +1,21 @@ // Copied from hyperium/hyper-tls#62e3376/src/stream.rs use futures::Poll; +use rustls::ClientSession; use std::fmt; use std::io::{self, Read, Write}; -use rustls::ClientSession; -use tokio_core::net::TcpStream; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_rustls::TlsStream; /// A stream that might be protected with TLS. -pub enum MaybeHttpsStream { +pub enum MaybeHttpsStream { /// A stream over plain text. - Http(TcpStream), + Http(T), /// A stream protected with TLS. - Https(TlsStream), + Https(TlsStream), } -impl fmt::Debug for MaybeHttpsStream { +impl fmt::Debug for MaybeHttpsStream { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { MaybeHttpsStream::Http(..) => f.pad("Http(..)"), @@ -25,7 +24,7 @@ impl fmt::Debug for MaybeHttpsStream { } } -impl Read for MaybeHttpsStream { +impl Read for MaybeHttpsStream { #[inline] fn read(&mut self, buf: &mut [u8]) -> io::Result { match *self { @@ -35,7 +34,7 @@ impl Read for MaybeHttpsStream { } } -impl Write for MaybeHttpsStream { +impl Write for MaybeHttpsStream { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { match *self { @@ -53,7 +52,7 @@ impl Write for MaybeHttpsStream { } } -impl AsyncRead for MaybeHttpsStream { +impl AsyncRead for MaybeHttpsStream { unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { match *self { MaybeHttpsStream::Http(ref s) => s.prepare_uninitialized_buffer(buf), @@ -62,7 +61,7 @@ impl AsyncRead for MaybeHttpsStream { } } -impl AsyncWrite for MaybeHttpsStream { +impl AsyncWrite for MaybeHttpsStream { fn shutdown(&mut self) -> Poll<(), io::Error> { match *self { MaybeHttpsStream::Http(ref mut s) => s.shutdown(), diff --git a/tests/tests.rs b/tests/tests.rs index 7711cb4..ae67202 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,48 +1,47 @@ -use std::process::{Command, Stdio}; use std::io::Write; +use std::process::{Command, Stdio}; use std::thread; use std::time; #[test] fn client() { - let rc = Command::new("target/debug/examples/client") - .arg("https://google.com") - .output() - .expect("cannot run client example"); + let rc = Command::new("target/debug/examples/client") + .arg("https://google.com") + .output() + .expect("cannot run client example"); - assert!(rc.status.success()); + assert!(rc.status.success()); } #[test] fn server() { - let mut srv = Command::new("target/debug/examples/server") - .arg("1337") - .spawn() - .expect("cannot run server example"); + let mut srv = Command::new("target/debug/examples/server") + .arg("1337") + .spawn() + .expect("cannot run server example"); - thread::sleep(time::Duration::from_secs(1)); + thread::sleep(time::Duration::from_secs(1)); - let mut cli = Command::new("openssl") - .arg("s_client") - .arg("-ign_eof") - .arg("-connect") - .arg("localhost:1337") - .stdin(Stdio::piped()) - .spawn() - .expect("cannot run openssl"); + let mut cli = Command::new("openssl") + .arg("s_client") + .arg("-ign_eof") + .arg("-connect") + .arg("localhost:1337") + .stdin(Stdio::piped()) + .spawn() + .expect("cannot run openssl"); - cli.stdin - .as_mut() - .unwrap() - .write(b"GET / HTTP/1.0\r\n\r\n").unwrap(); + cli.stdin + .as_mut() + .unwrap() + .write(b"GET / HTTP/1.0\r\n\r\n") + .unwrap(); - let rc = cli.wait() - .expect("openssl failed"); + let rc = cli.wait().expect("openssl failed"); - assert!(rc.success()); + assert!(rc.success()); - srv.kill() - .unwrap(); + srv.kill().unwrap(); } #[test] @@ -60,8 +59,7 @@ fn custom_ca_store() { .output() .expect("cannot run client example"); - srv.kill() - .unwrap(); + srv.kill().unwrap(); if !rc.status.success() { assert_eq!(String::from_utf8_lossy(&rc.stdout), "");