Permalink
Browse files

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.
  • Loading branch information...
1 parent 003b655 commit b87bb20f0c25891c30ef2399da2721596fbc1fcf @seanmonstar seanmonstar committed Mar 12, 2015
Showing with 354 additions and 727 deletions.
  1. +5 −0 Cargo.toml
  2. +4 −0 examples/client.rs
  3. +2 −0 examples/hello.rs
  4. +3 −2 examples/server.rs
  5. +9 −7 src/client/response.rs
  6. +88 −0 src/error.rs
  7. +9 −11 src/header/common/authorization.rs
  8. +38 −35 src/header/mod.rs
  9. +79 −554 src/http.rs
  10. +4 −68 src/lib.rs
  11. +4 −3 src/method.rs
  12. +0 −1 src/net.rs
  13. +39 −27 src/server/mod.rs
  14. +29 −19 src/server/request.rs
  15. +41 −0 src/uri.rs
View
@@ -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 = "*"
+
View
@@ -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 => {
View
@@ -1,6 +1,7 @@
#![deny(warnings)]
#![feature(io, net)]
extern crate hyper;
+extern crate env_logger;
use std::io::Write;
use std::net::IpAddr;
@@ -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");
View
@@ -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;
@@ -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; }
}
}}
);
@@ -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");
@@ -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;
@@ -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>() {
@@ -74,7 +76,7 @@ impl Response {
Ok(Response {
status: status,
- version: version,
+ version: head.version,
headers: headers,
body: body,
status_raw: raw_status,
View
@@ -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,
+ }
+ }
+}
@@ -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 {
@@ -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() {
@@ -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]
@@ -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()));
}
Oops, something went wrong.

0 comments on commit b87bb20

Please sign in to comment.