Merge pull request #370 from hyperium/httparse
perf(http): changes http parsing to use httparse crate
This commit is contained in:
		| @@ -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 = "*" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 => { | ||||||
|   | |||||||
| @@ -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"); | ||||||
|   | |||||||
| @@ -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"); | ||||||
|   | |||||||
| @@ -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
									
								
							
							
						
						
									
										88
									
								
								src/error.rs
									
									
									
									
									
										Normal 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, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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())); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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)) => { |  | ||||||
|                     debug!("raw header: {:?}={:?}", name, &value[..]); |  | ||||||
|                     count += (name.len() + value.len()) as u32; |  | ||||||
|                     if count > MAX_HEADERS_LENGTH { |  | ||||||
|                         debug!("Max header size reached, aborting"); |  | ||||||
|                         return Err(HttpError::HttpHeaderError) |  | ||||||
|                     } |  | ||||||
|                     let name = UniCase(Owned(name)); |  | ||||||
|             let mut item = match headers.data.entry(name) { |             let mut item = match headers.data.entry(name) { | ||||||
|                 Entry::Vacant(entry) => entry.insert(Item::new_raw(vec![])), |                 Entry::Vacant(entry) => entry.insert(Item::new_raw(vec![])), | ||||||
|                 Entry::Occupied(entry) => entry.into_mut() |                 Entry::Occupied(entry) => entry.into_mut() | ||||||
|             }; |             }; | ||||||
|                     item.mut_raw().push(value); |             let trim = header.value.iter().rev().take_while(|&&x| x == b' ').count(); | ||||||
|                 }, |             let value = &header.value[.. header.value.len() - trim]; | ||||||
|                 None => break, |             item.mut_raw().push(value.to_vec()); | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|         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] | ||||||
|   | |||||||
							
								
								
									
										641
									
								
								src/http.rs
									
									
									
									
									
								
							
							
						
						
									
										641
									
								
								src/http.rs
									
									
									
									
									
								
							| @@ -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(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								src/lib.rs
									
									
									
									
									
								
							| @@ -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>>(); | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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>; | ||||||
|   | |||||||
| @@ -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,34 +121,45 @@ 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) { |         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, |         Ok(req) => req, | ||||||
|         Err(e@HttpIoError(_)) => { |         Err(e@HttpIoError(_)) => { | ||||||
|             debug!("ioerror in keepalive loop = {:?}", e); |             debug!("ioerror in keepalive loop = {:?}", e); | ||||||
|                 return; |             return false; | ||||||
|         } |         } | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             //TODO: send a 400 response |             //TODO: send a 400 response | ||||||
|             error!("request error = {:?}", e); |             error!("request error = {:?}", e); | ||||||
|                 return; |             return false; | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|         keep_alive = match (req.version, req.headers.get::<Connection>()) { |     let keep_alive = match (req.version, req.headers.get::<Connection>()) { | ||||||
|         (Http10, Some(conn)) if !conn.contains(&KeepAlive) => false, |         (Http10, Some(conn)) if !conn.contains(&KeepAlive) => false, | ||||||
|         (Http11, Some(conn)) if conn.contains(&Close)  => false, |         (Http11, Some(conn)) if conn.contains(&Close)  => false, | ||||||
|         _ => true |         _ => true | ||||||
|     }; |     }; | ||||||
|     res.version = req.version; |     res.version = req.version; | ||||||
|     handler.handle(req, res); |     handler.handle(req, res); | ||||||
|         debug!("keep_alive = {:?}", keep_alive); |     keep_alive | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /// A listening server, which can later be closed. | /// A listening server, which can later be closed. | ||||||
| @@ -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) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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(); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								src/uri.rs
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								src/uri.rs
									
									
									
									
									
								
							| @@ -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()))); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user