From 53bba6eb7f34e61e5c8a835281d625436532de8f Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Fri, 19 Jun 2015 10:35:03 -0700 Subject: [PATCH] feat(ssl): redesign SSL usage BREAKING CHANGE: Server::https was changed to allow any implementation of Ssl. Server in general was also changed. HttpConnector no longer uses SSL; using HttpsConnector instead. --- .travis.yml | 2 + Cargo.toml | 23 +- README.md | 2 +- examples/hello.rs | 4 +- examples/server.rs | 4 +- src/client/mod.rs | 36 +-- src/client/pool.rs | 33 +-- src/client/request.rs | 4 +- src/error.rs | 27 ++- src/header/common/set_cookie.rs | 4 +- src/http/h1.rs | 15 +- src/http/h2.rs | 9 +- src/http/message.rs | 7 +- src/lib.rs | 1 + src/mock.rs | 45 +--- src/net.rs | 385 ++++++++++++++++++++------------ src/server/mod.rs | 113 +++------- 17 files changed, 339 insertions(+), 375 deletions(-) diff --git a/.travis.yml b/.travis.yml index 45ebad3611..ecac5f4120 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,8 @@ matrix: env: FEATURES="--features nightly" - rust: beta - rust: stable + - rust: stable + env: FEATURES="--no-default-features" sudo: false diff --git a/Cargo.toml b/Cargo.toml index b7350d6b54..c3883b2144 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,22 +12,33 @@ authors = ["Sean McArthur ", keywords = ["http", "hyper", "hyperium"] [dependencies] -cookie = "0.1" httparse = "0.1" log = "0.3" -mime = "0.0.11" +mime = "0.0.12" num_cpus = "0.2" -openssl = "0.6" rustc-serialize = "0.3" time = "0.1" -unicase = "0.1" -url = "0.2" traitobject = "0.0.1" typeable = "0.1" -solicit = "0.2" +unicase = "0.1" +url = "0.2" + +[dependencies.cookie] +version = "0.1" +default-features = false + +[dependencies.openssl] +version = "0.6" +optional = true + +[dependencies.solicit] +version = "0.3" +default-features = false [dev-dependencies] env_logger = "*" [features] +default = ["ssl"] +ssl = ["openssl", "cookie/secure", "solicit/openssl"] nightly = [] diff --git a/README.md b/README.md index 7d9ea75f21..31a134b1a3 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ fn hello(_: Request, res: Response) { } fn main() { - Server::http(hello).listen("127.0.0.1:3000").unwrap(); + Server::http("127.0.0.1:3000").unwrap().handle(hello); } ``` diff --git a/examples/hello.rs b/examples/hello.rs index 1783e191db..fb2790bc6d 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -12,7 +12,7 @@ fn hello(_: Request, res: Response) { fn main() { env_logger::init().unwrap(); - let _listening = hyper::Server::http(hello) - .listen("127.0.0.1:3000").unwrap(); + let _listening = hyper::Server::http("127.0.0.1:3000").unwrap() + .handle(hello); println!("Listening on http://127.0.0.1:3000"); } diff --git a/examples/server.rs b/examples/server.rs index 945827e291..06b68b37c0 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -41,7 +41,7 @@ fn echo(mut req: Request, mut res: Response) { fn main() { env_logger::init().unwrap(); - let server = Server::http(echo); - let _guard = server.listen("127.0.0.1:1337").unwrap(); + let server = Server::http("127.0.0.1:1337").unwrap(); + let _guard = server.handle(echo); println!("Listening on http://127.0.0.1:1337"); } diff --git a/src/client/mod.rs b/src/client/mod.rs index 08227d707f..9103469477 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -65,7 +65,7 @@ use url::ParseError as UrlError; use header::{Headers, Header, HeaderFormat}; use header::{ContentLength, Location}; use method::Method; -use net::{NetworkConnector, NetworkStream, ContextVerifier}; +use net::{NetworkConnector, NetworkStream}; use {Url}; use Error; @@ -116,11 +116,6 @@ impl Client { } } - /// Set the SSL verifier callback for use with OpenSSL. - pub fn set_ssl_verifier(&mut self, verifier: ContextVerifier) { - self.protocol.set_ssl_verifier(verifier); - } - /// Set the RedirectPolicy. pub fn set_redirect_policy(&mut self, policy: RedirectPolicy) { self.redirect_policy = policy; @@ -417,8 +412,6 @@ mod tests { use header::Server; use super::{Client, RedirectPolicy}; use url::Url; - use mock::ChannelMockConnector; - use std::sync::mpsc::{self, TryRecvError}; mock_connector!(MockRedirectPolicy { "http://127.0.0.1" => "HTTP/1.1 301 Redirect\r\n\ @@ -464,31 +457,4 @@ mod tests { let res = client.get("http://127.0.0.1").send().unwrap(); assert_eq!(res.headers.get(), Some(&Server("mock2".to_owned()))); } - - /// Tests that the `Client::set_ssl_verifier` method does not drop the - /// old connector, but rather delegates the change to the connector itself. - #[test] - fn test_client_set_ssl_verifer() { - let (tx, rx) = mpsc::channel(); - let mut client = Client::with_connector(ChannelMockConnector::new(tx)); - - client.set_ssl_verifier(Box::new(|_| {})); - - // Make sure that the client called the `set_ssl_verifier` method - match rx.try_recv() { - Ok(meth) => { - assert_eq!(meth, "set_ssl_verifier"); - }, - _ => panic!("Expected a call to `set_ssl_verifier`"), - }; - // Now make sure that no other method was called, as well as that - // the connector is still alive (i.e. wasn't dropped by the client). - match rx.try_recv() { - Err(TryRecvError::Empty) => {}, - Err(TryRecvError::Disconnected) => { - panic!("Expected the connector to still be alive."); - }, - Ok(_) => panic!("Did not expect any more method calls."), - }; - } } diff --git a/src/client/pool.rs b/src/client/pool.rs index 9736a6a5d2..0962469566 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -5,7 +5,7 @@ use std::io::{self, Read, Write}; use std::net::{SocketAddr, Shutdown}; use std::sync::{Arc, Mutex}; -use net::{NetworkConnector, NetworkStream, HttpConnector, ContextVerifier}; +use net::{NetworkConnector, NetworkStream, DefaultConnector}; /// The `NetworkConnector` that behaves as a connection pool used by hyper's `Client`. pub struct Pool { @@ -58,11 +58,11 @@ impl<'a> From<&'a str> for Scheme { } } -impl Pool { - /// Creates a `Pool` with an `HttpConnector`. +impl Pool { + /// Creates a `Pool` with a `DefaultConnector`. #[inline] - pub fn new(config: Config) -> Pool { - Pool::with_connector(config, HttpConnector(None)) + pub fn new(config: Config) -> Pool { + Pool::with_connector(config, DefaultConnector::default()) } } @@ -119,10 +119,6 @@ impl, S: NetworkStream + Send> NetworkConnector fo pool: self.inner.clone() }) } - #[inline] - fn set_ssl_verifier(&mut self, verifier: ContextVerifier) { - self.connector.set_ssl_verifier(verifier); - } } /// A Stream that will try to be returned to the Pool when dropped. @@ -181,9 +177,8 @@ impl Drop for PooledStream { #[cfg(test)] mod tests { use std::net::Shutdown; - use mock::{MockConnector, ChannelMockConnector}; + use mock::{MockConnector}; use net::{NetworkConnector, NetworkStream}; - use std::sync::mpsc; use super::{Pool, key}; @@ -220,20 +215,4 @@ mod tests { let locked = pool.inner.lock().unwrap(); assert_eq!(locked.conns.len(), 0); } - - /// Tests that the `Pool::set_ssl_verifier` method sets the SSL verifier of - /// the underlying `Connector` instance that it uses. - #[test] - fn test_set_ssl_verifier_delegates_to_connector() { - let (tx, rx) = mpsc::channel(); - let mut pool = Pool::with_connector( - Default::default(), ChannelMockConnector::new(tx)); - - pool.set_ssl_verifier(Box::new(|_| { })); - - match rx.try_recv() { - Ok(meth) => assert_eq!(meth, "set_ssl_verifier"), - _ => panic!("Expected a call to `set_ssl_verifier`"), - }; - } } diff --git a/src/client/request.rs b/src/client/request.rs index c6259bc3b4..8ae7fc65c8 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -7,7 +7,7 @@ use url::Url; use method::{self, Method}; use header::Headers; use header::Host; -use net::{NetworkStream, NetworkConnector, HttpConnector, Fresh, Streaming}; +use net::{NetworkStream, NetworkConnector, DefaultConnector, Fresh, Streaming}; use version; use client::{Response, get_host_and_port}; @@ -66,7 +66,7 @@ impl Request { /// Create a new client request. pub fn new(method: method::Method, url: Url) -> ::Result> { - let mut conn = HttpConnector(None); + let mut conn = DefaultConnector::default(); Request::with_connector(method, url, &mut conn) } diff --git a/src/error.rs b/src/error.rs index 762f29e66a..2f8a73becc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,10 +5,12 @@ use std::io::Error as IoError; use std::str::Utf8Error; use httparse; -use openssl::ssl::error::SslError; use url; use solicit::http::HttpError as Http2Error; +#[cfg(feature = "openssl")] +use openssl::ssl::error::SslError; + use self::Error::{ Method, Uri, @@ -43,8 +45,8 @@ pub enum Error { Status, /// An `io::Error` that occurred while trying to read or write to a network stream. Io(IoError), - /// An error from the `openssl` library. - Ssl(SslError), + /// An error from a SSL library. + Ssl(Box), /// An HTTP/2-specific error, coming from the `solicit` library. Http2(Http2Error), /// Parsing a field as string failed @@ -89,7 +91,7 @@ impl StdError for Error { fn cause(&self) -> Option<&StdError> { match *self { Io(ref error) => Some(error), - Ssl(ref error) => Some(error), + Ssl(ref error) => Some(&**error), Uri(ref error) => Some(error), Http2(ref error) => Some(error), _ => None, @@ -109,11 +111,12 @@ impl From for Error { } } +#[cfg(feature = "openssl")] impl From for Error { fn from(err: SslError) -> Error { match err { SslError::StreamError(err) => Io(err), - err => Ssl(err), + err => Ssl(Box::new(err)), } } } @@ -149,7 +152,6 @@ mod tests { use std::error::Error as StdError; use std::io; use httparse; - use openssl::ssl::error::SslError; use solicit::http::HttpError as Http2Error; use url; use super::Error; @@ -192,12 +194,8 @@ mod tests { from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => Io(..)); from_and_cause!(url::ParseError::EmptyHost => Uri(..)); - from_and_cause!(SslError::SslSessionClosed => Ssl(..)); from_and_cause!(Http2Error::UnknownStreamId => Http2(..)); - from!(SslError::StreamError(io::Error::new(io::ErrorKind::Other, "ssl negotiation")) => Io(..)); - - from!(httparse::Error::HeaderName => Header); from!(httparse::Error::HeaderName => Header); from!(httparse::Error::HeaderValue => Header); @@ -207,4 +205,13 @@ mod tests { from!(httparse::Error::TooManyHeaders => TooLarge); from!(httparse::Error::Version => Version); } + + #[cfg(feature = "openssl")] + #[test] + fn test_from_ssl() { + use openssl::ssl::error::SslError; + + from!(SslError::StreamError(io::Error::new(io::ErrorKind::Other, "ssl negotiation")) => Io(..)); + from_and_cause!(SslError::SslSessionClosed => Ssl(..)); + } } diff --git a/src/header/common/set_cookie.rs b/src/header/common/set_cookie.rs index a092319f1c..62f708072b 100644 --- a/src/header/common/set_cookie.rs +++ b/src/header/common/set_cookie.rs @@ -168,13 +168,13 @@ fn test_fmt() { fn cookie_jar() { let jar = CookieJar::new(b"secret"); let cookie = Cookie::new("foo".to_owned(), "bar".to_owned()); - jar.encrypted().add(cookie); + jar.add(cookie); let cookies = SetCookie::from_cookie_jar(&jar); let mut new_jar = CookieJar::new(b"secret"); cookies.apply_to_cookie_jar(&mut new_jar); - assert_eq!(jar.encrypted().find("foo"), new_jar.encrypted().find("foo")); + assert_eq!(jar.find("foo"), new_jar.find("foo")); assert_eq!(jar.iter().collect::>(), new_jar.iter().collect::>()); } diff --git a/src/http/h1.rs b/src/http/h1.rs index 070b220bb5..c36323a153 100644 --- a/src/http/h1.rs +++ b/src/http/h1.rs @@ -12,7 +12,7 @@ use Error; use header::{Headers, ContentLength, TransferEncoding}; use header::Encoding::Chunked; use method::{Method}; -use net::{NetworkConnector, NetworkStream, ContextVerifier}; +use net::{NetworkConnector, NetworkStream}; use status::StatusCode; use version::HttpVersion; use version::HttpVersion::{Http10, Http11}; @@ -264,11 +264,6 @@ impl Protocol for Http11Protocol { Ok(Box::new(Http11Message::with_stream(stream))) } - - #[inline] - fn set_ssl_verifier(&mut self, verifier: ContextVerifier) { - self.connector.set_ssl_verifier(verifier); - } } impl Http11Protocol { @@ -292,10 +287,6 @@ impl + Send + Sync, S: NetworkStream + Send> Netwo -> ::Result> { Ok(try!(self.0.connect(host, port, scheme)).into()) } - #[inline] - fn set_ssl_verifier(&mut self, verifier: ContextVerifier) { - self.0.set_ssl_verifier(verifier); - } } struct Connector(Box> + Send + Sync>); @@ -307,10 +298,6 @@ impl NetworkConnector for Connector { -> ::Result> { Ok(try!(self.0.connect(host, port, scheme)).into()) } - #[inline] - fn set_ssl_verifier(&mut self, verifier: ContextVerifier) { - self.0.set_ssl_verifier(verifier); - } } diff --git a/src/http/h2.rs b/src/http/h2.rs index 0e7fd89e4c..cb3a5fe8e5 100644 --- a/src/http/h2.rs +++ b/src/http/h2.rs @@ -12,7 +12,7 @@ use http::{ ResponseHead, RawStatus, }; -use net::{NetworkStream, NetworkConnector, ContextVerifier}; +use net::{NetworkStream, NetworkConnector}; use net::{HttpConnector, HttpStream}; use url::Url; use header::Headers; @@ -133,11 +133,6 @@ impl Protocol for Http2Protocol where C: NetworkConnector Ok(Box::new(Http2Message::with_client(client))) } - - #[inline] - fn set_ssl_verifier(&mut self, verifier: ContextVerifier) { - self.connector.set_ssl_verifier(verifier) - } } /// Represents an HTTP/2 request, described by a `RequestHead` and the body of the request. @@ -387,7 +382,7 @@ impl HttpMessage for Http2Message where S: CloneableStream { /// (which produces an `HttpStream` for the underlying transport layer). #[inline] pub fn new_protocol() -> Http2Protocol { - Http2Protocol::with_connector(HttpConnector(None)) + Http2Protocol::with_connector(HttpConnector) } #[cfg(test)] diff --git a/src/http/message.rs b/src/http/message.rs index 541037c218..2e09fd24b7 100644 --- a/src/http/message.rs +++ b/src/http/message.rs @@ -16,15 +16,12 @@ use url::Url; use method; use version; use traitobject; -use net::ContextVerifier; /// The trait provides an API for creating new `HttpMessage`s depending on the underlying HTTP /// protocol. pub trait Protocol { /// Creates a fresh `HttpMessage` bound to the given host, based on the given protocol scheme. fn new_message(&self, host: &str, port: u16, scheme: &str) -> ::Result>; - /// Sets the SSL verifier that should be used when establishing TLS-protected connections. - fn set_ssl_verifier(&mut self, verifier: ContextVerifier); } /// Describes a request. @@ -63,7 +60,9 @@ pub trait HttpMessage: Write + Read + Send + Any + Typeable + Debug { /// After this, the `HttpMessage` instance can be used as an `io::Read` in order to read out /// the response body. fn get_incoming(&mut self) -> ::Result; - + /// Set the read timeout duration for this message. + #[cfg(feature = "timeouts")] + fn set_read_timeout(&self, dur: Option) -> ::Result<()>; /// Closes the underlying HTTP connection. fn close_connection(&mut self) -> ::Result<()>; } diff --git a/src/lib.rs b/src/lib.rs index c3d8e5148d..14f564b54f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,6 +131,7 @@ extern crate rustc_serialize as serialize; extern crate time; extern crate url; +#[cfg(feature = "openssl")] extern crate openssl; extern crate cookie; extern crate unicase; diff --git a/src/mock.rs b/src/mock.rs index 15d132fb0d..bb4716c673 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -3,7 +3,6 @@ use std::ascii::AsciiExt; use std::io::{self, Read, Write, Cursor}; use std::cell::RefCell; use std::net::SocketAddr; -use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use solicit::http::HttpScheme; @@ -12,7 +11,7 @@ use solicit::http::frame::{SettingsFrame, Frame}; use solicit::http::connection::{HttpConnection, EndStream, DataChunk}; use header::Headers; -use net::{NetworkStream, NetworkConnector, ContextVerifier}; +use net::{NetworkStream, NetworkConnector}; pub struct MockStream { pub read: Cursor>, @@ -133,39 +132,6 @@ impl NetworkConnector for MockConnector { fn connect(&self, _host: &str, _port: u16, _scheme: &str) -> ::Result { Ok(MockStream::new()) } - - fn set_ssl_verifier(&mut self, _verifier: ContextVerifier) { - // pass - } -} - -/// A mock implementation of the `NetworkConnector` trait that keeps track of all calls to its -/// methods by sending corresponding messages onto a channel. -/// -/// Otherwise, it behaves the same as `MockConnector`. -pub struct ChannelMockConnector { - calls: Mutex>, -} - -impl ChannelMockConnector { - pub fn new(calls: Sender) -> ChannelMockConnector { - ChannelMockConnector { calls: Mutex::new(calls) } - } -} - -impl NetworkConnector for ChannelMockConnector { - type Stream = MockStream; - #[inline] - fn connect(&self, _host: &str, _port: u16, _scheme: &str) - -> ::Result { - self.calls.lock().unwrap().send("connect".into()).unwrap(); - Ok(MockStream::new()) - } - - #[inline] - fn set_ssl_verifier(&mut self, _verifier: ContextVerifier) { - self.calls.lock().unwrap().send("set_ssl_verifier".into()).unwrap(); - } } /// new connectors must be created if you wish to intercept requests. @@ -196,10 +162,6 @@ macro_rules! mock_connector ( None => panic!("{:?} doesn't know url {}", stringify!($name), key) } } - - fn set_ssl_verifier(&mut self, _verifier: ::net::ContextVerifier) { - // pass - } } ) @@ -296,9 +258,4 @@ impl NetworkConnector for MockHttp2Connector { -> ::Result { Ok(self.streams.borrow_mut().remove(0)) } - - #[inline] - fn set_ssl_verifier(&mut self, _verifier: ContextVerifier) { - // pass - } } diff --git a/src/net.rs b/src/net.rs index 513d80e11b..1ed7486ba6 100644 --- a/src/net.rs +++ b/src/net.rs @@ -4,16 +4,12 @@ use std::fmt; use std::io::{self, ErrorKind, Read, Write}; use std::net::{SocketAddr, ToSocketAddrs, TcpStream, TcpListener, Shutdown}; use std::mem; -use std::path::Path; -use std::sync::Arc; -use openssl::ssl::{Ssl, SslStream, SslContext, SSL_VERIFY_NONE}; -use openssl::ssl::SslMethod::Sslv23; -use openssl::ssl::error::StreamError as SslIoError; -use openssl::x509::X509FileType; +#[cfg(feature = "openssl")] +pub use self::openssl::Openssl; use typeable::Typeable; -use {traitobject}; +use traitobject; /// The write-status indicating headers have not been written. pub enum Fresh {} @@ -70,9 +66,6 @@ pub trait NetworkConnector { type Stream: Into>; /// Connect to a remote address. fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result; - /// Sets the given `ContextVerifier` to be used when verifying the SSL context - /// on the establishment of a new connection. - fn set_ssl_verifier(&mut self, verifier: ContextVerifier); } impl From for Box { @@ -143,43 +136,22 @@ impl NetworkStream + Send { } /// A `NetworkListener` for `HttpStream`s. -pub enum HttpListener { - /// Http variant. - Http(TcpListener), - /// Https variant. The two paths point to the certificate and key PEM files, in that order. - Https(TcpListener, Arc) -} +pub struct HttpListener(TcpListener); impl Clone for HttpListener { + #[inline] fn clone(&self) -> HttpListener { - match *self { - HttpListener::Http(ref tcp) => HttpListener::Http(tcp.try_clone().unwrap()), - HttpListener::Https(ref tcp, ref ssl) => HttpListener::Https(tcp.try_clone().unwrap(), ssl.clone()), - } + HttpListener(self.0.try_clone().unwrap()) } } impl HttpListener { /// Start listening to an address over HTTP. - pub fn http(addr: To) -> ::Result { - Ok(HttpListener::Http(try!(TcpListener::bind(addr)))) + pub fn new(addr: To) -> ::Result { + Ok(HttpListener(try!(TcpListener::bind(addr)))) } - /// Start listening to an address over HTTPS. - pub fn https(addr: To, cert: &Path, key: &Path) -> ::Result { - let mut ssl_context = try!(SslContext::new(Sslv23)); - try!(ssl_context.set_cipher_list("DEFAULT")); - try!(ssl_context.set_certificate_file(cert, X509FileType::PEM)); - try!(ssl_context.set_private_key_file(key, X509FileType::PEM)); - ssl_context.set_verify(SSL_VERIFY_NONE, None); - HttpListener::https_with_context(addr, ssl_context) - } - - /// Start listening to an address of HTTPS using the given SslContext - pub fn https_with_context(addr: To, ssl_context: SslContext) -> ::Result { - Ok(HttpListener::Https(try!(TcpListener::bind(addr)), Arc::new(ssl_context))) - } } impl NetworkListener for HttpListener { @@ -187,51 +159,42 @@ impl NetworkListener for HttpListener { #[inline] fn accept(&mut self) -> ::Result { - match *self { - HttpListener::Http(ref mut tcp) => Ok(HttpStream::Http(CloneTcpStream(try!(tcp.accept()).0))), - HttpListener::Https(ref mut tcp, ref ssl_context) => { - let stream = CloneTcpStream(try!(tcp.accept()).0); - match SslStream::new_server(&**ssl_context, stream) { - Ok(ssl_stream) => Ok(HttpStream::Https(ssl_stream)), - Err(SslIoError(e)) => { - Err(io::Error::new(io::ErrorKind::ConnectionAborted, e).into()) - }, - Err(e) => Err(e.into()) - } - } - } + Ok(HttpStream(try!(self.0.accept()).0)) } #[inline] fn local_addr(&mut self) -> io::Result { - match *self { - HttpListener::Http(ref mut tcp) => tcp.local_addr(), - HttpListener::Https(ref mut tcp, _) => tcp.local_addr(), - } + self.0.local_addr() } } -#[doc(hidden)] -pub struct CloneTcpStream(TcpStream); +/// A wrapper around a TcpStream. +pub struct HttpStream(pub TcpStream); -impl Clone for CloneTcpStream{ +impl Clone for HttpStream { #[inline] - fn clone(&self) -> CloneTcpStream { - CloneTcpStream(self.0.try_clone().unwrap()) + fn clone(&self) -> HttpStream { + HttpStream(self.0.try_clone().unwrap()) + } +} + +impl fmt::Debug for HttpStream { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("HttpStream(_)") } } -impl Read for CloneTcpStream { +impl Read for HttpStream { #[inline] fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } } -impl Write for CloneTcpStream { +impl Write for HttpStream { #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.write(buf) + fn write(&mut self, msg: &[u8]) -> io::Result { + self.0.write(msg) } #[inline] fn flush(&mut self) -> io::Result<()> { @@ -239,123 +202,278 @@ impl Write for CloneTcpStream { } } -/// A wrapper around a TcpStream. -#[derive(Clone)] -pub enum HttpStream { - /// A stream over the HTTP protocol. - Http(CloneTcpStream), - /// A stream over the HTTP protocol, protected by SSL. - Https(SslStream), +impl NetworkStream for HttpStream { + #[inline] + fn peer_addr(&mut self) -> io::Result { + self.0.peer_addr() + } + + #[inline] + fn close(&mut self, how: Shutdown) -> io::Result<()> { + match self.0.shutdown(how) { + Ok(_) => Ok(()), + // see /~https://github.com/hyperium/hyper/issues/508 + Err(ref e) if e.kind() == ErrorKind::NotConnected => Ok(()), + err => err + } + } } -impl fmt::Debug for HttpStream { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match *self { - HttpStream::Http(_) => write!(fmt, "Http HttpStream"), - HttpStream::Https(_) => write!(fmt, "Https HttpStream"), +/// A connector that will produce HttpStreams. +#[derive(Debug, Clone, Default)] +pub struct HttpConnector; + +impl NetworkConnector for HttpConnector { + type Stream = HttpStream; + + fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result { + let addr = &(host, port); + Ok(try!(match scheme { + "http" => { + debug!("http scheme"); + Ok(HttpStream(try!(TcpStream::connect(addr)))) + }, + _ => { + Err(io::Error::new(io::ErrorKind::InvalidInput, + "Invalid scheme for Http")) + } + })) } - } } -impl Read for HttpStream { + +/// An abstraction to allow any SSL implementation to be used with HttpsStreams. +pub trait Ssl { + /// The protected stream. + type Stream: NetworkStream + Send + Clone; + /// Wrap a client stream with SSL. + fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result; + /// Wrap a server stream with SSL. + fn wrap_server(&self, stream: HttpStream) -> ::Result; +} + +/// A stream over the HTTP protocol, possibly protected by SSL. +#[derive(Debug, Clone)] +pub enum HttpsStream { + /// A plain text stream. + Http(HttpStream), + /// A stream protected by SSL. + Https(S) +} + +impl Read for HttpsStream { #[inline] fn read(&mut self, buf: &mut [u8]) -> io::Result { match *self { - HttpStream::Http(ref mut inner) => inner.read(buf), - HttpStream::Https(ref mut inner) => inner.read(buf) + HttpsStream::Http(ref mut s) => s.read(buf), + HttpsStream::Https(ref mut s) => s.read(buf) } } } -impl Write for HttpStream { +impl Write for HttpsStream { #[inline] fn write(&mut self, msg: &[u8]) -> io::Result { match *self { - HttpStream::Http(ref mut inner) => inner.write(msg), - HttpStream::Https(ref mut inner) => inner.write(msg) + HttpsStream::Http(ref mut s) => s.write(msg), + HttpsStream::Https(ref mut s) => s.write(msg) } } + #[inline] fn flush(&mut self) -> io::Result<()> { match *self { - HttpStream::Http(ref mut inner) => inner.flush(), - HttpStream::Https(ref mut inner) => inner.flush(), + HttpsStream::Http(ref mut s) => s.flush(), + HttpsStream::Https(ref mut s) => s.flush() } } } -impl NetworkStream for HttpStream { +impl NetworkStream for HttpsStream { #[inline] fn peer_addr(&mut self) -> io::Result { match *self { - HttpStream::Http(ref mut inner) => inner.0.peer_addr(), - HttpStream::Https(ref mut inner) => inner.get_mut().0.peer_addr() + HttpsStream::Http(ref mut s) => s.peer_addr(), + HttpsStream::Https(ref mut s) => s.peer_addr() } } #[inline] fn close(&mut self, how: Shutdown) -> io::Result<()> { - #[inline] - fn shutdown(tcp: &mut TcpStream, how: Shutdown) -> io::Result<()> { - match tcp.shutdown(how) { - Ok(_) => Ok(()), - // see /~https://github.com/hyperium/hyper/issues/508 - Err(ref e) if e.kind() == ErrorKind::NotConnected => Ok(()), - err => err - } - } - match *self { - HttpStream::Http(ref mut inner) => shutdown(&mut inner.0, how), - HttpStream::Https(ref mut inner) => shutdown(&mut inner.get_mut().0, how) + HttpsStream::Http(ref mut s) => s.close(how), + HttpsStream::Https(ref mut s) => s.close(how) } + } +} + +/// A Http Listener over SSL. +#[derive(Clone)] +pub struct HttpsListener { + listener: HttpListener, + ssl: S, +} + +impl HttpsListener { + /// Start listening to an address over HTTPS. + pub fn new(addr: To, ssl: S) -> ::Result> { + HttpListener::new(addr).map(|l| HttpsListener { + listener: l, + ssl: ssl + }) } + } -/// A connector that will produce HttpStreams. -pub struct HttpConnector(pub Option); +impl NetworkListener for HttpsListener { + type Stream = S::Stream; -/// A method that can set verification methods on an SSL context -pub type ContextVerifier = Box () + Send + Sync>; + #[inline] + fn accept(&mut self) -> ::Result { + self.listener.accept().and_then(|s| self.ssl.wrap_server(s)) + } -impl NetworkConnector for HttpConnector { - type Stream = HttpStream; + #[inline] + fn local_addr(&mut self) -> io::Result { + self.listener.local_addr() + } +} - fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result { +/// A connector that can protect HTTP streams using SSL. +#[derive(Debug, Default)] +pub struct HttpsConnector { + ssl: S +} + +impl HttpsConnector { + /// Create a new connector using the provided SSL implementation. + pub fn new(s: S) -> HttpsConnector { + HttpsConnector { ssl: s } + } +} + +impl NetworkConnector for HttpsConnector { + type Stream = HttpsStream; + + fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result { let addr = &(host, port); - Ok(try!(match scheme { - "http" => { - debug!("http scheme"); - Ok(HttpStream::Http(CloneTcpStream(try!(TcpStream::connect(addr))))) - }, - "https" => { - debug!("https scheme"); - let stream = CloneTcpStream(try!(TcpStream::connect(addr))); - let mut context = try!(SslContext::new(Sslv23)); - if let Some(ref verifier) = self.0 { - verifier(&mut context); - } - let ssl = try!(Ssl::new(&context)); - try!(ssl.set_hostname(host)); - let stream = try!(SslStream::new(&context, stream)); - Ok(HttpStream::Https(stream)) - }, - _ => { - Err(io::Error::new(io::ErrorKind::InvalidInput, - "Invalid scheme for Http")) + if scheme == "https" { + debug!("https scheme"); + let stream = HttpStream(try!(TcpStream::connect(addr))); + self.ssl.wrap_client(stream, host).map(HttpsStream::Https) + } else { + HttpConnector.connect(host, port, scheme).map(HttpsStream::Http) + } + } +} + + +#[cfg(not(feature = "openssl"))] +#[doc(hidden)] +pub type DefaultConnector = HttpConnector; + +#[cfg(feature = "openssl")] +#[doc(hidden)] +pub type DefaultConnector = HttpsConnector; + +#[cfg(feature = "openssl")] +mod openssl { + use std::io; + use std::net::{SocketAddr, Shutdown}; + use std::path::Path; + use std::sync::Arc; + use openssl::ssl::{Ssl, SslContext, SslStream, SslMethod, SSL_VERIFY_NONE}; + use openssl::ssl::error::StreamError as SslIoError; + use openssl::ssl::error::SslError; + use openssl::x509::X509FileType; + use super::{NetworkStream, HttpStream}; + + + /// An implementation of `Ssl` for OpenSSL. + /// + /// # Example + /// + /// ```no_run + /// use hyper::Server; + /// use hyper::net::Openssl; + /// + /// let ssl = Openssl::with_cert_and_key("/home/foo/cert", "/home/foo/key").unwrap(); + /// Server::https("0.0.0.0:443", ssl).unwrap(); + /// ``` + /// + /// For complete control, create a `SslContext` with the options you desire + /// and then create `Openssl { context: ctx } + #[derive(Debug, Clone)] + pub struct Openssl { + /// The `SslContext` from openssl crate. + pub context: Arc + } + + impl Default for Openssl { + fn default() -> Openssl { + Openssl { + context: Arc::new(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) + })) } - })) + } } - fn set_ssl_verifier(&mut self, verifier: ContextVerifier) { - self.0 = Some(verifier); + + impl Openssl { + /// Ease creating an `Openssl` with a certificate and key. + pub fn with_cert_and_key(cert: C, key: K) -> Result + where C: AsRef, K: AsRef { + let mut ctx = try!(SslContext::new(SslMethod::Sslv23)); + try!(ctx.set_cipher_list("DEFAULT")); + try!(ctx.set_certificate_file(cert.as_ref(), X509FileType::PEM)); + try!(ctx.set_private_key_file(key.as_ref(), X509FileType::PEM)); + ctx.set_verify(SSL_VERIFY_NONE, None); + Ok(Openssl { context: Arc::new(ctx) }) + } + } + + impl super::Ssl for Openssl { + type Stream = SslStream; + + fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result { + //if let Some(ref verifier) = self.verifier { + // verifier(&mut context); + //} + let ssl = try!(Ssl::new(&self.context)); + try!(ssl.set_hostname(host)); + SslStream::new(&self.context, stream).map_err(From::from) + } + + fn wrap_server(&self, stream: HttpStream) -> ::Result { + match SslStream::new_server(&self.context, stream) { + Ok(ssl_stream) => Ok(ssl_stream), + Err(SslIoError(e)) => { + Err(io::Error::new(io::ErrorKind::ConnectionAborted, e).into()) + }, + Err(e) => Err(e.into()) + } + } + } + + impl NetworkStream for SslStream { + #[inline] + fn peer_addr(&mut self) -> io::Result { + self.get_mut().peer_addr() + } + + fn close(&mut self, how: Shutdown) -> io::Result<()> { + self.get_mut().close(how) + } } } #[cfg(test)] mod tests { use mock::MockStream; - use super::{NetworkStream, HttpConnector, NetworkConnector}; + use super::{NetworkStream}; #[test] fn test_downcast_box_stream() { @@ -376,13 +494,4 @@ mod tests { assert_eq!(mock, Box::new(MockStream::new())); } - - #[test] - fn test_http_connector_set_ssl_verifier() { - let mut connector = HttpConnector(None); - - connector.set_ssl_verifier(Box::new(|_| {})); - - assert!(connector.0.is_some()); - } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 0f9aee53ed..57675a871f 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -20,7 +20,7 @@ //! // handle things here //! } //! -//! Server::http(hello).listen("0.0.0.0:0").unwrap(); +//! Server::http("0.0.0.0:0").unwrap().handle(hello).unwrap(); //! ``` //! //! As with any trait, you can also define a struct and implement `Handler` @@ -43,9 +43,9 @@ //! //! //! let (tx, rx) = channel(); -//! Server::http(SenderHandler { +//! Server::http("0.0.0.0:0").unwrap().handle(SenderHandler { //! sender: Mutex::new(tx) -//! }).listen("0.0.0.0:0").unwrap(); +//! }).unwrap(); //! ``` //! //! Since the `Server` will be listening on multiple threads, the `Handler` @@ -56,9 +56,9 @@ //! use hyper::server::{Server, Request, Response}; //! //! let counter = AtomicUsize::new(0); -//! Server::http(move |req: Request, res: Response| { +//! Server::http("0.0.0.0:0").unwrap().handle(move |req: Request, res: Response| { //! counter.fetch_add(1, Ordering::Relaxed); -//! }).listen("0.0.0.0:0").unwrap(); +//! }).unwrap(); //! ``` //! //! # The `Request` and `Response` pair @@ -76,14 +76,14 @@ //! use hyper::server::{Server, Request, Response}; //! use hyper::status::StatusCode; //! -//! Server::http(|mut req: Request, mut res: Response| { +//! Server::http("0.0.0.0:0").unwrap().handle(|mut req: Request, mut res: Response| { //! match req.method { //! hyper::Post => { //! io::copy(&mut req, &mut res.start().unwrap()).unwrap(); //! }, //! _ => *res.status_mut() = StatusCode::MethodNotAllowed //! } -//! }).listen("0.0.0.0:0").unwrap(); +//! }).unwrap(); //! ``` //! //! ## An aside: Write Status @@ -107,30 +107,12 @@ //! out by calling `start` on the `Request`. This will return a new //! `Request` object, that no longer has `headers_mut()`, but does //! implement `Write`. -//! -//! ```no_run -//! use hyper::server::{Server, Request, Response}; -//! use hyper::status::StatusCode; -//! use hyper::uri::RequestUri; -//! -//! let server = Server::http(|req: Request, mut res: Response| { -//! *res.status_mut() = match (req.method, req.uri) { -//! (hyper::Get, RequestUri::AbsolutePath(ref path)) if path == "/" => { -//! StatusCode::Ok -//! }, -//! (hyper::Get, _) => StatusCode::NotFound, -//! _ => StatusCode::MethodNotAllowed -//! }; -//! }).listen("0.0.0.0:8080").unwrap(); use std::fmt; use std::io::{ErrorKind, BufWriter, Write}; -use std::marker::PhantomData; use std::net::{SocketAddr, ToSocketAddrs}; -use std::path::Path; use std::thread::{self, JoinHandle}; use num_cpus; -use openssl::ssl::SslContext; pub use self::request::Request; pub use self::response::Response; @@ -142,7 +124,7 @@ use buffer::BufReader; use header::{Headers, Expect, Connection}; use http; use method::Method; -use net::{NetworkListener, NetworkStream, HttpListener}; +use net::{NetworkListener, NetworkStream, HttpListener, HttpsListener, Ssl}; use status::StatusCode; use uri::RequestUri; use version::HttpVersion::Http11; @@ -154,21 +136,13 @@ pub mod response; mod listener; -#[derive(Debug)] -enum SslConfig<'a> { - CertAndKey(&'a Path, &'a Path), - Context(SslContext), -} - /// A server can listen on a TCP socket. /// /// Once listening, it will create a `Request`/`Response` pair for each /// incoming connection, and hand them to the provided handler. #[derive(Debug)] -pub struct Server<'a, H: Handler, L = HttpListener> { - handler: H, - ssl: Option>, - _marker: PhantomData +pub struct Server { + listener: L, } macro_rules! try_option( @@ -180,64 +154,41 @@ macro_rules! try_option( }} ); -impl<'a, H: Handler, L: NetworkListener> Server<'a, H, L> { +impl Server { /// Creates a new server with the provided handler. - pub fn new(handler: H) -> Server<'a, H, L> { + #[inline] + pub fn new(listener: L) -> Server { Server { - handler: handler, - ssl: None, - _marker: PhantomData + listener: listener } } } -impl<'a, H: Handler + 'static> Server<'a, H, HttpListener> { +impl Server { /// Creates a new server that will handle `HttpStream`s. - pub fn http(handler: H) -> Server<'a, H, HttpListener> { - Server::new(handler) - } - /// Creates a new server that will handler `HttpStreams`s using a TLS connection. - pub fn https(handler: H, cert: &'a Path, key: &'a Path) -> Server<'a, H, HttpListener> { - Server { - handler: handler, - ssl: Some(SslConfig::CertAndKey(cert, key)), - _marker: PhantomData - } - } - /// Creates a new server that will handler `HttpStreams`s using a TLS connection defined by an SslContext. - pub fn https_with_context(handler: H, ssl_context: SslContext) -> Server<'a, H, HttpListener> { - Server { - handler: handler, - ssl: Some(SslConfig::Context(ssl_context)), - _marker: PhantomData - } + pub fn http(addr: To) -> ::Result> { + HttpListener::new(addr).map(Server::new) } } -impl<'a, H: Handler + 'static> Server<'a, H, HttpListener> { - /// Binds to a socket, and starts handling connections using a task pool. - pub fn listen_threads(self, addr: T, threads: usize) -> ::Result { - let listener = try!(match self.ssl { - Some(SslConfig::CertAndKey(cert, key)) => HttpListener::https(addr, cert, key), - Some(SslConfig::Context(ssl_context)) => HttpListener::https_with_context(addr, ssl_context), - None => HttpListener::http(addr) - }); - with_listener(self.handler, listener, threads) +impl Server> { + /// Creates a new server that will handle `HttpStream`s over SSL. + /// + /// You can use any SSL implementation, as long as implements `hyper::net::Ssl`. + pub fn https(addr: A, ssl: S) -> ::Result>> { + HttpsListener::new(addr, ssl).map(Server::new) } +} +impl Server { /// Binds to a socket and starts handling connections. - pub fn listen(self, addr: T) -> ::Result { - self.listen_threads(addr, num_cpus::get() * 5 / 4) + pub fn handle(self, handler: H) -> ::Result { + with_listener(handler, self.listener, num_cpus::get() * 5 / 4) } -} -impl< -'a, -H: Handler + 'static, -L: NetworkListener + Send + 'static, -S: NetworkStream + Clone + Send> Server<'a, H, L> { - /// Creates a new server that will handle `HttpStream`s. - pub fn with_listener(self, listener: L, threads: usize) -> ::Result { - with_listener(self.handler, listener, threads) + /// Binds to a socket and starts handling connections with the provided + /// number of threads. + pub fn handle_threads(self, handler: H, threads: usize) -> ::Result { + with_listener(handler, self.listener, threads) } } @@ -247,7 +198,7 @@ L: NetworkListener + Send + 'static { let socket = try!(listener.local_addr()); debug!("threads = {:?}", threads); - let pool = ListenerPool::new(listener.clone()); + let pool = ListenerPool::new(listener); let work = move |mut stream| Worker(&handler).handle_connection(&mut stream); let guard = thread::spawn(move || pool.accept(work, threads));