Skip to content

Commit

Permalink
Refactor server startup APIs and Https support
Browse files Browse the repository at this point in the history
The previous support for SSL was not great - you were locked into
OpenSSL and had no ability to configure it the way you wanted. Hyper is
going to be cutting a release soon that completely removes the built-in
OpenSSL and Secure Transport functionalty in favor of external crates.

While we're at it, also allow a `NetworkListener` to be manually
created, and refactor the configuration a bit to avoid combinatoric
blowup in methods.

Closes #483
Closes #447
Closes #424
  • Loading branch information
sfackler committed Jan 9, 2017
1 parent 3802f26 commit 363479e
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 129 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Expand Up @@ -6,8 +6,8 @@ sudo: false
script:
- cargo build
- cargo test
- cargo build --features=ssl
- cargo test --features=ssl
- cargo build --features native-tls-example
- cargo test --features native-tls-example
env:
global:
secure: DUE2yG7/ASacYARIs7nysUAUhK86AqwE/PdQ3j+D5dqzxs3IOMSOcc7PA1r2w3FkXd52rENCYqKz2iReniJn4fG5S3Q+NbcfaYkhS/6P1y0sQB8yIIVeBRf/Bo2bR2P5TRh+piYWDmqYLUvsQ0+DpQ78MRA6HSxz7gCKpkZS4Y4=
Expand Down
9 changes: 3 additions & 6 deletions Cargo.toml
Expand Up @@ -15,13 +15,12 @@ authors = [
]

[lib]

name = "iron"
path = "src/lib.rs"

[features]
default = []
ssl = ["hyper/ssl"]
native-tls-example = ["hyper-native-tls"]

[dependencies]
typemap = "0.3"
Expand All @@ -33,10 +32,8 @@ log = "0.3"
conduit-mime-types = "0.7"
lazy_static = "0.2"
num_cpus = "1.0"

[dependencies.hyper]
version = "0.9"
default-features = false
hyper = { version = "0.9", default-features = false }
hyper-native-tls = { version = "0.1", optional = true }

[dev-dependencies]
time = "0.1"
Expand Down
22 changes: 9 additions & 13 deletions examples/hello_custom_config.rs
Expand Up @@ -4,22 +4,18 @@ use std::time::Duration;

use iron::prelude::*;
use iron::status;
use iron::Protocol;
use iron::Timeouts;

fn main() {
Iron::new(|_: &mut Request| {
let mut iron = Iron::new(|_: &mut Request| {
Ok(Response::with((status::Ok, "Hello world!")))
})
.listen_with(
"localhost:3000",
8, // thread num
Protocol::Http,
Some(Timeouts{
keep_alive: Some(Duration::from_secs(10)),
read: Some(Duration::from_secs(10)),
write: Some(Duration::from_secs(10))
})
).unwrap();
});
iron.threads = 8;
iron.timeouts = Timeouts {
keep_alive: Some(Duration::from_secs(10)),
read: Some(Duration::from_secs(10)),
write: Some(Duration::from_secs(10))
};
iron.http("localhost:3000").unwrap();
}

30 changes: 15 additions & 15 deletions examples/https.rs
@@ -1,41 +1,41 @@
// This requires running with:
//
// ```bash
// cargo run --example https --features ssl
// cargo run --example https --features native-tls-example
// ```
//
// Generate a key and certificate like so:
// Generate an identity like so:
//
// ```bash
// openssl genrsa -out localhost.key 4096
// openssl req -key localhost.key -x509 -new -days 3650 -out localhost.crt
// openssl req -x509 -newkey rsa:4096 -nodes -keyout localhost.key -out localhost.crt -days 3650
// openssl pkcs12 -export -out identity.p12 -inkey localhost.key -in localhost.crt --password mypass
//
// ```

extern crate iron;
#[cfg(feature = "native-tls-example")]
extern crate hyper_native_tls;

#[cfg(feature = "ssl")]
#[cfg(feature = "native-tls-example")]
fn main() {
// Avoid unused errors due to conditional compilation ('ssl' feature is not default)
use iron::status;
// Avoid unused errors due to conditional compilation ('native-tls-example' feature is not default)
use hyper_native_tls::NativeTlsServer;
use iron::{Iron, Request, Response};
use std::path::{Path};
use std::result::{Result};
use iron::status;
use std::result::Result;

// openssl genrsa -out localhost.key 4096
let key = Path::new("localhost.key").to_path_buf();
// openssl req -key localhost.key -x509 -new -days 3650 -out localhost.crt
let cert = Path::new("localhost.crt").to_path_buf();
let ssl = NativeTlsServer::new("identity.p12", "mypass").unwrap();

match Iron::new(|_: &mut Request| {
Ok(Response::with((status::Ok, "Hello world!")))
}).https("127.0.0.1:3000", cert, key) {
}).https("127.0.0.1:3000", ssl) {
Result::Ok(listening) => println!("{:?}", listening),
Result::Err(err) => panic!("{:?}", err),
}
// curl -vvvv https://127.0.0.1:3000/ -k
}

#[cfg(not(feature = "ssl"))]
#[cfg(not(feature = "native-tls-example"))]
fn main() {
// We need to do this to make sure `cargo test` passes.
}
165 changes: 72 additions & 93 deletions src/iron.rs
Expand Up @@ -3,12 +3,10 @@

use std::net::{ToSocketAddrs, SocketAddr};
use std::time::Duration;
#[cfg(feature = "ssl")]
use std::path::PathBuf;

pub use hyper::server::Listening;
use hyper::server::Server;
use hyper::net::Fresh;
use hyper::net::{Fresh, SslServer, HttpListener, HttpsListener, NetworkListener};

use request::HttpRequest;
use response::HttpResponse;
Expand All @@ -27,11 +25,13 @@ pub struct Iron<H> {
/// requests.
pub handler: H,

/// Once listening, the local address that this server is bound to.
addr: Option<SocketAddr>,
/// Server timeouts.
pub timeouts: Timeouts,

/// Once listening, the protocol used to serve content.
protocol: Option<Protocol>
/// The number of request handling threads.
///
/// Defaults to `8 * num_cpus`.
pub threads: usize,
}

/// A settings struct containing a set of timeouts which can be applied to a server.
Expand Down Expand Up @@ -65,34 +65,49 @@ impl Default for Timeouts {
}
}

/// Protocol used to serve content. Future versions of Iron may add new protocols
/// to this enum. Thus you should not exhaustively match on its variants.
#[derive(Clone)]
pub enum Protocol {
/// Plaintext HTTP/1
enum _Protocol {
Http,
/// HTTP/1 over SSL/TLS
#[cfg(feature = "ssl")]
Https {
/// Path to SSL certificate file
certificate: PathBuf,
/// Path to SSL private key file
key: PathBuf
}
Https,
}

/// Protocol used to serve content.
#[derive(Clone)]
pub struct Protocol(_Protocol);

impl Protocol {
/// Return the name used for this protocol in a URI's scheme part.
pub fn name(&self) -> &'static str {
match *self {
Protocol::Http => "http",
#[cfg(feature = "ssl")]
Protocol::Https { .. } => "https"
/// Plaintext HTTP/1
pub fn http() -> Protocol {
Protocol(_Protocol::Http)
}

/// HTTP/1 over SSL/TLS
pub fn https() -> Protocol {
Protocol(_Protocol::Https)
}

/// Returns the name used for this protocol in a URI's scheme part.
pub fn name(&self) -> &str {
match self.0 {
_Protocol::Http => "http",
_Protocol::Https => "https",
}
}
}

impl<H: Handler> Iron<H> {
/// Instantiate a new instance of `Iron`.
///
/// This will create a new `Iron`, the base unit of the server, using the
/// passed in `Handler`.
pub fn new(handler: H) -> Iron<H> {
Iron {
handler: handler,
timeouts: Timeouts::default(),
threads: 8 * ::num_cpus::get(),
}
}

/// Kick off the server process using the HTTP protocol.
///
/// Call this once to begin listening for requests on the server.
Expand All @@ -101,15 +116,10 @@ impl<H: Handler> Iron<H> {
///
/// The thread returns a guard that will automatically join with the parent
/// once it is dropped, blocking until this happens.
///
/// Defaults to a threadpool of size `8 * num_cpus`.
///
/// ## Panics
///
/// Panics if the provided address does not parse. To avoid this
/// call `to_socket_addrs` yourself and pass a parsed `SocketAddr`.
pub fn http<A: ToSocketAddrs>(self, addr: A) -> HttpResult<Listening> {
self.listen_with(addr, 8 * ::num_cpus::get(), Protocol::Http, None)
pub fn http<A>(self, addr: A) -> HttpResult<Listening>
where A: ToSocketAddrs
{
HttpListener::new(addr).and_then(|l| self.listen(l, Protocol::http()))
}

/// Kick off the server process using the HTTPS protocol.
Expand All @@ -120,78 +130,47 @@ impl<H: Handler> Iron<H> {
///
/// The thread returns a guard that will automatically join with the parent
/// once it is dropped, blocking until this happens.
///
/// Defaults to a threadpool of size `8 * num_cpus`.
///
/// ## Panics
///
/// Panics if the provided address does not parse. To avoid this
/// call `to_socket_addrs` yourself and pass a parsed `SocketAddr`.
#[cfg(feature = "ssl")]
pub fn https<A: ToSocketAddrs>(self, addr: A, certificate: PathBuf, key: PathBuf)
-> HttpResult<Listening> {
self.listen_with(addr, 8 * ::num_cpus::get(),
Protocol::Https { certificate: certificate, key: key }, None)
pub fn https<A, S>(self, addr: A, ssl: S) -> HttpResult<Listening>
where A: ToSocketAddrs,
S: 'static + SslServer + Send + Clone
{
HttpsListener::new(addr, ssl).and_then(|l| self.listen(l, Protocol::http()))
}

/// Kick off the server process with X threads.
///
/// ## Panics
/// Kick off a server process on an arbitrary `Listener`.
///
/// Panics if the provided address does not parse. To avoid this
/// call `to_socket_addrs` yourself and pass a parsed `SocketAddr`.
pub fn listen_with<A: ToSocketAddrs>(mut self, addr: A, threads: usize,
protocol: Protocol,
timeouts: Option<Timeouts>) -> HttpResult<Listening> {
let sock_addr = addr.to_socket_addrs()
.ok().and_then(|mut addrs| addrs.next()).expect("Could not parse socket address.");

self.addr = Some(sock_addr);
self.protocol = Some(protocol.clone());

match protocol {
Protocol::Http => {
let mut server = try!(Server::http(sock_addr));
let timeouts = timeouts.unwrap_or_default();
server.keep_alive(timeouts.keep_alive);
server.set_read_timeout(timeouts.read);
server.set_write_timeout(timeouts.write);
server.handle_threads(self, threads)
},

#[cfg(feature = "ssl")]
Protocol::Https { ref certificate, ref key } => {
use hyper::net::Openssl;

let ssl = try!(Openssl::with_cert_and_key(certificate, key));
let mut server = try!(Server::https(sock_addr, ssl));
let timeouts = timeouts.unwrap_or_default();
server.keep_alive(timeouts.keep_alive);
server.set_read_timeout(timeouts.read);
server.set_write_timeout(timeouts.write);
server.handle_threads(self, threads)
}
}
/// Most use cases may call `http` and `https` methods instead of this.
pub fn listen<L>(self, mut listener: L, protocol: Protocol) -> HttpResult<Listening>
where L: 'static + NetworkListener + Send
{
let handler = RawHandler {
handler: self.handler,
addr: try!(listener.local_addr()),
protocol: protocol,
};

let mut server = Server::new(listener);
server.keep_alive(self.timeouts.keep_alive);
server.set_read_timeout(self.timeouts.read);
server.set_write_timeout(self.timeouts.write);
server.handle_threads(handler, self.threads)
}
}

/// Instantiate a new instance of `Iron`.
///
/// This will create a new `Iron`, the base unit of the server, using the
/// passed in `Handler`.
pub fn new(handler: H) -> Iron<H> {
Iron { handler: handler, addr: None, protocol: None }
}
struct RawHandler<H> {
handler: H,
addr: SocketAddr,
protocol: Protocol,
}

impl<H: Handler> ::hyper::server::Handler for Iron<H> {
impl<H: Handler> ::hyper::server::Handler for RawHandler<H> {
fn handle(&self, http_req: HttpRequest, mut http_res: HttpResponse<Fresh>) {
// Set some defaults in case request handler panics.
// This should not be necessary anymore once stdlib's catch_panic becomes stable.
*http_res.status_mut() = status::InternalServerError;

// Create `Request` wrapper.
match Request::from_http(http_req, self.addr.clone().unwrap(),
self.protocol.as_ref().unwrap()) {
match Request::from_http(http_req, self.addr.clone(), &self.protocol) {
Ok(mut req) => {
// Dispatch the request, write the response back to http_res
self.handler.handle(&mut req).unwrap_or_else(|e| {
Expand Down

0 comments on commit 363479e

Please sign in to comment.