Skip to content

Commit

Permalink
perf(http): changes http parsing to use httparse crate
Browse files Browse the repository at this point in the history
httparse is a http1 stateless push parser. This not only speeds up
parsing right now with sync io, but will also be useful for when we get
async io, since it's push based instead of pull.

BREAKING CHANGE: Several public functions and types in the `http` module
  have been removed. They have been replaced with 2 methods that handle
  all of the http1 parsing.
  • Loading branch information
seanmonstar committed Mar 13, 2015
1 parent 003b655 commit b87bb20
Show file tree
Hide file tree
Showing 15 changed files with 354 additions and 727 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ keywords = ["http", "hyper", "hyperium"]

[dependencies]
cookie = "*"
httparse = "*"
log = ">= 0.2.0"
mime = "*"
openssl = "*"
rustc-serialize = "*"
time = "*"
unicase = "*"
url = "*"

[dev-dependencies]
env_logger = "*"

4 changes: 4 additions & 0 deletions examples/client.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#![deny(warnings)]
extern crate hyper;

extern crate env_logger;

use std::env;

use hyper::Client;

fn main() {
env_logger::init().unwrap();

let url = match env::args().nth(1) {
Some(url) => url,
None => {
Expand Down
2 changes: 2 additions & 0 deletions examples/hello.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![deny(warnings)]
#![feature(io, net)]
extern crate hyper;
extern crate env_logger;

use std::io::Write;
use std::net::IpAddr;
Expand All @@ -15,6 +16,7 @@ fn hello(_: Request, res: Response) {
}

fn main() {
env_logger::init().unwrap();
let _listening = hyper::Server::http(hello)
.listen(IpAddr::new_v4(127, 0, 0, 1), 3000).unwrap();
println!("Listening on http://127.0.0.1:3000");
Expand Down
5 changes: 3 additions & 2 deletions examples/server.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![deny(warnings)]
#![feature(io, net)]
extern crate hyper;
#[macro_use] extern crate log;
extern crate env_logger;

use std::io::{Write, copy};
use std::net::IpAddr;
Expand All @@ -15,7 +15,7 @@ macro_rules! try_return(
($e:expr) => {{
match $e {
Ok(v) => v,
Err(e) => { error!("Error: {}", e); return; }
Err(e) => { println!("Error: {}", e); return; }
}
}}
);
Expand Down Expand Up @@ -51,6 +51,7 @@ fn echo(mut req: Request, mut res: Response) {
}

fn main() {
env_logger::init().unwrap();
let server = Server::http(echo);
let _guard = server.listen(IpAddr::new_v4(127, 0, 0, 1), 1337).unwrap();
println!("Listening on http://127.0.0.1:1337");
Expand Down
16 changes: 9 additions & 7 deletions src/client/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use header;
use header::{ContentLength, TransferEncoding};
use header::Encoding::Chunked;
use net::{NetworkStream, HttpStream};
use http::{read_status_line, HttpReader, RawStatus};
use http::{self, HttpReader, RawStatus};
use http::HttpReader::{SizedReader, ChunkedReader, EofReader};
use status;
use version;
Expand Down Expand Up @@ -36,15 +36,17 @@ impl Response {
/// Creates a new response from a server.
pub fn new(stream: Box<NetworkStream + Send>) -> HttpResult<Response> {
let mut stream = BufReader::new(stream);
let (version, raw_status) = try!(read_status_line(&mut stream));

let head = try!(http::parse_response(&mut stream));
let raw_status = head.subject;
let headers = head.headers;

let status = match FromPrimitive::from_u16(raw_status.0) {
Some(status) => status,
None => return Err(HttpStatusError)
};
debug!("{:?} {:?}", version, status);

let headers = try!(header::Headers::from_raw(&mut stream));
debug!("Headers: [\n{:?}]", headers);
debug!("version={:?}, status={:?}", head.version, status);
debug!("headers={:?}", headers);

let body = if headers.has::<TransferEncoding>() {
match headers.get::<TransferEncoding>() {
Expand Down Expand Up @@ -74,7 +76,7 @@ impl Response {

Ok(Response {
status: status,
version: version,
version: head.version,
headers: headers,
body: body,
status_raw: raw_status,
Expand Down
88 changes: 88 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! HttpError and HttpResult module.
use std::error::{Error, FromError};
use std::fmt;
use std::io::Error as IoError;

use httparse;
use url;

use self::HttpError::{HttpMethodError, HttpUriError, HttpVersionError,
HttpHeaderError, HttpStatusError, HttpIoError,
HttpTooLargeError};


/// Result type often returned from methods that can have `HttpError`s.
pub type HttpResult<T> = Result<T, HttpError>;

/// A set of errors that can occur parsing HTTP streams.
#[derive(Debug, PartialEq, Clone)]
pub enum HttpError {
/// An invalid `Method`, such as `GE,T`.
HttpMethodError,
/// An invalid `RequestUri`, such as `exam ple.domain`.
HttpUriError(url::ParseError),
/// An invalid `HttpVersion`, such as `HTP/1.1`
HttpVersionError,
/// An invalid `Header`.
HttpHeaderError,
/// A message head is too large to be reasonable.
HttpTooLargeError,
/// An invalid `Status`, such as `1337 ELITE`.
HttpStatusError,
/// An `IoError` that occured while trying to read or write to a network stream.
HttpIoError(IoError),
}

impl fmt::Display for HttpError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.description())
}
}

impl Error for HttpError {
fn description(&self) -> &str {
match *self {
HttpMethodError => "Invalid Method specified",
HttpUriError(_) => "Invalid Request URI specified",
HttpVersionError => "Invalid HTTP version specified",
HttpHeaderError => "Invalid Header provided",
HttpTooLargeError => "Message head is too large",
HttpStatusError => "Invalid Status provided",
HttpIoError(_) => "An IoError occurred while connecting to the specified network",
}
}

fn cause(&self) -> Option<&Error> {
match *self {
HttpIoError(ref error) => Some(error as &Error),
HttpUriError(ref error) => Some(error as &Error),
_ => None,
}
}
}

impl FromError<IoError> for HttpError {
fn from_error(err: IoError) -> HttpError {
HttpIoError(err)
}
}

impl FromError<url::ParseError> for HttpError {
fn from_error(err: url::ParseError) -> HttpError {
HttpUriError(err)
}
}

impl FromError<httparse::Error> for HttpError {
fn from_error(err: httparse::Error) -> HttpError {
match err {
httparse::Error::HeaderName => HttpHeaderError,
httparse::Error::HeaderValue => HttpHeaderError,
httparse::Error::NewLine => HttpHeaderError,
httparse::Error::Status => HttpStatusError,
httparse::Error::Token => HttpHeaderError,
httparse::Error::TooManyHeaders => HttpTooLargeError,
httparse::Error::Version => HttpVersionError,
}
}
}
20 changes: 9 additions & 11 deletions src/header/common/authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ impl<S: Scheme + 'static> Header for Authorization<S> where <S as FromStr>::Err:
match (from_utf8(unsafe { &raw.get_unchecked(0)[..] }), Scheme::scheme(None::<S>)) {
(Ok(header), Some(scheme))
if header.starts_with(scheme) && header.len() > scheme.len() + 1 => {
header[scheme.len() + 1..].parse::<S>().map(|s| Authorization(s)).ok()
header[scheme.len() + 1..].parse::<S>().map(Authorization).ok()
},
(Ok(header), None) => header.parse::<S>().map(|s| Authorization(s)).ok(),
(Ok(header), None) => header.parse::<S>().map(Authorization).ok(),
_ => None
}
} else {
Expand Down Expand Up @@ -143,7 +143,7 @@ impl FromStr for Basic {
#[cfg(test)]
mod tests {
use super::{Authorization, Basic};
use super::super::super::{Headers};
use super::super::super::{Headers, Header};

#[test]
fn test_raw_auth() {
Expand All @@ -154,8 +154,8 @@ mod tests {

#[test]
fn test_raw_auth_parse() {
let headers = Headers::from_raw(&mut b"Authorization: foo bar baz\r\n\r\n").unwrap();
assert_eq!(&headers.get::<Authorization<String>>().unwrap().0[..], "foo bar baz");
let header: Authorization<String> = Header::parse_header(&[b"foo bar baz".to_vec()]).unwrap();
assert_eq!(header.0, "foo bar baz");
}

#[test]
Expand All @@ -174,17 +174,15 @@ mod tests {

#[test]
fn test_basic_auth_parse() {
let headers = Headers::from_raw(&mut b"Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\r\n").unwrap();
let auth = headers.get::<Authorization<Basic>>().unwrap();
assert_eq!(&auth.0.username[..], "Aladdin");
let auth: Authorization<Basic> = Header::parse_header(&[b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".to_vec()]).unwrap();
assert_eq!(auth.0.username, "Aladdin");
assert_eq!(auth.0.password, Some("open sesame".to_string()));
}

#[test]
fn test_basic_auth_parse_no_password() {
let headers = Headers::from_raw(&mut b"Authorization: Basic QWxhZGRpbjo=\r\n\r\n").unwrap();
let auth = headers.get::<Authorization<Basic>>().unwrap();
assert_eq!(auth.0.username.as_slice(), "Aladdin");
let auth: Authorization<Basic> = Header::parse_header(&[b"Basic QWxhZGRpbjo=".to_vec()]).unwrap();
assert_eq!(auth.0.username, "Aladdin");
assert_eq!(auth.0.password, Some("".to_string()));
}

Expand Down
Loading

0 comments on commit b87bb20

Please sign in to comment.