Merge pull request #370 from hyperium/httparse

perf(http): changes http parsing to use httparse crate
This commit is contained in:
Sean McArthur
2015-03-13 17:58:41 -07:00
15 changed files with 358 additions and 731 deletions

View File

@@ -13,6 +13,7 @@ keywords = ["http", "hyper", "hyperium"]
[dependencies] [dependencies]
cookie = "*" cookie = "*"
httparse = "*"
log = ">= 0.2.0" log = ">= 0.2.0"
mime = "*" mime = "*"
openssl = "*" openssl = "*"
@@ -20,3 +21,7 @@ rustc-serialize = "*"
time = "*" time = "*"
unicase = "*" unicase = "*"
url = "*" url = "*"
[dev-dependencies]
env_logger = "*"

View File

@@ -1,11 +1,15 @@
#![deny(warnings)] #![deny(warnings)]
extern crate hyper; extern crate hyper;
extern crate env_logger;
use std::env; use std::env;
use hyper::Client; use hyper::Client;
fn main() { fn main() {
env_logger::init().unwrap();
let url = match env::args().nth(1) { let url = match env::args().nth(1) {
Some(url) => url, Some(url) => url,
None => { None => {

View File

@@ -1,6 +1,7 @@
#![deny(warnings)] #![deny(warnings)]
#![feature(io, net)] #![feature(io, net)]
extern crate hyper; extern crate hyper;
extern crate env_logger;
use std::io::Write; use std::io::Write;
use std::net::IpAddr; use std::net::IpAddr;
@@ -15,6 +16,7 @@ fn hello(_: Request, res: Response) {
} }
fn main() { fn main() {
env_logger::init().unwrap();
let _listening = hyper::Server::http(hello) let _listening = hyper::Server::http(hello)
.listen(IpAddr::new_v4(127, 0, 0, 1), 3000).unwrap(); .listen(IpAddr::new_v4(127, 0, 0, 1), 3000).unwrap();
println!("Listening on http://127.0.0.1:3000"); println!("Listening on http://127.0.0.1:3000");

View File

@@ -1,7 +1,7 @@
#![deny(warnings)] #![deny(warnings)]
#![feature(io, net)] #![feature(io, net)]
extern crate hyper; extern crate hyper;
#[macro_use] extern crate log; extern crate env_logger;
use std::io::{Write, copy}; use std::io::{Write, copy};
use std::net::IpAddr; use std::net::IpAddr;
@@ -15,7 +15,7 @@ macro_rules! try_return(
($e:expr) => {{ ($e:expr) => {{
match $e { match $e {
Ok(v) => v, 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() { fn main() {
env_logger::init().unwrap();
let server = Server::http(echo); let server = Server::http(echo);
let _guard = server.listen(IpAddr::new_v4(127, 0, 0, 1), 1337).unwrap(); let _guard = server.listen(IpAddr::new_v4(127, 0, 0, 1), 1337).unwrap();
println!("Listening on http://127.0.0.1:1337"); println!("Listening on http://127.0.0.1:1337");

View File

@@ -7,7 +7,7 @@ use header;
use header::{ContentLength, TransferEncoding}; use header::{ContentLength, TransferEncoding};
use header::Encoding::Chunked; use header::Encoding::Chunked;
use net::{NetworkStream, HttpStream}; use net::{NetworkStream, HttpStream};
use http::{read_status_line, HttpReader, RawStatus}; use http::{self, HttpReader, RawStatus};
use http::HttpReader::{SizedReader, ChunkedReader, EofReader}; use http::HttpReader::{SizedReader, ChunkedReader, EofReader};
use status; use status;
use version; use version;
@@ -36,15 +36,17 @@ impl Response {
/// Creates a new response from a server. /// Creates a new response from a server.
pub fn new(stream: Box<NetworkStream + Send>) -> HttpResult<Response> { pub fn new(stream: Box<NetworkStream + Send>) -> HttpResult<Response> {
let mut stream = BufReader::new(stream); 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) { let status = match FromPrimitive::from_u16(raw_status.0) {
Some(status) => status, Some(status) => status,
None => return Err(HttpStatusError) None => return Err(HttpStatusError)
}; };
debug!("{:?} {:?}", version, status); debug!("version={:?}, status={:?}", head.version, status);
debug!("headers={:?}", headers);
let headers = try!(header::Headers::from_raw(&mut stream));
debug!("Headers: [\n{:?}]", headers);
let body = if headers.has::<TransferEncoding>() { let body = if headers.has::<TransferEncoding>() {
match headers.get::<TransferEncoding>() { match headers.get::<TransferEncoding>() {
@@ -74,7 +76,7 @@ impl Response {
Ok(Response { Ok(Response {
status: status, status: status,
version: version, version: head.version,
headers: headers, headers: headers,
body: body, body: body,
status_raw: raw_status, status_raw: raw_status,

88
src/error.rs Normal file
View File

@@ -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,
}
}
}

View File

@@ -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>)) { match (from_utf8(unsafe { &raw.get_unchecked(0)[..] }), Scheme::scheme(None::<S>)) {
(Ok(header), Some(scheme)) (Ok(header), Some(scheme))
if header.starts_with(scheme) && header.len() > scheme.len() + 1 => { 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 _ => None
} }
} else { } else {
@@ -143,7 +143,7 @@ impl FromStr for Basic {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Authorization, Basic}; use super::{Authorization, Basic};
use super::super::super::{Headers}; use super::super::super::{Headers, Header};
#[test] #[test]
fn test_raw_auth() { fn test_raw_auth() {
@@ -154,8 +154,8 @@ mod tests {
#[test] #[test]
fn test_raw_auth_parse() { fn test_raw_auth_parse() {
let headers = Headers::from_raw(&mut b"Authorization: foo bar baz\r\n\r\n").unwrap(); let header: Authorization<String> = Header::parse_header(&[b"foo bar baz".to_vec()]).unwrap();
assert_eq!(&headers.get::<Authorization<String>>().unwrap().0[..], "foo bar baz"); assert_eq!(header.0, "foo bar baz");
} }
#[test] #[test]
@@ -174,17 +174,15 @@ mod tests {
#[test] #[test]
fn test_basic_auth_parse() { fn test_basic_auth_parse() {
let headers = Headers::from_raw(&mut b"Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\r\n").unwrap(); let auth: Authorization<Basic> = Header::parse_header(&[b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".to_vec()]).unwrap();
let auth = headers.get::<Authorization<Basic>>().unwrap(); assert_eq!(auth.0.username, "Aladdin");
assert_eq!(&auth.0.username[..], "Aladdin");
assert_eq!(auth.0.password, Some("open sesame".to_string())); assert_eq!(auth.0.password, Some("open sesame".to_string()));
} }
#[test] #[test]
fn test_basic_auth_parse_no_password() { fn test_basic_auth_parse_no_password() {
let headers = Headers::from_raw(&mut b"Authorization: Basic QWxhZGRpbjo=\r\n\r\n").unwrap(); let auth: Authorization<Basic> = Header::parse_header(&[b"Basic QWxhZGRpbjo=".to_vec()]).unwrap();
let auth = headers.get::<Authorization<Basic>>().unwrap(); assert_eq!(auth.0.username, "Aladdin");
assert_eq!(auth.0.username.as_slice(), "Aladdin");
assert_eq!(auth.0.password, Some("".to_string())); assert_eq!(auth.0.password, Some("".to_string()));
} }

View File

@@ -5,9 +5,9 @@
//! must implement the `Header` trait from this module. Several common headers //! must implement the `Header` trait from this module. Several common headers
//! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others. //! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others.
use std::any::Any; use std::any::Any;
use std::borrow::Cow::{Borrowed, Owned}; use std::borrow::Cow::{Borrowed};
use std::borrow::ToOwned;
use std::fmt; use std::fmt;
use std::io::Read;
use std::raw::TraitObject; use std::raw::TraitObject;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::hash_map::{Iter, Entry}; use std::collections::hash_map::{Iter, Entry};
@@ -15,10 +15,11 @@ use std::iter::{FromIterator, IntoIterator};
use std::borrow::{Cow, IntoCow}; use std::borrow::{Cow, IntoCow};
use std::{mem, raw}; use std::{mem, raw};
use httparse;
use unicase::UniCase; use unicase::UniCase;
use self::internals::Item; use self::internals::Item;
use {http, HttpResult, HttpError}; use error::HttpResult;
pub use self::shared::{Charset, Encoding, EntityTag, Quality, QualityItem, qitem, q}; pub use self::shared::{Charset, Encoding, EntityTag, Quality, QualityItem, qitem, q};
pub use self::common::*; pub use self::common::*;
@@ -105,10 +106,6 @@ pub struct Headers {
data: HashMap<HeaderName, Item> data: HashMap<HeaderName, Item>
} }
// To prevent DOS from a server sending a never ending header.
// The value was copied from curl.
const MAX_HEADERS_LENGTH: u32 = 100 * 1024;
impl Headers { impl Headers {
/// Creates a new, empty headers map. /// Creates a new, empty headers map.
@@ -119,27 +116,18 @@ impl Headers {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn from_raw<R: Read>(rdr: &mut R) -> HttpResult<Headers> { pub fn from_raw<'a>(raw: &[httparse::Header<'a>]) -> HttpResult<Headers> {
let mut headers = Headers::new(); let mut headers = Headers::new();
let mut count = 0u32; for header in raw {
loop { debug!("raw header: {:?}={:?}", header.name, &header.value[..]);
match try!(http::read_header(rdr)) { let name = UniCase(header.name.to_owned().into_cow());
Some((name, value)) => { let mut item = match headers.data.entry(name) {
debug!("raw header: {:?}={:?}", name, &value[..]); Entry::Vacant(entry) => entry.insert(Item::new_raw(vec![])),
count += (name.len() + value.len()) as u32; Entry::Occupied(entry) => entry.into_mut()
if count > MAX_HEADERS_LENGTH { };
debug!("Max header size reached, aborting"); let trim = header.value.iter().rev().take_while(|&&x| x == b' ').count();
return Err(HttpError::HttpHeaderError) let value = &header.value[.. header.value.len() - trim];
} item.mut_raw().push(value.to_vec());
let name = UniCase(Owned(name));
let mut item = match headers.data.entry(name) {
Entry::Vacant(entry) => entry.insert(Item::new_raw(vec![])),
Entry::Occupied(entry) => entry.into_mut()
};
item.mut_raw().push(value);
},
None => break,
}
} }
Ok(headers) Ok(headers)
} }
@@ -364,12 +352,26 @@ mod tests {
use mime::SubLevel::Plain; use mime::SubLevel::Plain;
use super::{Headers, Header, HeaderFormat, ContentLength, ContentType, use super::{Headers, Header, HeaderFormat, ContentLength, ContentType,
Accept, Host, qitem}; Accept, Host, qitem};
use httparse;
use test::Bencher; use test::Bencher;
macro_rules! raw {
($($line:expr),*) => ({
[$({
let line = $line;
let pos = line.position_elem(&b':').expect("raw splits on :, not found");
httparse::Header {
name: ::std::str::from_utf8(&line[..pos]).unwrap(),
value: &line[pos + 2..]
}
}),*]
})
}
#[test] #[test]
fn test_from_raw() { fn test_from_raw() {
let headers = Headers::from_raw(&mut b"Content-Length: 10\r\n\r\n").unwrap(); let headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
assert_eq!(headers.get(), Some(&ContentLength(10))); assert_eq!(headers.get(), Some(&ContentLength(10)));
} }
@@ -422,20 +424,20 @@ mod tests {
#[test] #[test]
fn test_different_structs_for_same_header() { fn test_different_structs_for_same_header() {
let headers = Headers::from_raw(&mut b"Content-Length: 10\r\n\r\n").unwrap(); let headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
assert_eq!(headers.get::<ContentLength>(), Some(&ContentLength(10))); assert_eq!(headers.get::<ContentLength>(), Some(&ContentLength(10)));
assert_eq!(headers.get::<CrazyLength>(), Some(&CrazyLength(Some(false), 10))); assert_eq!(headers.get::<CrazyLength>(), Some(&CrazyLength(Some(false), 10)));
} }
#[test] #[test]
fn test_trailing_whitespace() { fn test_trailing_whitespace() {
let headers = Headers::from_raw(&mut b"Content-Length: 10 \r\n\r\n").unwrap(); let headers = Headers::from_raw(&raw!(b"Content-Length: 10 ")).unwrap();
assert_eq!(headers.get::<ContentLength>(), Some(&ContentLength(10))); assert_eq!(headers.get::<ContentLength>(), Some(&ContentLength(10)));
} }
#[test] #[test]
fn test_multiple_reads() { fn test_multiple_reads() {
let headers = Headers::from_raw(&mut b"Content-Length: 10\r\n\r\n").unwrap(); let headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
let ContentLength(one) = *headers.get::<ContentLength>().unwrap(); let ContentLength(one) = *headers.get::<ContentLength>().unwrap();
let ContentLength(two) = *headers.get::<ContentLength>().unwrap(); let ContentLength(two) = *headers.get::<ContentLength>().unwrap();
assert_eq!(one, two); assert_eq!(one, two);
@@ -443,14 +445,14 @@ mod tests {
#[test] #[test]
fn test_different_reads() { fn test_different_reads() {
let headers = Headers::from_raw(&mut b"Content-Length: 10\r\nContent-Type: text/plain\r\n\r\n").unwrap(); let headers = Headers::from_raw(&raw!(b"Content-Length: 10", b"Content-Type: text/plain")).unwrap();
let ContentLength(_) = *headers.get::<ContentLength>().unwrap(); let ContentLength(_) = *headers.get::<ContentLength>().unwrap();
let ContentType(_) = *headers.get::<ContentType>().unwrap(); let ContentType(_) = *headers.get::<ContentType>().unwrap();
} }
#[test] #[test]
fn test_get_mutable() { fn test_get_mutable() {
let mut headers = Headers::from_raw(&mut b"Content-Length: 10\r\nContent-Type: text/plain\r\n\r\n").unwrap(); let mut headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
*headers.get_mut::<ContentLength>().unwrap() = ContentLength(20); *headers.get_mut::<ContentLength>().unwrap() = ContentLength(20);
assert_eq!(*headers.get::<ContentLength>().unwrap(), ContentLength(20)); assert_eq!(*headers.get::<ContentLength>().unwrap(), ContentLength(20));
} }
@@ -471,7 +473,7 @@ mod tests {
#[test] #[test]
fn test_headers_show_raw() { fn test_headers_show_raw() {
let headers = Headers::from_raw(&mut b"Content-Length: 10\r\n\r\n").unwrap(); let headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
let s = headers.to_string(); let s = headers.to_string();
assert_eq!(s, "Content-Length: 10\r\n"); assert_eq!(s, "Content-Length: 10\r\n");
} }
@@ -538,7 +540,8 @@ mod tests {
#[bench] #[bench]
fn bench_headers_from_raw(b: &mut Bencher) { fn bench_headers_from_raw(b: &mut Bencher) {
b.iter(|| Headers::from_raw(&mut b"Content-Length: 10\r\n\r\n").unwrap()) let raw = raw!(b"Content-Length: 10");
b.iter(|| Headers::from_raw(&raw).unwrap())
} }
#[bench] #[bench]

View File

@@ -1,22 +1,15 @@
//! Pieces pertaining to the HTTP message protocol. //! Pieces pertaining to the HTTP message protocol.
use std::borrow::Cow::{self, Borrowed, Owned}; use std::borrow::{Cow, IntoCow, ToOwned};
use std::borrow::IntoCow;
use std::cmp::min; use std::cmp::min;
use std::io::{self, Read, Write, Cursor}; use std::io::{self, Read, Write, BufRead};
use std::num::from_u16;
use std::str;
use url::Url; use httparse;
use url::ParseError as UrlError;
use method; use header::Headers;
use status::StatusCode; use method::Method;
use uri; use uri::RequestUri;
use uri::RequestUri::{AbsolutePath, AbsoluteUri, Authority, Star}; use version::HttpVersion::{self, Http10, Http11};
use version::HttpVersion; use HttpError:: HttpTooLargeError;
use version::HttpVersion::{Http09, Http10, Http11};
use HttpError::{HttpHeaderError, HttpMethodError, HttpStatusError,
HttpUriError, HttpVersionError};
use HttpResult; use HttpResult;
use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader}; use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader};
@@ -314,338 +307,74 @@ impl<W: Write> Write for HttpWriter<W> {
} }
} }
/// Parses a request into an Incoming message head.
pub fn parse_request<T: BufRead>(buf: &mut T) -> HttpResult<Incoming<(Method, RequestUri)>> {
let (inc, len) = {
let slice = try!(buf.fill_buf());
let mut headers = [httparse::Header { name: "", value: b"" }; 64];
let mut req = httparse::Request::new(&mut headers);
match try!(req.parse(slice)) {
httparse::Status::Complete(len) => {
(Incoming {
version: if req.version.unwrap() == 1 { Http11 } else { Http10 },
subject: (
try!(req.method.unwrap().parse()),
try!(req.path.unwrap().parse())
),
headers: try!(Headers::from_raw(req.headers))
}, len)
},
_ => {
// request head is bigger than a BufRead's buffer? 400 that!
return Err(HttpTooLargeError)
}
}
};
buf.consume(len);
Ok(inc)
}
/// Parses a response into an Incoming message head.
pub fn parse_response<T: BufRead>(buf: &mut T) -> HttpResult<Incoming<RawStatus>> {
let (inc, len) = {
let mut headers = [httparse::Header { name: "", value: b"" }; 64];
let mut res = httparse::Response::new(&mut headers);
match try!(res.parse(try!(buf.fill_buf()))) {
httparse::Status::Complete(len) => {
(Incoming {
version: if res.version.unwrap() == 1 { Http11 } else { Http10 },
subject: RawStatus(
res.code.unwrap(), res.reason.unwrap().to_owned().into_cow()
),
headers: try!(Headers::from_raw(res.headers))
}, len)
},
_ => {
// response head is bigger than a BufRead's buffer?
return Err(HttpTooLargeError)
}
}
};
buf.consume(len);
Ok(inc)
}
/// An Incoming Message head. Includes request/status line, and headers.
pub struct Incoming<S> {
/// HTTP version of the message.
pub version: HttpVersion,
/// Subject (request line or status line) of Incoming message.
pub subject: S,
/// Headers of the Incoming message.
pub headers: Headers
}
pub const SP: u8 = b' '; pub const SP: u8 = b' ';
pub const CR: u8 = b'\r'; pub const CR: u8 = b'\r';
pub const LF: u8 = b'\n'; pub const LF: u8 = b'\n';
pub const STAR: u8 = b'*'; pub const STAR: u8 = b'*';
pub const LINE_ENDING: &'static str = "\r\n"; pub const LINE_ENDING: &'static str = "\r\n";
/// Determines if byte is a token char.
///
/// > ```notrust
/// > token = 1*tchar
/// >
/// > tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
/// > / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
/// > / DIGIT / ALPHA
/// > ; any VCHAR, except delimiters
/// > ```
#[inline]
pub fn is_token(b: u8) -> bool {
match b {
b'a'...b'z' |
b'A'...b'Z' |
b'0'...b'9' |
b'!' |
b'#' |
b'$' |
b'%' |
b'&' |
b'\''|
b'*' |
b'+' |
b'-' |
b'.' |
b'^' |
b'_' |
b'`' |
b'|' |
b'~' => true,
_ => false
}
}
/// Read token bytes from `stream` into `buf` until a space is encountered.
/// Returns `Ok(true)` if we read until a space,
/// `Ok(false)` if we got to the end of `buf` without encountering a space,
/// otherwise returns any error encountered reading the stream.
///
/// The remaining contents of `buf` are left untouched.
fn read_method_token_until_space<R: Read>(stream: &mut R, buf: &mut [u8]) -> HttpResult<bool> {
macro_rules! byte (
($rdr:ident) => ({
let mut slot = [0];
match try!($rdr.read(&mut slot)) {
1 => slot[0],
_ => return Err(HttpMethodError),
}
})
);
let mut cursor = Cursor::new(buf);
loop {
let b = byte!(stream);
if b == SP {
break;
} else if !is_token(b) {
return Err(HttpMethodError);
// Read to end but there's still more
} else {
match cursor.write(&[b]) {
Ok(1) => (),
_ => return Ok(false)
}
}
}
if cursor.position() == 0 {
return Err(HttpMethodError);
}
Ok(true)
}
/// Read a `Method` from a raw stream, such as `GET`.
/// ### Note:
/// Extension methods are only parsed to 16 characters.
pub fn read_method<R: Read>(stream: &mut R) -> HttpResult<method::Method> {
let mut buf = [SP; 16];
if !try!(read_method_token_until_space(stream, &mut buf)) {
return Err(HttpMethodError);
}
let maybe_method = match &buf[0..7] {
b"GET " => Some(method::Method::Get),
b"PUT " => Some(method::Method::Put),
b"POST " => Some(method::Method::Post),
b"HEAD " => Some(method::Method::Head),
b"PATCH " => Some(method::Method::Patch),
b"TRACE " => Some(method::Method::Trace),
b"DELETE " => Some(method::Method::Delete),
b"CONNECT" => Some(method::Method::Connect),
b"OPTIONS" => Some(method::Method::Options),
_ => None,
};
debug!("maybe_method = {:?}", maybe_method);
match (maybe_method, &buf[..]) {
(Some(method), _) => Ok(method),
(None, ext) => {
// We already checked that the buffer is ASCII
Ok(method::Method::Extension(unsafe { str::from_utf8_unchecked(ext) }.trim().to_string()))
},
}
}
/// Read a `RequestUri` from a raw stream.
pub fn read_uri<R: Read>(stream: &mut R) -> HttpResult<uri::RequestUri> {
macro_rules! byte (
($rdr:ident) => ({
let mut buf = [0];
match try!($rdr.read(&mut buf)) {
1 => buf[0],
_ => return Err(HttpUriError(UrlError::InvalidCharacter)),
}
})
);
let mut b = byte!(stream);
while b == SP {
b = byte!(stream);
}
let mut s = String::new();
if b == STAR {
try!(expect(byte!(stream), SP));
return Ok(Star)
} else {
s.push(b as char);
loop {
match byte!(stream) {
SP => {
break;
},
CR | LF => {
return Err(HttpUriError(UrlError::InvalidCharacter))
},
b => s.push(b as char)
}
}
}
debug!("uri buf = {:?}", s);
if s.as_slice().starts_with("/") {
Ok(AbsolutePath(s))
} else if s.as_slice().contains("/") {
Ok(AbsoluteUri(try!(Url::parse(s.as_slice()))))
} else {
let mut temp = "http://".to_string();
temp.push_str(s.as_slice());
try!(Url::parse(temp.as_slice()));
todo!("compare vs u.authority()");
Ok(Authority(s))
}
}
/// Read the `HttpVersion` from a raw stream, such as `HTTP/1.1`.
pub fn read_http_version<R: Read>(stream: &mut R) -> HttpResult<HttpVersion> {
macro_rules! byte (
($rdr:ident) => ({
let mut buf = [0];
match try!($rdr.read(&mut buf)) {
1 => buf[0],
_ => return Err(HttpVersionError),
}
})
);
try!(expect(byte!(stream), b'H'));
try!(expect(byte!(stream), b'T'));
try!(expect(byte!(stream), b'T'));
try!(expect(byte!(stream), b'P'));
try!(expect(byte!(stream), b'/'));
match byte!(stream) {
b'0' => {
try!(expect(byte!(stream), b'.'));
try!(expect(byte!(stream), b'9'));
Ok(Http09)
},
b'1' => {
try!(expect(byte!(stream), b'.'));
match byte!(stream) {
b'0' => Ok(Http10),
b'1' => Ok(Http11),
_ => Err(HttpVersionError)
}
},
_ => Err(HttpVersionError)
}
}
const MAX_HEADER_NAME_LENGTH: usize = 100;
const MAX_HEADER_FIELD_LENGTH: usize = 4096;
/// The raw bytes when parsing a header line.
///
/// A String and Vec<u8>, divided by COLON (`:`). The String is guaranteed
/// to be all `token`s. See `is_token` source for all valid characters.
pub type RawHeaderLine = (String, Vec<u8>);
/// Read a RawHeaderLine from a Reader.
///
/// From [spec](https://tools.ietf.org/html/http#section-3.2):
///
/// > Each header field consists of a case-insensitive field name followed
/// > by a colon (":"), optional leading whitespace, the field value, and
/// > optional trailing whitespace.
/// >
/// > ```notrust
/// > header-field = field-name ":" OWS field-value OWS
/// >
/// > field-name = token
/// > field-value = *( field-content / obs-fold )
/// > field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
/// > field-vchar = VCHAR / obs-text
/// >
/// > obs-fold = CRLF 1*( SP / HTAB )
/// > ; obsolete line folding
/// > ; see Section 3.2.4
/// > ```
pub fn read_header<R: Read>(stream: &mut R) -> HttpResult<Option<RawHeaderLine>> {
macro_rules! byte (
($rdr:ident) => ({
let mut buf = [0];
match try!($rdr.read(&mut buf)) {
1 => buf[0],
_ => return Err(HttpHeaderError),
}
})
);
let mut name = String::new();
let mut value = vec![];
loop {
match byte!(stream) {
CR if name.len() == 0 => {
match byte!(stream) {
LF => return Ok(None),
_ => return Err(HttpHeaderError)
}
},
b':' => break,
b if is_token(b) => {
if name.len() > MAX_HEADER_NAME_LENGTH { return Err(HttpHeaderError); }
name.push(b as char)
},
_nontoken => return Err(HttpHeaderError)
};
}
debug!("header name = {:?}", name);
let mut ows = true; //optional whitespace
todo!("handle obs-folding (gross!)");
loop {
match byte!(stream) {
CR => break,
LF => return Err(HttpHeaderError),
b' ' if ows => {},
b => {
ows = false;
if value.len() > MAX_HEADER_FIELD_LENGTH { return Err(HttpHeaderError); }
value.push(b)
}
};
}
// Remove optional trailing whitespace
let real_len = value.len() - value.iter().rev().take_while(|&&x| b' ' == x).count();
value.truncate(real_len);
match byte!(stream) {
LF => Ok(Some((name, value))),
_ => Err(HttpHeaderError)
}
}
/// `request-line = method SP request-target SP HTTP-version CRLF`
pub type RequestLine = (method::Method, uri::RequestUri, HttpVersion);
/// Read the `RequestLine`, such as `GET / HTTP/1.1`.
pub fn read_request_line<R: Read>(stream: &mut R) -> HttpResult<RequestLine> {
macro_rules! byte (
($rdr:ident) => ({
let mut buf = [0];
match try!($rdr.read(&mut buf)) {
1 => buf[0],
_ => return Err(HttpVersionError),
}
})
);
debug!("read request line");
let method = try!(read_method(stream));
debug!("method = {:?}", method);
let uri = try!(read_uri(stream));
debug!("uri = {:?}", uri);
let version = try!(read_http_version(stream));
debug!("version = {:?}", version);
if byte!(stream) != CR {
return Err(HttpVersionError);
}
if byte!(stream) != LF {
return Err(HttpVersionError);
}
Ok((method, uri, version))
}
/// `status-line = HTTP-version SP status-code SP reason-phrase CRLF`
///
/// However, reason-phrase is absolutely useless, so its tossed.
pub type StatusLine = (HttpVersion, RawStatus);
/// The raw status code and reason-phrase. /// The raw status code and reason-phrase.
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub struct RawStatus(pub u16, pub Cow<'static, str>); pub struct RawStatus(pub u16, pub Cow<'static, str>);
@@ -656,219 +385,12 @@ impl Clone for RawStatus {
} }
} }
/// Read the StatusLine, such as `HTTP/1.1 200 OK`.
///
/// > The first line of a response message is the status-line, consisting
/// > of the protocol version, a space (SP), the status code, another
/// > space, a possibly empty textual phrase describing the status code,
/// > and ending with CRLF.
/// >
/// >```notrust
/// > status-line = HTTP-version SP status-code SP reason-phrase CRLF
/// > status-code = 3DIGIT
/// > reason-phrase = *( HTAB / SP / VCHAR / obs-text )
/// >```
pub fn read_status_line<R: Read>(stream: &mut R) -> HttpResult<StatusLine> {
macro_rules! byte (
($rdr:ident) => ({
let mut buf = [0];
match try!($rdr.read(&mut buf)) {
1 => buf[0],
_ => return Err(HttpVersionError),
}
})
);
let version = try!(read_http_version(stream));
if byte!(stream) != SP {
return Err(HttpVersionError);
}
let code = try!(read_status(stream));
Ok((version, code))
}
/// Read the StatusCode from a stream.
pub fn read_status<R: Read>(stream: &mut R) -> HttpResult<RawStatus> {
macro_rules! byte (
($rdr:ident) => ({
let mut buf = [0];
match try!($rdr.read(&mut buf)) {
1 => buf[0],
_ => return Err(HttpStatusError),
}
})
);
let code = [
byte!(stream),
byte!(stream),
byte!(stream),
];
let code = match str::from_utf8(code.as_slice()).ok().and_then(|x| x.parse().ok()) {
Some(num) => num,
None => return Err(HttpStatusError)
};
match byte!(stream) {
b' ' => (),
_ => return Err(HttpStatusError)
}
let mut buf = [SP; 32];
let mut cursor = Cursor::new(&mut buf[..]);
{
'read: loop {
match byte!(stream) {
CR => match byte!(stream) {
LF => break,
_ => return Err(HttpStatusError)
},
b => match cursor.write(&[b]) {
Ok(0) | Err(_) => {
for _ in 0u8..128 {
match byte!(stream) {
CR => match byte!(stream) {
LF => break 'read,
_ => return Err(HttpStatusError)
},
_ => { /* ignore */ }
}
}
return Err(HttpStatusError)
}
Ok(_) => (),
}
}
}
}
let reason = match str::from_utf8(cursor.into_inner()) {
Ok(s) => s.trim(),
Err(_) => return Err(HttpStatusError)
};
let reason = match from_u16::<StatusCode>(code) {
Some(status) => match status.canonical_reason() {
Some(phrase) => {
if phrase == reason {
Borrowed(phrase)
} else {
Owned(reason.to_string())
}
}
_ => Owned(reason.to_string())
},
None => return Err(HttpStatusError)
};
Ok(RawStatus(code, reason))
}
#[inline]
fn expect(actual: u8, expected: u8) -> HttpResult<()> {
if actual == expected {
Ok(())
} else {
Err(HttpVersionError)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::io::{self, Write}; use std::io::{self, Write};
use std::borrow::Cow::{Borrowed, Owned};
use test::Bencher;
use uri::RequestUri;
use uri::RequestUri::{Star, AbsoluteUri, AbsolutePath, Authority};
use method;
use version::HttpVersion;
use version::HttpVersion::{Http10, Http11};
use HttpError::{HttpVersionError, HttpMethodError};
use HttpResult;
use url::Url;
use super::{read_method, read_uri, read_http_version, read_header, use super::{read_chunk_size};
RawHeaderLine, read_status, RawStatus, read_chunk_size};
#[test]
fn test_read_method() {
fn read(s: &str, result: HttpResult<method::Method>) {
assert_eq!(read_method(&mut s.as_bytes()), result);
}
read("GET /", Ok(method::Method::Get));
read("POST /", Ok(method::Method::Post));
read("PUT /", Ok(method::Method::Put));
read("HEAD /", Ok(method::Method::Head));
read("OPTIONS /", Ok(method::Method::Options));
read("CONNECT /", Ok(method::Method::Connect));
read("TRACE /", Ok(method::Method::Trace));
read("PATCH /", Ok(method::Method::Patch));
read("FOO /", Ok(method::Method::Extension("FOO".to_string())));
read("akemi!~#HOMURA /", Ok(method::Method::Extension("akemi!~#HOMURA".to_string())));
read(" ", Err(HttpMethodError));
}
#[test]
fn test_read_uri() {
fn read(s: &str, result: HttpResult<RequestUri>) {
assert_eq!(read_uri(&mut s.as_bytes()), result);
}
read("* ", Ok(Star));
read("http://hyper.rs/ ", Ok(AbsoluteUri(Url::parse("http://hyper.rs/").unwrap())));
read("hyper.rs ", Ok(Authority("hyper.rs".to_string())));
read("/ ", Ok(AbsolutePath("/".to_string())));
}
#[test]
fn test_read_http_version() {
fn read(s: &str, result: HttpResult<HttpVersion>) {
assert_eq!(read_http_version(&mut s.as_bytes()), result);
}
read("HTTP/1.0", Ok(Http10));
read("HTTP/1.1", Ok(Http11));
read("HTP/2.0", Err(HttpVersionError));
read("HTTP.2.0", Err(HttpVersionError));
read("HTTP 2.0", Err(HttpVersionError));
read("TTP 2.0", Err(HttpVersionError));
}
#[test]
fn test_read_status() {
fn read(s: &str, result: HttpResult<RawStatus>) {
assert_eq!(read_status(&mut s.as_bytes()), result);
}
fn read_ignore_string(s: &str, result: HttpResult<RawStatus>) {
match (read_status(&mut s.as_bytes()), result) {
(Ok(RawStatus(ref c1, _)), Ok(RawStatus(ref c2, _))) => {
assert_eq!(c1, c2);
},
(r1, r2) => assert_eq!(r1, r2)
}
}
read("200 OK\r\n", Ok(RawStatus(200, Borrowed("OK"))));
read("404 Not Found\r\n", Ok(RawStatus(404, Borrowed("Not Found"))));
read("200 crazy pants\r\n", Ok(RawStatus(200, Owned("crazy pants".to_string()))));
read("301 Moved Permanently\r\n", Ok(RawStatus(301, Owned("Moved Permanently".to_string()))));
read_ignore_string("301 Unreasonably long header that should not happen, \
but some men just want to watch the world burn\r\n",
Ok(RawStatus(301, Owned("Ignored".to_string()))));
}
#[test]
fn test_read_header() {
fn read(s: &str, result: HttpResult<Option<RawHeaderLine>>) {
assert_eq!(read_header(&mut s.as_bytes()), result);
}
read("Host: rust-lang.org\r\n", Ok(Some(("Host".to_string(),
b"rust-lang.org".to_vec()))));
}
#[test] #[test]
fn test_write_chunked() { fn test_write_chunked() {
@@ -934,16 +456,19 @@ mod tests {
read_err("1;no CRLF"); read_err("1;no CRLF");
} }
#[bench] use test::Bencher;
fn bench_read_method(b: &mut Bencher) {
b.bytes = b"CONNECT ".len() as u64;
b.iter(|| assert_eq!(read_method(&mut b"CONNECT "), Ok(method::Method::Connect)));
}
#[bench] #[bench]
fn bench_read_status(b: &mut Bencher) { fn bench_parse_incoming(b: &mut Bencher) {
b.bytes = b"404 Not Found\r\n".len() as u64; use std::io::BufReader;
b.iter(|| assert_eq!(read_status(&mut b"404 Not Found\r\n"), Ok(RawStatus(404, Borrowed("Not Found"))))); use mock::MockStream;
use net::NetworkStream;
use super::parse_request;
b.iter(|| {
let mut raw = MockStream::with_input(b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n");
let mut buf = BufReader::new(&mut raw as &mut NetworkStream);
parse_request(&mut buf).unwrap();
});
} }
} }

View File

@@ -1,6 +1,6 @@
#![feature(core, collections, io, net, os, path, #![feature(core, collections, io, net, os, path,
std_misc, box_syntax, unsafe_destructor)] std_misc, box_syntax, unsafe_destructor)]
#![cfg_attr(test, deny(missing_docs))] #![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))] #![cfg_attr(test, deny(warnings))]
#![cfg_attr(test, feature(alloc, test))] #![cfg_attr(test, feature(alloc, test))]
@@ -132,6 +132,7 @@ extern crate url;
extern crate openssl; extern crate openssl;
extern crate cookie; extern crate cookie;
extern crate unicase; extern crate unicase;
extern crate httparse;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
@@ -143,17 +144,11 @@ extern crate test;
pub use mimewrapper::mime; pub use mimewrapper::mime;
pub use url::Url; pub use url::Url;
pub use client::Client; pub use client::Client;
pub use error::{HttpResult, HttpError};
pub use method::Method::{Get, Head, Post, Delete}; pub use method::Method::{Get, Head, Post, Delete};
pub use status::StatusCode::{Ok, BadRequest, NotFound}; pub use status::StatusCode::{Ok, BadRequest, NotFound};
pub use server::Server; pub use server::Server;
use std::error::{Error, FromError};
use std::fmt;
use std::io::Error as IoError;
use self::HttpError::{HttpMethodError, HttpUriError, HttpVersionError,
HttpHeaderError, HttpStatusError, HttpIoError};
macro_rules! todo( macro_rules! todo(
($($arg:tt)*) => (if cfg!(not(ndebug)) { ($($arg:tt)*) => (if cfg!(not(ndebug)) {
trace!("TODO: {:?}", format_args!($($arg)*)) trace!("TODO: {:?}", format_args!($($arg)*))
@@ -173,6 +168,7 @@ macro_rules! inspect(
mod mock; mod mock;
pub mod client; pub mod client;
pub mod error;
pub mod method; pub mod method;
pub mod header; pub mod header;
pub mod http; pub mod http;
@@ -188,66 +184,6 @@ mod mimewrapper {
extern crate mime; extern crate mime;
} }
/// 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,
/// 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",
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)
}
}
#[allow(unconditional_recursion)] #[allow(unconditional_recursion)]
fn _assert_send<T: Send>() { fn _assert_send<T: Send>() {
_assert_send::<client::Request<net::Fresh>>(); _assert_send::<client::Request<net::Fresh>>();

View File

@@ -2,6 +2,7 @@
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use error::HttpError;
use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch, use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch,
Extension}; Extension};
@@ -68,10 +69,10 @@ impl Method {
} }
impl FromStr for Method { impl FromStr for Method {
type Err = (); type Err = HttpError;
fn from_str(s: &str) -> Result<Method, ()> { fn from_str(s: &str) -> Result<Method, HttpError> {
if s == "" { if s == "" {
Err(()) Err(HttpError::HttpMethodError)
} else { } else {
Ok(match s { Ok(match s {
"OPTIONS" => Options, "OPTIONS" => Options,

View File

@@ -66,7 +66,6 @@ pub trait NetworkStream: Read + Write + Any + StreamClone + Send {
fn peer_addr(&mut self) -> io::Result<SocketAddr>; fn peer_addr(&mut self) -> io::Result<SocketAddr>;
} }
#[doc(hidden)] #[doc(hidden)]
pub trait StreamClone { pub trait StreamClone {
fn clone_box(&self) -> Box<NetworkStream + Send>; fn clone_box(&self) -> Box<NetworkStream + Send>;

View File

@@ -45,6 +45,7 @@ macro_rules! try_option(
); );
impl<'a, H: Handler, L: NetworkListener> Server<'a, H, L> { impl<'a, H: Handler, L: NetworkListener> Server<'a, H, L> {
/// Creates a new server with the provided handler.
pub fn new(handler: H) -> Server<'a, H, L> { pub fn new(handler: H) -> Server<'a, H, L> {
Server { Server {
handler: handler, handler: handler,
@@ -97,7 +98,7 @@ S: NetworkStream + Clone + Send> Server<'a, H, L> {
debug!("threads = {:?}", threads); debug!("threads = {:?}", threads);
let pool = ListenerPool::new(listener.clone()); let pool = ListenerPool::new(listener.clone());
let work = move |stream| handle_connection(stream, &handler); let work = move |stream| keep_alive_loop(stream, &handler);
let guard = thread::scoped(move || pool.accept(work, threads)); let guard = thread::scoped(move || pool.accept(work, threads));
@@ -109,7 +110,7 @@ S: NetworkStream + Clone + Send> Server<'a, H, L> {
} }
fn handle_connection<S, H>(mut stream: S, handler: &H) fn keep_alive_loop<'h, S, H>(mut stream: S, handler: &'h H)
where S: NetworkStream + Clone, H: Handler { where S: NetworkStream + Clone, H: Handler {
debug!("Incoming stream"); debug!("Incoming stream");
let addr = match stream.peer_addr() { let addr = match stream.peer_addr() {
@@ -120,36 +121,47 @@ where S: NetworkStream + Clone, H: Handler {
} }
}; };
let mut rdr = BufReader::new(stream.clone()); let mut stream_clone = stream.clone();
let mut rdr = BufReader::new(&mut stream_clone as &mut NetworkStream);
let mut wrt = BufWriter::new(stream); let mut wrt = BufWriter::new(stream);
let mut keep_alive = true; let mut keep_alive = true;
while keep_alive { while keep_alive {
let mut res = Response::new(&mut wrt); keep_alive = handle_connection(addr, &mut rdr, &mut wrt, handler);
let req = match Request::new(&mut rdr, addr) {
Ok(req) => req,
Err(e@HttpIoError(_)) => {
debug!("ioerror in keepalive loop = {:?}", e);
return;
}
Err(e) => {
//TODO: send a 400 response
error!("request error = {:?}", e);
return;
}
};
keep_alive = match (req.version, req.headers.get::<Connection>()) {
(Http10, Some(conn)) if !conn.contains(&KeepAlive) => false,
(Http11, Some(conn)) if conn.contains(&Close) => false,
_ => true
};
res.version = req.version;
handler.handle(req, res);
debug!("keep_alive = {:?}", keep_alive); debug!("keep_alive = {:?}", keep_alive);
} }
} }
fn handle_connection<'a, 'aa, 'h, S, H>(
addr: SocketAddr,
rdr: &'a mut BufReader<&'aa mut NetworkStream>,
wrt: &mut BufWriter<S>,
handler: &'h H
) -> bool where 'aa: 'a, S: NetworkStream, H: Handler {
let mut res = Response::new(wrt);
let req = match Request::<'a, 'aa>::new(rdr, addr) {
Ok(req) => req,
Err(e@HttpIoError(_)) => {
debug!("ioerror in keepalive loop = {:?}", e);
return false;
}
Err(e) => {
//TODO: send a 400 response
error!("request error = {:?}", e);
return false;
}
};
let keep_alive = match (req.version, req.headers.get::<Connection>()) {
(Http10, Some(conn)) if !conn.contains(&KeepAlive) => false,
(Http11, Some(conn)) if conn.contains(&Close) => false,
_ => true
};
res.version = req.version;
handler.handle(req, res);
keep_alive
}
/// A listening server, which can later be closed. /// A listening server, which can later be closed.
pub struct Listening { pub struct Listening {
_guard: JoinGuard<'static, ()>, _guard: JoinGuard<'static, ()>,
@@ -171,11 +183,11 @@ pub trait Handler: Sync + Send {
/// Receives a `Request`/`Response` pair, and should perform some action on them. /// Receives a `Request`/`Response` pair, and should perform some action on them.
/// ///
/// This could reading from the request, and writing to the response. /// This could reading from the request, and writing to the response.
fn handle<'a>(&'a self, Request<'a>, Response<'a, Fresh>); fn handle<'a, 'aa, 'b, 's>(&'s self, Request<'aa, 'a>, Response<'b, Fresh>);
} }
impl<F> Handler for F where F: Fn(Request, Response<Fresh>), F: Sync + Send { impl<F> Handler for F where F: Fn(Request, Response<Fresh>), F: Sync + Send {
fn handle(&self, req: Request, res: Response<Fresh>) { fn handle<'a, 'aa, 'b, 's>(&'s self, req: Request<'a, 'aa>, res: Response<'b, Fresh>) {
(*self)(req, res) self(req, res)
} }
} }

View File

@@ -2,20 +2,20 @@
//! //!
//! These are requests that a `hyper::Server` receives, and include its method, //! These are requests that a `hyper::Server` receives, and include its method,
//! target URI, headers, and message body. //! target URI, headers, and message body.
use std::io::{self, Read}; use std::io::{self, Read, BufReader};
use std::net::SocketAddr; use std::net::SocketAddr;
use {HttpResult}; use {HttpResult};
use net::NetworkStream;
use version::{HttpVersion}; use version::{HttpVersion};
use method::Method::{self, Get, Head}; use method::Method::{self, Get, Head};
use header::{Headers, ContentLength, TransferEncoding}; use header::{Headers, ContentLength, TransferEncoding};
use http::{read_request_line}; use http::{self, Incoming, HttpReader};
use http::HttpReader;
use http::HttpReader::{SizedReader, ChunkedReader, EmptyReader}; use http::HttpReader::{SizedReader, ChunkedReader, EmptyReader};
use uri::RequestUri; use uri::RequestUri;
/// A request bundles several parts of an incoming `NetworkStream`, given to a `Handler`. /// A request bundles several parts of an incoming `NetworkStream`, given to a `Handler`.
pub struct Request<'a> { pub struct Request<'a, 'b: 'a> {
/// The IP address of the remote connection. /// The IP address of the remote connection.
pub remote_addr: SocketAddr, pub remote_addr: SocketAddr,
/// The `Method`, such as `Get`, `Post`, etc. /// The `Method`, such as `Get`, `Post`, etc.
@@ -26,17 +26,18 @@ pub struct Request<'a> {
pub uri: RequestUri, pub uri: RequestUri,
/// The version of HTTP for this request. /// The version of HTTP for this request.
pub version: HttpVersion, pub version: HttpVersion,
body: HttpReader<&'a mut (Read + 'a)> body: HttpReader<&'a mut BufReader<&'b mut NetworkStream>>
} }
impl<'a> Request<'a> { impl<'a, 'b: 'a> Request<'a, 'b> {
/// Create a new Request, reading the StartLine and Headers so they are /// Create a new Request, reading the StartLine and Headers so they are
/// immediately useful. /// immediately useful.
pub fn new(mut stream: &'a mut (Read + 'a), addr: SocketAddr) -> HttpResult<Request<'a>> { pub fn new(mut stream: &'a mut BufReader<&'b mut NetworkStream>, addr: SocketAddr)
let (method, uri, version) = try!(read_request_line(&mut stream)); -> HttpResult<Request<'a, 'b>> {
let Incoming { version, subject: (method, uri), headers } = try!(http::parse_request(stream));
debug!("Request Line: {:?} {:?} {:?}", method, uri, version); debug!("Request Line: {:?} {:?} {:?}", method, uri, version);
let headers = try!(Headers::from_raw(&mut stream));
debug!("{:?}", headers); debug!("{:?}", headers);
let body = if method == Get || method == Head { let body = if method == Get || method == Head {
@@ -66,13 +67,13 @@ impl<'a> Request<'a> {
/// Deconstruct a Request into its constituent parts. /// Deconstruct a Request into its constituent parts.
pub fn deconstruct(self) -> (SocketAddr, Method, Headers, pub fn deconstruct(self) -> (SocketAddr, Method, Headers,
RequestUri, HttpVersion, RequestUri, HttpVersion,
HttpReader<&'a mut (Read + 'a)>,) { HttpReader<&'a mut BufReader<&'b mut NetworkStream>>) {
(self.remote_addr, self.method, self.headers, (self.remote_addr, self.method, self.headers,
self.uri, self.version, self.body) self.uri, self.version, self.body)
} }
} }
impl<'a> Read for Request<'a> { impl<'a, 'b> Read for Request<'a, 'b> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.body.read(buf) self.body.read(buf)
} }
@@ -81,10 +82,11 @@ impl<'a> Read for Request<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use header::{Host, TransferEncoding, Encoding}; use header::{Host, TransferEncoding, Encoding};
use net::NetworkStream;
use mock::MockStream; use mock::MockStream;
use super::Request; use super::Request;
use std::io::{self, Read}; use std::io::{self, Read, BufReader};
use std::net::SocketAddr; use std::net::SocketAddr;
fn sock(s: &str) -> SocketAddr { fn sock(s: &str) -> SocketAddr {
@@ -99,25 +101,28 @@ mod tests {
#[test] #[test]
fn test_get_empty_body() { fn test_get_empty_body() {
let mut stream = MockStream::with_input(b"\ let mut mock = MockStream::with_input(b"\
GET / HTTP/1.1\r\n\ GET / HTTP/1.1\r\n\
Host: example.domain\r\n\ Host: example.domain\r\n\
\r\n\ \r\n\
I'm a bad request.\r\n\ I'm a bad request.\r\n\
"); ");
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
assert_eq!(read_to_string(req), Ok("".to_string())); assert_eq!(read_to_string(req), Ok("".to_string()));
} }
#[test] #[test]
fn test_head_empty_body() { fn test_head_empty_body() {
let mut stream = MockStream::with_input(b"\ let mut mock = MockStream::with_input(b"\
HEAD / HTTP/1.1\r\n\ HEAD / HTTP/1.1\r\n\
Host: example.domain\r\n\ Host: example.domain\r\n\
\r\n\ \r\n\
I'm a bad request.\r\n\ I'm a bad request.\r\n\
"); ");
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
assert_eq!(read_to_string(req), Ok("".to_string())); assert_eq!(read_to_string(req), Ok("".to_string()));
@@ -125,12 +130,13 @@ mod tests {
#[test] #[test]
fn test_post_empty_body() { fn test_post_empty_body() {
let mut stream = MockStream::with_input(b"\ let mut mock = MockStream::with_input(b"\
POST / HTTP/1.1\r\n\ POST / HTTP/1.1\r\n\
Host: example.domain\r\n\ Host: example.domain\r\n\
\r\n\ \r\n\
I'm a bad request.\r\n\ I'm a bad request.\r\n\
"); ");
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
assert_eq!(read_to_string(req), Ok("".to_string())); assert_eq!(read_to_string(req), Ok("".to_string()));
@@ -138,7 +144,7 @@ mod tests {
#[test] #[test]
fn test_parse_chunked_request() { fn test_parse_chunked_request() {
let mut stream = MockStream::with_input(b"\ let mut mock = MockStream::with_input(b"\
POST / HTTP/1.1\r\n\ POST / HTTP/1.1\r\n\
Host: example.domain\r\n\ Host: example.domain\r\n\
Transfer-Encoding: chunked\r\n\ Transfer-Encoding: chunked\r\n\
@@ -152,6 +158,7 @@ mod tests {
0\r\n\ 0\r\n\
\r\n" \r\n"
); );
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
@@ -177,7 +184,7 @@ mod tests {
/// is returned. /// is returned.
#[test] #[test]
fn test_invalid_chunk_size_not_hex_digit() { fn test_invalid_chunk_size_not_hex_digit() {
let mut stream = MockStream::with_input(b"\ let mut mock = MockStream::with_input(b"\
POST / HTTP/1.1\r\n\ POST / HTTP/1.1\r\n\
Host: example.domain\r\n\ Host: example.domain\r\n\
Transfer-Encoding: chunked\r\n\ Transfer-Encoding: chunked\r\n\
@@ -187,6 +194,7 @@ mod tests {
0\r\n\ 0\r\n\
\r\n" \r\n"
); );
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
@@ -197,7 +205,7 @@ mod tests {
/// returned. /// returned.
#[test] #[test]
fn test_invalid_chunk_size_extension() { fn test_invalid_chunk_size_extension() {
let mut stream = MockStream::with_input(b"\ let mut mock = MockStream::with_input(b"\
POST / HTTP/1.1\r\n\ POST / HTTP/1.1\r\n\
Host: example.domain\r\n\ Host: example.domain\r\n\
Transfer-Encoding: chunked\r\n\ Transfer-Encoding: chunked\r\n\
@@ -207,6 +215,7 @@ mod tests {
0\r\n\ 0\r\n\
\r\n" \r\n"
); );
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
@@ -217,7 +226,7 @@ mod tests {
/// the chunk size, the chunk is correctly read. /// the chunk size, the chunk is correctly read.
#[test] #[test]
fn test_chunk_size_with_extension() { fn test_chunk_size_with_extension() {
let mut stream = MockStream::with_input(b"\ let mut mock = MockStream::with_input(b"\
POST / HTTP/1.1\r\n\ POST / HTTP/1.1\r\n\
Host: example.domain\r\n\ Host: example.domain\r\n\
Transfer-Encoding: chunked\r\n\ Transfer-Encoding: chunked\r\n\
@@ -227,6 +236,7 @@ mod tests {
0\r\n\ 0\r\n\
\r\n" \r\n"
); );
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();

View File

@@ -1,5 +1,9 @@
//! HTTP RequestUris //! HTTP RequestUris
use std::str::FromStr;
use url::Url; use url::Url;
use url::ParseError as UrlError;
use error::HttpError;
/// The Request-URI of a Request's StartLine. /// The Request-URI of a Request's StartLine.
/// ///
@@ -45,3 +49,40 @@ pub enum RequestUri {
Star, Star,
} }
impl FromStr for RequestUri {
type Err = HttpError;
fn from_str(s: &str) -> Result<RequestUri, HttpError> {
match s.as_bytes() {
[] => Err(HttpError::HttpUriError(UrlError::InvalidCharacter)),
[b'*'] => Ok(RequestUri::Star),
[b'/', ..] => Ok(RequestUri::AbsolutePath(s.to_string())),
bytes if bytes.contains(&b'/') => {
Ok(RequestUri::AbsoluteUri(try!(Url::parse(s))))
}
_ => {
let mut temp = "http://".to_string();
temp.push_str(s);
try!(Url::parse(&temp[..]));
todo!("compare vs u.authority()");
Ok(RequestUri::Authority(s.to_string()))
}
}
}
}
#[test]
fn test_uri_fromstr() {
use error::HttpResult;
fn read(s: &str, result: HttpResult<RequestUri>) {
assert_eq!(s.parse(), result);
}
read("*", Ok(RequestUri::Star));
read("http://hyper.rs/", Ok(RequestUri::AbsoluteUri(Url::parse("http://hyper.rs/").unwrap())));
read("hyper.rs", Ok(RequestUri::Authority("hyper.rs".to_string())));
read("/", Ok(RequestUri::AbsolutePath("/".to_string())));
}