Skip to content

Commit

Permalink
Merge pull request #370 from hyperium/httparse
Browse files Browse the repository at this point in the history
perf(http): changes http parsing to use httparse crate
  • Loading branch information
seanmonstar committed Mar 14, 2015
2 parents 79200ed + b87bb20 commit a2c6c4a
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 a2c6c4a

Please sign in to comment.