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.
This commit is contained in:
Sean McArthur
2015-06-19 10:35:03 -07:00
parent e689f20376
commit 53bba6eb7f
17 changed files with 355 additions and 391 deletions

View File

@@ -6,6 +6,8 @@ matrix:
env: FEATURES="--features nightly"
- rust: beta
- rust: stable
- rust: stable
env: FEATURES="--no-default-features"
sudo: false

View File

@@ -12,22 +12,33 @@ authors = ["Sean McArthur <sean.monstar@gmail.com>",
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 = []

View File

@@ -41,7 +41,7 @@ fn hello(_: Request, res: Response<Fresh>) {
}
fn main() {
Server::http(hello).listen("127.0.0.1:3000").unwrap();
Server::http("127.0.0.1:3000").unwrap().handle(hello);
}
```

View File

@@ -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");
}

View File

@@ -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");
}

View File

@@ -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."),
};
}
}

View File

@@ -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<C: NetworkConnector> {
@@ -58,11 +58,11 @@ impl<'a> From<&'a str> for Scheme {
}
}
impl Pool<HttpConnector> {
/// Creates a `Pool` with an `HttpConnector`.
impl Pool<DefaultConnector> {
/// Creates a `Pool` with a `DefaultConnector`.
#[inline]
pub fn new(config: Config) -> Pool<HttpConnector> {
Pool::with_connector(config, HttpConnector(None))
pub fn new(config: Config) -> Pool<DefaultConnector> {
Pool::with_connector(config, DefaultConnector::default())
}
}
@@ -119,10 +119,6 @@ impl<C: NetworkConnector<Stream=S>, 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<S> Drop for PooledStream<S> {
#[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`"),
};
}
}

View File

@@ -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<Fresh> {
/// Create a new client request.
pub fn new(method: method::Method, url: Url) -> ::Result<Request<Fresh>> {
let mut conn = HttpConnector(None);
let mut conn = DefaultConnector::default();
Request::with_connector(method, url, &mut conn)
}

View File

@@ -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<StdError + Send + Sync>),
/// 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<url::ParseError> for Error {
}
}
#[cfg(feature = "openssl")]
impl From<SslError> 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(..));
}
}

View File

@@ -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::<Vec<Cookie>>(), new_jar.iter().collect::<Vec<Cookie>>());
}

View File

@@ -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<C: NetworkConnector<Stream=S> + Send + Sync, S: NetworkStream + Send> Netwo
-> ::Result<Box<NetworkStream + Send>> {
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<NetworkConnector<Stream=Box<NetworkStream + Send>> + Send + Sync>);
@@ -307,10 +298,6 @@ impl NetworkConnector for Connector {
-> ::Result<Box<NetworkStream + Send>> {
Ok(try!(self.0.connect(host, port, scheme)).into())
}
#[inline]
fn set_ssl_verifier(&mut self, verifier: ContextVerifier) {
self.0.set_ssl_verifier(verifier);
}
}

View File

@@ -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<C, S> Protocol for Http2Protocol<C, S> where C: NetworkConnector<Stream=S>
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<S> HttpMessage for Http2Message<S> where S: CloneableStream {
/// (which produces an `HttpStream` for the underlying transport layer).
#[inline]
pub fn new_protocol() -> Http2Protocol<HttpConnector, HttpStream> {
Http2Protocol::with_connector(HttpConnector(None))
Http2Protocol::with_connector(HttpConnector)
}
#[cfg(test)]

View File

@@ -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<Box<HttpMessage>>;
/// 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<ResponseHead>;
/// Set the read timeout duration for this message.
#[cfg(feature = "timeouts")]
fn set_read_timeout(&self, dur: Option<Duration>) -> ::Result<()>;
/// Closes the underlying HTTP connection.
fn close_connection(&mut self) -> ::Result<()>;
}

View File

@@ -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;

View File

@@ -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<Vec<u8>>,
@@ -133,39 +132,6 @@ impl NetworkConnector for MockConnector {
fn connect(&self, _host: &str, _port: u16, _scheme: &str) -> ::Result<MockStream> {
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<Sender<String>>,
}
impl ChannelMockConnector {
pub fn new(calls: Sender<String>) -> ChannelMockConnector {
ChannelMockConnector { calls: Mutex::new(calls) }
}
}
impl NetworkConnector for ChannelMockConnector {
type Stream = MockStream;
#[inline]
fn connect(&self, _host: &str, _port: u16, _scheme: &str)
-> ::Result<MockStream> {
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<CloneableMockStream> {
Ok(self.streams.borrow_mut().remove(0))
}
#[inline]
fn set_ssl_verifier(&mut self, _verifier: ContextVerifier) {
// pass
}
}

View File

@@ -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<Box<NetworkStream + Send>>;
/// Connect to a remote address.
fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result<Self::Stream>;
/// 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<T: NetworkStream + Send> From<T> for Box<NetworkStream + Send> {
@@ -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<SslContext>)
}
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<To: ToSocketAddrs>(addr: To) -> ::Result<HttpListener> {
Ok(HttpListener::Http(try!(TcpListener::bind(addr))))
pub fn new<To: ToSocketAddrs>(addr: To) -> ::Result<HttpListener> {
Ok(HttpListener(try!(TcpListener::bind(addr))))
}
/// Start listening to an address over HTTPS.
pub fn https<To: ToSocketAddrs>(addr: To, cert: &Path, key: &Path) -> ::Result<HttpListener> {
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<To: ToSocketAddrs>(addr: To, ssl_context: SslContext) -> ::Result<HttpListener> {
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<HttpStream> {
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<SocketAddr> {
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 Read for CloneTcpStream {
impl fmt::Debug for HttpStream {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("HttpStream(_)")
}
}
impl Read for HttpStream {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
impl Write for CloneTcpStream {
impl Write for HttpStream {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
self.0.write(msg)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
@@ -239,85 +202,26 @@ 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<CloneTcpStream>),
}
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"),
}
}
}
impl Read for HttpStream {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
HttpStream::Http(ref mut inner) => inner.read(buf),
HttpStream::Https(ref mut inner) => inner.read(buf)
}
}
}
impl Write for HttpStream {
#[inline]
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
match *self {
HttpStream::Http(ref mut inner) => inner.write(msg),
HttpStream::Https(ref mut inner) => inner.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(),
}
}
}
impl NetworkStream for HttpStream {
#[inline]
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
match *self {
HttpStream::Http(ref mut inner) => inner.0.peer_addr(),
HttpStream::Https(ref mut inner) => inner.get_mut().0.peer_addr()
}
self.0.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) {
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
}
}
match *self {
HttpStream::Http(ref mut inner) => shutdown(&mut inner.0, how),
HttpStream::Https(ref mut inner) => shutdown(&mut inner.get_mut().0, how)
}
}
}
/// A connector that will produce HttpStreams.
pub struct HttpConnector(pub Option<ContextVerifier>);
/// A method that can set verification methods on an SSL context
pub type ContextVerifier = Box<Fn(&mut SslContext) -> () + Send + Sync>;
#[derive(Debug, Clone, Default)]
pub struct HttpConnector;
impl NetworkConnector for HttpConnector {
type Stream = HttpStream;
@@ -327,19 +231,7 @@ impl NetworkConnector for HttpConnector {
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))
Ok(HttpStream(try!(TcpStream::connect(addr))))
},
_ => {
Err(io::Error::new(io::ErrorKind::InvalidInput,
@@ -347,15 +239,241 @@ impl NetworkConnector for HttpConnector {
}
}))
}
fn set_ssl_verifier(&mut self, verifier: ContextVerifier) {
self.0 = Some(verifier);
}
/// 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<Self::Stream>;
/// Wrap a server stream with SSL.
fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream>;
}
/// A stream over the HTTP protocol, possibly protected by SSL.
#[derive(Debug, Clone)]
pub enum HttpsStream<S: NetworkStream> {
/// A plain text stream.
Http(HttpStream),
/// A stream protected by SSL.
Https(S)
}
impl<S: NetworkStream> Read for HttpsStream<S> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
HttpsStream::Http(ref mut s) => s.read(buf),
HttpsStream::Https(ref mut s) => s.read(buf)
}
}
}
impl<S: NetworkStream> Write for HttpsStream<S> {
#[inline]
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
match *self {
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 {
HttpsStream::Http(ref mut s) => s.flush(),
HttpsStream::Https(ref mut s) => s.flush()
}
}
}
impl<S: NetworkStream> NetworkStream for HttpsStream<S> {
#[inline]
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
match *self {
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<()> {
match *self {
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<S: Ssl> {
listener: HttpListener,
ssl: S,
}
impl<S: Ssl> HttpsListener<S> {
/// Start listening to an address over HTTPS.
pub fn new<To: ToSocketAddrs>(addr: To, ssl: S) -> ::Result<HttpsListener<S>> {
HttpListener::new(addr).map(|l| HttpsListener {
listener: l,
ssl: ssl
})
}
}
impl<S: Ssl + Clone> NetworkListener for HttpsListener<S> {
type Stream = S::Stream;
#[inline]
fn accept(&mut self) -> ::Result<S::Stream> {
self.listener.accept().and_then(|s| self.ssl.wrap_server(s))
}
#[inline]
fn local_addr(&mut self) -> io::Result<SocketAddr> {
self.listener.local_addr()
}
}
/// A connector that can protect HTTP streams using SSL.
#[derive(Debug, Default)]
pub struct HttpsConnector<S: Ssl> {
ssl: S
}
impl<S: Ssl> HttpsConnector<S> {
/// Create a new connector using the provided SSL implementation.
pub fn new(s: S) -> HttpsConnector<S> {
HttpsConnector { ssl: s }
}
}
impl<S: Ssl> NetworkConnector for HttpsConnector<S> {
type Stream = HttpsStream<S::Stream>;
fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result<Self::Stream> {
let addr = &(host, port);
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<self::openssl::Openssl>;
#[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<SslContext>
}
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)
}))
}
}
}
impl Openssl {
/// Ease creating an `Openssl` with a certificate and key.
pub fn with_cert_and_key<C, K>(cert: C, key: K) -> Result<Openssl, SslError>
where C: AsRef<Path>, K: AsRef<Path> {
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<HttpStream>;
fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream> {
//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<Self::Stream> {
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<S: NetworkStream> NetworkStream for SslStream<S> {
#[inline]
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
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());
}
}

View File

@@ -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<Fresh>`. This will return a new
//! `Request<Streaming>` 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<SslConfig<'a>>,
_marker: PhantomData<L>
pub struct Server<L = HttpListener> {
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<L: NetworkListener> Server<L> {
/// Creates a new server with the provided handler.
pub fn new(handler: H) -> Server<'a, H, L> {
#[inline]
pub fn new(listener: L) -> Server<L> {
Server {
handler: handler,
ssl: None,
_marker: PhantomData
listener: listener
}
}
}
impl<'a, H: Handler + 'static> Server<'a, H, HttpListener> {
impl Server<HttpListener> {
/// 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<To: ToSocketAddrs>(addr: To) -> ::Result<Server<HttpListener>> {
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<T: ToSocketAddrs>(self, addr: T, threads: usize) -> ::Result<Listening> {
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<S: Ssl + Clone + Send> Server<HttpsListener<S>> {
/// 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<A: ToSocketAddrs>(addr: A, ssl: S) -> ::Result<Server<HttpsListener<S>>> {
HttpsListener::new(addr, ssl).map(Server::new)
}
}
impl<L: NetworkListener + Send + 'static> Server<L> {
/// Binds to a socket and starts handling connections.
pub fn listen<T: ToSocketAddrs>(self, addr: T) -> ::Result<Listening> {
self.listen_threads(addr, num_cpus::get() * 5 / 4)
pub fn handle<H: Handler + 'static>(self, handler: H) -> ::Result<Listening> {
with_listener(handler, self.listener, num_cpus::get() * 5 / 4)
}
}
impl<
'a,
H: Handler + 'static,
L: NetworkListener<Stream=S> + 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<Listening> {
with_listener(self.handler, listener, threads)
/// Binds to a socket and starts handling connections with the provided
/// number of threads.
pub fn handle_threads<H: Handler + 'static>(self, handler: H, threads: usize) -> ::Result<Listening> {
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));