diff --git a/Cargo.toml b/Cargo.toml index 3b2b690c..9f3d53b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,9 @@ git = "https://github.com/servo/rust-url" [dependencies.mime] git = "https://github.com/seanmonstar/mime.rs" + +[dev-dependencies.curl] +git = "https://github.com/carllerche/curl-rust" + +[dev-dependencies.http] +git = "https://github.com/chris-morgan/rust-http" diff --git a/README.md b/README.md index 3c9987fd..3296a89d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,21 @@ An HTTP library for Rust. +## Scientific* Benchmarks + +[Client bench:](./benches/client.rs) + +``` +running 3 tests +test bench_curl ... bench: 346762 ns/iter (+/- 16469) +test bench_http ... bench: 310861 ns/iter (+/- 123168) +test bench_hyper ... bench: 284916 ns/iter (+/- 65935) + +test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured +``` + +_* No science was harmed in this benchmark._ + ## License [MIT](./LICENSE) diff --git a/benches/client.rs b/benches/client.rs new file mode 100644 index 00000000..eea156f9 --- /dev/null +++ b/benches/client.rs @@ -0,0 +1,68 @@ +extern crate curl; +extern crate http; +extern crate hyper; + +extern crate test; + +use std::io::IoResult; +use std::time::Duration; +use std::io::timer::sleep; +use std::io::net::ip::Ipv4Addr; +use std::sync::{Once, ONCE_INIT}; +use hyper::server::{Request, Response, Server}; + +static mut SERVER: Once = ONCE_INIT; + +fn listen() { + unsafe { + SERVER.doit(|| { + let server = Server::http(Ipv4Addr(127, 0, 0, 1), 1337); + let listening = server.listen(handle).unwrap(); + spawn(proc() { + sleep(Duration::seconds(20)); + listening.close().unwrap(); + }); + }) + } +} + +fn handle(_req: Request, mut res: Response) -> IoResult<()> { + try!(res.write(b"Benchmarking hyper vs others!")); + res.end() +} + + +#[bench] +fn bench_curl(b: &mut test::Bencher) { + listen(); + b.iter(|| { + curl::http::handle().get("http://127.0.0.1:1337/").exec().unwrap() + }); +} + +#[bench] +fn bench_hyper(b: &mut test::Bencher) { + listen(); + b.iter(|| { + hyper::get(hyper::Url::parse("http://127.0.0.1:1337/").unwrap()).unwrap() + .send().unwrap() + .read_to_string().unwrap() + }); +} + +#[bench] +fn bench_http(b: &mut test::Bencher) { + listen(); + b.iter(|| { + let req: http::client::RequestWriter = http::client::RequestWriter::new( + http::method::Get, + hyper::Url::parse("http://127.0.0.1:1337/").unwrap() + ).unwrap(); + // cant unwrap because Err contains RequestWriter, which does not implement Show + let mut res = match req.read_response() { + Ok(res) => res, + Err(..) => fail!("http response failed") + }; + res.read_to_string().unwrap(); + }) +} diff --git a/examples/server.rs b/examples/server.rs index b24be5b1..28483369 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -5,17 +5,21 @@ use std::io::{IoResult}; use std::io::util::copy; use std::io::net::ip::Ipv4Addr; -use hyper::method::{Get, Post}; +use hyper::{Get, Post}; use hyper::server::{Server, Handler, Request, Response}; +use hyper::header::ContentLength; struct Echo; impl Handler for Echo { fn handle(&mut self, mut req: Request, mut res: Response) -> IoResult<()> { - match &req.uri { - &hyper::uri::AbsolutePath(ref path) => match (&req.method, path.as_slice()) { + match req.uri { + hyper::uri::AbsolutePath(ref path) => match (&req.method, path.as_slice()) { (&Get, "/") | (&Get, "/echo") => { - try!(res.write_str("Try POST /echo")); + let out = b"Try POST /echo"; + + res.headers.set(ContentLength(out.len())); + try!(res.write(out)); return res.end(); }, (&Post, "/echo") => (), // fall through, fighting mutable borrows @@ -27,14 +31,12 @@ impl Handler for Echo { _ => return res.end() }; - println!("copying..."); try!(copy(&mut req, &mut res)); - println!("copied..."); res.end() } } fn main() { let server = Server::http(Ipv4Addr(127, 0, 0, 1), 1337); - server.listen(Echo); + server.listen(Echo).unwrap(); } diff --git a/src/client/mod.rs b/src/client/mod.rs index e51306a3..4a8d0e3b 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,7 +1,7 @@ -//! # Client +//! HTTP Client use url::Url; -use method::{Get, Method}; +use method::{Get, Head, Post, Delete, Method}; pub use self::request::Request; pub use self::response::Response; @@ -16,6 +16,22 @@ pub fn get(url: Url) -> HttpResult { request(Get, url) } +/// Create a HEAD client request. +pub fn head(url: Url) -> HttpResult { + request(Head, url) +} + +/// Create a POST client request. +pub fn post(url: Url) -> HttpResult { + // TODO: should this accept a Body parameter? or just let user `write` to the request? + request(Post, url) +} + +/// Create a DELETE client request. +pub fn delete(url: Url) -> HttpResult { + request(Delete, url) +} + /// Create a client request. pub fn request(method: Method, url: Url) -> HttpResult { Request::new(method, url) diff --git a/src/client/request.rs b/src/client/request.rs index 621e079d..d3d6eb12 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -1,4 +1,4 @@ -//! # Client Requests +//! Client Requests use std::io::net::tcp::TcpStream; use std::io::IoResult; diff --git a/src/client/response.rs b/src/client/response.rs index 92bf2068..5287f895 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,4 +1,4 @@ -//! # Client Responses +//! Client Responses use std::io::{Reader, IoResult}; use std::io::net::tcp::TcpStream; diff --git a/src/header.rs b/src/header.rs index 9ac32edd..712aa6a3 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,4 +1,4 @@ -//! # Headers +//! Headers container, and common header fields. //! //! hyper has the opinion that Headers should be strongly-typed, because that's //! why we're using Rust in the first place. To set or get any header, an object diff --git a/src/lib.rs b/src/lib.rs index f6870a3a..c46465c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,9 @@ extern crate url; pub use std::io::net::ip::{SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr, Port}; pub use mimewrapper::mime; pub use url::Url; -pub use client::{get}; +pub use client::{get, head, post, delete, request}; +pub use method::{Get, Head, Post, Delete}; +pub use status::{Ok, BadRequest, NotFound}; pub use server::Server; use std::fmt; @@ -30,7 +32,7 @@ struct Trace; impl fmt::Show for Trace { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let _ = backtrace::write(fmt); - Ok(()) + ::std::result::Ok(()) } } diff --git a/src/method.rs b/src/method.rs index 5dd9372a..fc1141ef 100644 --- a/src/method.rs +++ b/src/method.rs @@ -1,4 +1,4 @@ -//! # HTTP Method +//! The HTTP request method use std::fmt; use std::from_str::FromStr; diff --git a/src/mime.rs b/src/mime.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/mime.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/server/mod.rs b/src/server/mod.rs index 086f1be2..7ad0d094 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,6 +1,6 @@ -//! # Server -use std::io::net::tcp::TcpListener; -use std::io::{Acceptor, Listener, IoResult}; +//! HTTP Server +use std::io::net::tcp::{TcpListener, TcpAcceptor}; +use std::io::{Acceptor, Listener, IoResult, EndOfFile}; use std::io::net::ip::{IpAddr, Port}; pub use self::request::Request; @@ -30,53 +30,72 @@ impl Server { } /// Binds to a socket, and starts handling connections. - pub fn listen(&self, mut handler: H) { - let listener = match TcpListener::bind(self.ip.to_string().as_slice(), self.port) { - Ok(listener) => listener, - Err(err) => fail!("Listen failed: {}", err) - }; - let mut acceptor = listener.listen(); + pub fn listen(&self, mut handler: H) -> IoResult { + let listener = try!(TcpListener::bind(self.ip.to_string().as_slice(), self.port)); + let acceptor = try!(listener.listen()); + let worker = acceptor.clone(); - for conn in acceptor.incoming() { - match conn { - Ok(stream) => { - debug!("Incoming stream"); - let clone = stream.clone(); - let req = match Request::new(stream) { - Ok(r) => r, - Err(err) => { - error!("creating Request: {}", err); - continue; - } - }; - let mut res = Response::new(clone); - res.version = req.version; - match handler.handle(req, res) { - Ok(..) => debug!("Stream handled"), - Err(e) => { - error!("Error from handler: {}", e) - //TODO try to send a status code + spawn(proc() { + let mut acceptor = worker; + for conn in acceptor.incoming() { + match conn { + Ok(stream) => { + debug!("Incoming stream"); + let clone = stream.clone(); + let req = match Request::new(stream) { + Ok(r) => r, + Err(err) => { + error!("creating Request: {}", err); + continue; + } + }; + let mut res = Response::new(clone); + res.version = req.version; + match handler.handle(req, res) { + Ok(..) => debug!("Stream handled"), + Err(e) => { + error!("Error from handler: {}", e) + //TODO try to send a status code + } } + }, + Err(ref e) if e.kind == EndOfFile => break, // server closed + Err(e) => { + error!("Connection failed: {}", e); } - }, - Err(e) => { - error!("Connection failed: {}", e); } } - } + }); + + Ok(Listening { + acceptor: acceptor + }) } } +/// A listening server, which can later be closed. +pub struct Listening { + acceptor: TcpAcceptor +} + +impl Listening { + /// Stop the server from listening to it's socket address. + pub fn close(mut self) -> IoResult<()> { + debug!("closing server"); + self.acceptor.close_accept() + } +} + /// A handler that can handle incoming requests for a server. -pub trait Handler { +pub trait Handler: Send { /// Receives a `Request`/`Response` pair, and should perform some action on them. /// /// This could reading from the request, and writing to the response. fn handle(&mut self, req: Request, res: Response) -> IoResult<()>; } -impl<'a> Handler for |Request, Response|: 'a -> IoResult<()> { +impl Handler for fn(Request, Response) -> IoResult<()> { fn handle(&mut self, req: Request, res: Response) -> IoResult<()> { (*self)(req, res) } diff --git a/src/server/request.rs b/src/server/request.rs index cd277823..cbbd2e56 100644 --- a/src/server/request.rs +++ b/src/server/request.rs @@ -1,4 +1,4 @@ -//! # Server Requests +//! Server Requests //! //! These are requests that a `hyper::Server` receives, and include its method, //! target URI, headers, and message body. diff --git a/src/server/response.rs b/src/server/response.rs index bfa11471..8aa1a49c 100644 --- a/src/server/response.rs +++ b/src/server/response.rs @@ -1,4 +1,4 @@ -//! # Server Responses +//! Server Responses //! //! These are responses sent by a `hyper::Server` to clients, after //! receiving a request. diff --git a/src/uri.rs b/src/uri.rs index bb72c5f9..276337c1 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -1,4 +1,4 @@ -//! # RequestUri +//! HTTP RequestUris use url::Url; /// The Request-URI of a Request's StartLine. diff --git a/src/version.rs b/src/version.rs index 43959820..52700937 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,4 +1,4 @@ -//! # HTTP Versions +//! HTTP Versions enum //! //! Instead of relying on typo-prone Strings, use expected HTTP versions as //! the `HttpVersion` enum.