Skip to content

Commit b87bb20

Browse files
committed
perf(http): changes http parsing to use httparse crate
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.
1 parent 003b655 commit b87bb20

File tree

15 files changed

+354
-727
lines changed

15 files changed

+354
-727
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@ keywords = ["http", "hyper", "hyperium"]
1313

1414
[dependencies]
1515
cookie = "*"
16+
httparse = "*"
1617
log = ">= 0.2.0"
1718
mime = "*"
1819
openssl = "*"
1920
rustc-serialize = "*"
2021
time = "*"
2122
unicase = "*"
2223
url = "*"
24+
25+
[dev-dependencies]
26+
env_logger = "*"
27+

examples/client.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
#![deny(warnings)]
22
extern crate hyper;
33

4+
extern crate env_logger;
5+
46
use std::env;
57

68
use hyper::Client;
79

810
fn main() {
11+
env_logger::init().unwrap();
12+
913
let url = match env::args().nth(1) {
1014
Some(url) => url,
1115
None => {

examples/hello.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![deny(warnings)]
22
#![feature(io, net)]
33
extern crate hyper;
4+
extern crate env_logger;
45

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

1718
fn main() {
19+
env_logger::init().unwrap();
1820
let _listening = hyper::Server::http(hello)
1921
.listen(IpAddr::new_v4(127, 0, 0, 1), 3000).unwrap();
2022
println!("Listening on http://127.0.0.1:3000");

examples/server.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![deny(warnings)]
22
#![feature(io, net)]
33
extern crate hyper;
4-
#[macro_use] extern crate log;
4+
extern crate env_logger;
55

66
use std::io::{Write, copy};
77
use std::net::IpAddr;
@@ -15,7 +15,7 @@ macro_rules! try_return(
1515
($e:expr) => {{
1616
match $e {
1717
Ok(v) => v,
18-
Err(e) => { error!("Error: {}", e); return; }
18+
Err(e) => { println!("Error: {}", e); return; }
1919
}
2020
}}
2121
);
@@ -51,6 +51,7 @@ fn echo(mut req: Request, mut res: Response) {
5151
}
5252

5353
fn main() {
54+
env_logger::init().unwrap();
5455
let server = Server::http(echo);
5556
let _guard = server.listen(IpAddr::new_v4(127, 0, 0, 1), 1337).unwrap();
5657
println!("Listening on http://127.0.0.1:1337");

src/client/response.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use header;
77
use header::{ContentLength, TransferEncoding};
88
use header::Encoding::Chunked;
99
use net::{NetworkStream, HttpStream};
10-
use http::{read_status_line, HttpReader, RawStatus};
10+
use http::{self, HttpReader, RawStatus};
1111
use http::HttpReader::{SizedReader, ChunkedReader, EofReader};
1212
use status;
1313
use version;
@@ -36,15 +36,17 @@ impl Response {
3636
/// Creates a new response from a server.
3737
pub fn new(stream: Box<NetworkStream + Send>) -> HttpResult<Response> {
3838
let mut stream = BufReader::new(stream);
39-
let (version, raw_status) = try!(read_status_line(&mut stream));
39+
40+
let head = try!(http::parse_response(&mut stream));
41+
let raw_status = head.subject;
42+
let headers = head.headers;
43+
4044
let status = match FromPrimitive::from_u16(raw_status.0) {
4145
Some(status) => status,
4246
None => return Err(HttpStatusError)
4347
};
44-
debug!("{:?} {:?}", version, status);
45-
46-
let headers = try!(header::Headers::from_raw(&mut stream));
47-
debug!("Headers: [\n{:?}]", headers);
48+
debug!("version={:?}, status={:?}", head.version, status);
49+
debug!("headers={:?}", headers);
4850

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

7577
Ok(Response {
7678
status: status,
77-
version: version,
79+
version: head.version,
7880
headers: headers,
7981
body: body,
8082
status_raw: raw_status,

src/error.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//! HttpError and HttpResult module.
2+
use std::error::{Error, FromError};
3+
use std::fmt;
4+
use std::io::Error as IoError;
5+
6+
use httparse;
7+
use url;
8+
9+
use self::HttpError::{HttpMethodError, HttpUriError, HttpVersionError,
10+
HttpHeaderError, HttpStatusError, HttpIoError,
11+
HttpTooLargeError};
12+
13+
14+
/// Result type often returned from methods that can have `HttpError`s.
15+
pub type HttpResult<T> = Result<T, HttpError>;
16+
17+
/// A set of errors that can occur parsing HTTP streams.
18+
#[derive(Debug, PartialEq, Clone)]
19+
pub enum HttpError {
20+
/// An invalid `Method`, such as `GE,T`.
21+
HttpMethodError,
22+
/// An invalid `RequestUri`, such as `exam ple.domain`.
23+
HttpUriError(url::ParseError),
24+
/// An invalid `HttpVersion`, such as `HTP/1.1`
25+
HttpVersionError,
26+
/// An invalid `Header`.
27+
HttpHeaderError,
28+
/// A message head is too large to be reasonable.
29+
HttpTooLargeError,
30+
/// An invalid `Status`, such as `1337 ELITE`.
31+
HttpStatusError,
32+
/// An `IoError` that occured while trying to read or write to a network stream.
33+
HttpIoError(IoError),
34+
}
35+
36+
impl fmt::Display for HttpError {
37+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38+
f.write_str(self.description())
39+
}
40+
}
41+
42+
impl Error for HttpError {
43+
fn description(&self) -> &str {
44+
match *self {
45+
HttpMethodError => "Invalid Method specified",
46+
HttpUriError(_) => "Invalid Request URI specified",
47+
HttpVersionError => "Invalid HTTP version specified",
48+
HttpHeaderError => "Invalid Header provided",
49+
HttpTooLargeError => "Message head is too large",
50+
HttpStatusError => "Invalid Status provided",
51+
HttpIoError(_) => "An IoError occurred while connecting to the specified network",
52+
}
53+
}
54+
55+
fn cause(&self) -> Option<&Error> {
56+
match *self {
57+
HttpIoError(ref error) => Some(error as &Error),
58+
HttpUriError(ref error) => Some(error as &Error),
59+
_ => None,
60+
}
61+
}
62+
}
63+
64+
impl FromError<IoError> for HttpError {
65+
fn from_error(err: IoError) -> HttpError {
66+
HttpIoError(err)
67+
}
68+
}
69+
70+
impl FromError<url::ParseError> for HttpError {
71+
fn from_error(err: url::ParseError) -> HttpError {
72+
HttpUriError(err)
73+
}
74+
}
75+
76+
impl FromError<httparse::Error> for HttpError {
77+
fn from_error(err: httparse::Error) -> HttpError {
78+
match err {
79+
httparse::Error::HeaderName => HttpHeaderError,
80+
httparse::Error::HeaderValue => HttpHeaderError,
81+
httparse::Error::NewLine => HttpHeaderError,
82+
httparse::Error::Status => HttpStatusError,
83+
httparse::Error::Token => HttpHeaderError,
84+
httparse::Error::TooManyHeaders => HttpTooLargeError,
85+
httparse::Error::Version => HttpVersionError,
86+
}
87+
}
88+
}

src/header/common/authorization.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ impl<S: Scheme + 'static> Header for Authorization<S> where <S as FromStr>::Err:
3232
match (from_utf8(unsafe { &raw.get_unchecked(0)[..] }), Scheme::scheme(None::<S>)) {
3333
(Ok(header), Some(scheme))
3434
if header.starts_with(scheme) && header.len() > scheme.len() + 1 => {
35-
header[scheme.len() + 1..].parse::<S>().map(|s| Authorization(s)).ok()
35+
header[scheme.len() + 1..].parse::<S>().map(Authorization).ok()
3636
},
37-
(Ok(header), None) => header.parse::<S>().map(|s| Authorization(s)).ok(),
37+
(Ok(header), None) => header.parse::<S>().map(Authorization).ok(),
3838
_ => None
3939
}
4040
} else {
@@ -143,7 +143,7 @@ impl FromStr for Basic {
143143
#[cfg(test)]
144144
mod tests {
145145
use super::{Authorization, Basic};
146-
use super::super::super::{Headers};
146+
use super::super::super::{Headers, Header};
147147

148148
#[test]
149149
fn test_raw_auth() {
@@ -154,8 +154,8 @@ mod tests {
154154

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

161161
#[test]
@@ -174,17 +174,15 @@ mod tests {
174174

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

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

0 commit comments

Comments
 (0)