Merge pull request #370 from hyperium/httparse
perf(http): changes http parsing to use httparse crate
This commit is contained in:
		| @@ -7,7 +7,7 @@ use header; | ||||
| use header::{ContentLength, TransferEncoding}; | ||||
| use header::Encoding::Chunked; | ||||
| use net::{NetworkStream, HttpStream}; | ||||
| use http::{read_status_line, HttpReader, RawStatus}; | ||||
| use http::{self, HttpReader, RawStatus}; | ||||
| use http::HttpReader::{SizedReader, ChunkedReader, EofReader}; | ||||
| use status; | ||||
| use version; | ||||
| @@ -36,15 +36,17 @@ impl Response { | ||||
|     /// Creates a new response from a server. | ||||
|     pub fn new(stream: Box<NetworkStream + Send>) -> HttpResult<Response> { | ||||
|         let mut stream = BufReader::new(stream); | ||||
|         let (version, raw_status) = try!(read_status_line(&mut stream)); | ||||
|  | ||||
|         let head = try!(http::parse_response(&mut stream)); | ||||
|         let raw_status = head.subject; | ||||
|         let headers = head.headers; | ||||
|  | ||||
|         let status = match FromPrimitive::from_u16(raw_status.0) { | ||||
|             Some(status) => status, | ||||
|             None => return Err(HttpStatusError) | ||||
|         }; | ||||
|         debug!("{:?} {:?}", version, status); | ||||
|  | ||||
|         let headers = try!(header::Headers::from_raw(&mut stream)); | ||||
|         debug!("Headers: [\n{:?}]", headers); | ||||
|         debug!("version={:?}, status={:?}", head.version, status); | ||||
|         debug!("headers={:?}", headers); | ||||
|  | ||||
|         let body = if headers.has::<TransferEncoding>() { | ||||
|             match headers.get::<TransferEncoding>() { | ||||
| @@ -74,7 +76,7 @@ impl Response { | ||||
|  | ||||
|         Ok(Response { | ||||
|             status: status, | ||||
|             version: version, | ||||
|             version: head.version, | ||||
|             headers: headers, | ||||
|             body: body, | ||||
|             status_raw: raw_status, | ||||
|   | ||||
							
								
								
									
										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>)) { | ||||
|                 (Ok(header), Some(scheme)) | ||||
|                     if header.starts_with(scheme) && header.len() > scheme.len() + 1 => { | ||||
|                     header[scheme.len() + 1..].parse::<S>().map(|s| Authorization(s)).ok() | ||||
|                     header[scheme.len() + 1..].parse::<S>().map(Authorization).ok() | ||||
|                 }, | ||||
|                 (Ok(header), None) => header.parse::<S>().map(|s| Authorization(s)).ok(), | ||||
|                 (Ok(header), None) => header.parse::<S>().map(Authorization).ok(), | ||||
|                 _ => None | ||||
|             } | ||||
|         } else { | ||||
| @@ -143,7 +143,7 @@ impl FromStr for Basic { | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::{Authorization, Basic}; | ||||
|     use super::super::super::{Headers}; | ||||
|     use super::super::super::{Headers, Header}; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_raw_auth() { | ||||
| @@ -154,8 +154,8 @@ mod tests { | ||||
|  | ||||
|     #[test] | ||||
|     fn test_raw_auth_parse() { | ||||
|         let headers = Headers::from_raw(&mut b"Authorization: foo bar baz\r\n\r\n").unwrap(); | ||||
|         assert_eq!(&headers.get::<Authorization<String>>().unwrap().0[..], "foo bar baz"); | ||||
|         let header: Authorization<String> = Header::parse_header(&[b"foo bar baz".to_vec()]).unwrap(); | ||||
|         assert_eq!(header.0, "foo bar baz"); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
| @@ -174,17 +174,15 @@ mod tests { | ||||
|  | ||||
|     #[test] | ||||
|     fn test_basic_auth_parse() { | ||||
|         let headers = Headers::from_raw(&mut b"Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\r\n").unwrap(); | ||||
|         let auth = headers.get::<Authorization<Basic>>().unwrap(); | ||||
|         assert_eq!(&auth.0.username[..], "Aladdin"); | ||||
|         let auth: Authorization<Basic> = Header::parse_header(&[b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".to_vec()]).unwrap(); | ||||
|         assert_eq!(auth.0.username, "Aladdin"); | ||||
|         assert_eq!(auth.0.password, Some("open sesame".to_string())); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_basic_auth_parse_no_password() { | ||||
|         let headers = Headers::from_raw(&mut b"Authorization: Basic QWxhZGRpbjo=\r\n\r\n").unwrap(); | ||||
|         let auth = headers.get::<Authorization<Basic>>().unwrap(); | ||||
|         assert_eq!(auth.0.username.as_slice(), "Aladdin"); | ||||
|         let auth: Authorization<Basic> = Header::parse_header(&[b"Basic QWxhZGRpbjo=".to_vec()]).unwrap(); | ||||
|         assert_eq!(auth.0.username, "Aladdin"); | ||||
|         assert_eq!(auth.0.password, Some("".to_string())); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -5,9 +5,9 @@ | ||||
| //! must implement the `Header` trait from this module. Several common headers | ||||
| //! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others. | ||||
| use std::any::Any; | ||||
| use std::borrow::Cow::{Borrowed, Owned}; | ||||
| use std::borrow::Cow::{Borrowed}; | ||||
| use std::borrow::ToOwned; | ||||
| use std::fmt; | ||||
| use std::io::Read; | ||||
| use std::raw::TraitObject; | ||||
| use std::collections::HashMap; | ||||
| use std::collections::hash_map::{Iter, Entry}; | ||||
| @@ -15,10 +15,11 @@ use std::iter::{FromIterator, IntoIterator}; | ||||
| use std::borrow::{Cow, IntoCow}; | ||||
| use std::{mem, raw}; | ||||
|  | ||||
| use httparse; | ||||
| use unicase::UniCase; | ||||
|  | ||||
| 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::common::*; | ||||
| @@ -105,10 +106,6 @@ pub struct Headers { | ||||
|     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 { | ||||
|  | ||||
|     /// Creates a new, empty headers map. | ||||
| @@ -119,27 +116,18 @@ impl Headers { | ||||
|     } | ||||
|  | ||||
|     #[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 count = 0u32; | ||||
|         loop { | ||||
|             match try!(http::read_header(rdr)) { | ||||
|                 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) { | ||||
|                         Entry::Vacant(entry) => entry.insert(Item::new_raw(vec![])), | ||||
|                         Entry::Occupied(entry) => entry.into_mut() | ||||
|                     }; | ||||
|                     item.mut_raw().push(value); | ||||
|                 }, | ||||
|                 None => break, | ||||
|             } | ||||
|         for header in raw { | ||||
|             debug!("raw header: {:?}={:?}", header.name, &header.value[..]); | ||||
|             let name = UniCase(header.name.to_owned().into_cow()); | ||||
|             let mut item = match headers.data.entry(name) { | ||||
|                 Entry::Vacant(entry) => entry.insert(Item::new_raw(vec![])), | ||||
|                 Entry::Occupied(entry) => entry.into_mut() | ||||
|             }; | ||||
|             let trim = header.value.iter().rev().take_while(|&&x| x == b' ').count(); | ||||
|             let value = &header.value[.. header.value.len() - trim]; | ||||
|             item.mut_raw().push(value.to_vec()); | ||||
|         } | ||||
|         Ok(headers) | ||||
|     } | ||||
| @@ -364,12 +352,26 @@ mod tests { | ||||
|     use mime::SubLevel::Plain; | ||||
|     use super::{Headers, Header, HeaderFormat, ContentLength, ContentType, | ||||
|                 Accept, Host, qitem}; | ||||
|     use httparse; | ||||
|  | ||||
|     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] | ||||
|     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))); | ||||
|     } | ||||
|  | ||||
| @@ -422,20 +424,20 @@ mod tests { | ||||
|  | ||||
|     #[test] | ||||
|     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::<CrazyLength>(), Some(&CrazyLength(Some(false), 10))); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     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))); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     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(two) = *headers.get::<ContentLength>().unwrap(); | ||||
|         assert_eq!(one, two); | ||||
| @@ -443,14 +445,14 @@ mod tests { | ||||
|  | ||||
|     #[test] | ||||
|     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 ContentType(_) = *headers.get::<ContentType>().unwrap(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     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); | ||||
|         assert_eq!(*headers.get::<ContentLength>().unwrap(), ContentLength(20)); | ||||
|     } | ||||
| @@ -471,7 +473,7 @@ mod tests { | ||||
|  | ||||
|     #[test] | ||||
|     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(); | ||||
|         assert_eq!(s, "Content-Length: 10\r\n"); | ||||
|     } | ||||
| @@ -538,7 +540,8 @@ mod tests { | ||||
|  | ||||
|     #[bench] | ||||
|     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] | ||||
|   | ||||
							
								
								
									
										641
									
								
								src/http.rs
									
									
									
									
									
								
							
							
						
						
									
										641
									
								
								src/http.rs
									
									
									
									
									
								
							| @@ -1,22 +1,15 @@ | ||||
| //! Pieces pertaining to the HTTP message protocol. | ||||
| use std::borrow::Cow::{self, Borrowed, Owned}; | ||||
| use std::borrow::IntoCow; | ||||
| use std::borrow::{Cow, IntoCow, ToOwned}; | ||||
| use std::cmp::min; | ||||
| use std::io::{self, Read, Write, Cursor}; | ||||
| use std::num::from_u16; | ||||
| use std::str; | ||||
| use std::io::{self, Read, Write, BufRead}; | ||||
|  | ||||
| use url::Url; | ||||
| use url::ParseError as UrlError; | ||||
| use httparse; | ||||
|  | ||||
| use method; | ||||
| use status::StatusCode; | ||||
| use uri; | ||||
| use uri::RequestUri::{AbsolutePath, AbsoluteUri, Authority, Star}; | ||||
| use version::HttpVersion; | ||||
| use version::HttpVersion::{Http09, Http10, Http11}; | ||||
| use HttpError::{HttpHeaderError, HttpMethodError, HttpStatusError, | ||||
|                 HttpUriError, HttpVersionError}; | ||||
| use header::Headers; | ||||
| use method::Method; | ||||
| use uri::RequestUri; | ||||
| use version::HttpVersion::{self, Http10, Http11}; | ||||
| use HttpError:: HttpTooLargeError; | ||||
| use HttpResult; | ||||
|  | ||||
| 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 CR: u8 = b'\r'; | ||||
| pub const LF: u8 = b'\n'; | ||||
| pub const STAR: u8 = b'*'; | ||||
| 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. | ||||
| #[derive(PartialEq, Debug)] | ||||
| 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)] | ||||
| mod tests { | ||||
|     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, | ||||
|                 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); | ||||
|         } | ||||
|     use super::{read_chunk_size}; | ||||
|  | ||||
|         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] | ||||
|     fn test_write_chunked() { | ||||
| @@ -934,16 +456,19 @@ mod tests { | ||||
|         read_err("1;no CRLF"); | ||||
|     } | ||||
|  | ||||
|     #[bench] | ||||
|     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))); | ||||
|     } | ||||
|     use test::Bencher; | ||||
|  | ||||
|     #[bench] | ||||
|     fn bench_read_status(b: &mut Bencher) { | ||||
|         b.bytes = b"404 Not Found\r\n".len() as u64; | ||||
|         b.iter(|| assert_eq!(read_status(&mut b"404 Not Found\r\n"), Ok(RawStatus(404, Borrowed("Not Found"))))); | ||||
|     fn bench_parse_incoming(b: &mut Bencher) { | ||||
|         use std::io::BufReader; | ||||
|         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, | ||||
|            std_misc, box_syntax, unsafe_destructor)] | ||||
| #![cfg_attr(test, deny(missing_docs))] | ||||
| #![deny(missing_docs)] | ||||
| #![cfg_attr(test, deny(warnings))] | ||||
| #![cfg_attr(test, feature(alloc, test))] | ||||
|  | ||||
| @@ -132,6 +132,7 @@ extern crate url; | ||||
| extern crate openssl; | ||||
| extern crate cookie; | ||||
| extern crate unicase; | ||||
| extern crate httparse; | ||||
|  | ||||
| #[macro_use] | ||||
| extern crate log; | ||||
| @@ -143,17 +144,11 @@ extern crate test; | ||||
| pub use mimewrapper::mime; | ||||
| pub use url::Url; | ||||
| pub use client::Client; | ||||
| pub use error::{HttpResult, HttpError}; | ||||
| pub use method::Method::{Get, Head, Post, Delete}; | ||||
| pub use status::StatusCode::{Ok, BadRequest, NotFound}; | ||||
| 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( | ||||
|     ($($arg:tt)*) => (if cfg!(not(ndebug)) { | ||||
|         trace!("TODO: {:?}", format_args!($($arg)*)) | ||||
| @@ -173,6 +168,7 @@ macro_rules! inspect( | ||||
| mod mock; | ||||
|  | ||||
| pub mod client; | ||||
| pub mod error; | ||||
| pub mod method; | ||||
| pub mod header; | ||||
| pub mod http; | ||||
| @@ -188,66 +184,6 @@ mod mimewrapper { | ||||
|     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)] | ||||
| fn _assert_send<T: Send>() { | ||||
|     _assert_send::<client::Request<net::Fresh>>(); | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| use std::fmt; | ||||
| use std::str::FromStr; | ||||
|  | ||||
| use error::HttpError; | ||||
| use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch, | ||||
|                    Extension}; | ||||
|  | ||||
| @@ -68,10 +69,10 @@ impl Method { | ||||
| } | ||||
|  | ||||
| impl FromStr for Method { | ||||
|     type Err = (); | ||||
|     fn from_str(s: &str) -> Result<Method, ()> { | ||||
|     type Err = HttpError; | ||||
|     fn from_str(s: &str) -> Result<Method, HttpError> { | ||||
|         if s == "" { | ||||
|             Err(()) | ||||
|             Err(HttpError::HttpMethodError) | ||||
|         } else { | ||||
|             Ok(match s { | ||||
|                 "OPTIONS" => Options, | ||||
|   | ||||
| @@ -66,7 +66,6 @@ pub trait NetworkStream: Read + Write + Any + StreamClone + Send { | ||||
|     fn peer_addr(&mut self) -> io::Result<SocketAddr>; | ||||
| } | ||||
|  | ||||
|  | ||||
| #[doc(hidden)] | ||||
| pub trait StreamClone { | ||||
|     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> { | ||||
|     /// Creates a new server with the provided handler. | ||||
|     pub fn new(handler: H) -> Server<'a, H, L> { | ||||
|         Server { | ||||
|             handler: handler, | ||||
| @@ -97,7 +98,7 @@ S: NetworkStream + Clone + Send> Server<'a, H, L> { | ||||
|  | ||||
|         debug!("threads = {:?}", threads); | ||||
|         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)); | ||||
|  | ||||
| @@ -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 { | ||||
|     debug!("Incoming stream"); | ||||
|     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 keep_alive = true; | ||||
|     while keep_alive { | ||||
|         let mut res = Response::new(&mut wrt); | ||||
|         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); | ||||
|         keep_alive = handle_connection(addr, &mut rdr, &mut wrt, handler); | ||||
|         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. | ||||
| pub struct Listening { | ||||
|     _guard: JoinGuard<'static, ()>, | ||||
| @@ -171,11 +183,11 @@ pub trait Handler: Sync + Send { | ||||
|     /// Receives a `Request`/`Response` pair, and should perform some action on them. | ||||
|     /// | ||||
|     /// 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 { | ||||
|     fn handle(&self, req: Request, res: Response<Fresh>) { | ||||
|         (*self)(req, res) | ||||
|     fn handle<'a, 'aa, 'b, 's>(&'s self, req: Request<'a, 'aa>, res: Response<'b, Fresh>) { | ||||
|         self(req, res) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,20 +2,20 @@ | ||||
| //! | ||||
| //! These are requests that a `hyper::Server` receives, and include its method, | ||||
| //! target URI, headers, and message body. | ||||
| use std::io::{self, Read}; | ||||
| use std::io::{self, Read, BufReader}; | ||||
| use std::net::SocketAddr; | ||||
|  | ||||
| use {HttpResult}; | ||||
| use net::NetworkStream; | ||||
| use version::{HttpVersion}; | ||||
| use method::Method::{self, Get, Head}; | ||||
| use header::{Headers, ContentLength, TransferEncoding}; | ||||
| use http::{read_request_line}; | ||||
| use http::HttpReader; | ||||
| use http::{self, Incoming, HttpReader}; | ||||
| use http::HttpReader::{SizedReader, ChunkedReader, EmptyReader}; | ||||
| use uri::RequestUri; | ||||
|  | ||||
| /// 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. | ||||
|     pub remote_addr: SocketAddr, | ||||
|     /// The `Method`, such as `Get`, `Post`, etc. | ||||
| @@ -26,17 +26,18 @@ pub struct Request<'a> { | ||||
|     pub uri: RequestUri, | ||||
|     /// The version of HTTP for this request. | ||||
|     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 | ||||
|     /// immediately useful. | ||||
|     pub fn new(mut stream: &'a mut (Read + 'a), addr: SocketAddr) -> HttpResult<Request<'a>> { | ||||
|         let (method, uri, version) = try!(read_request_line(&mut stream)); | ||||
|     pub fn new(mut stream: &'a mut BufReader<&'b mut NetworkStream>, addr: SocketAddr) | ||||
|         -> HttpResult<Request<'a, 'b>> { | ||||
|  | ||||
|         let Incoming { version, subject: (method, uri), headers } = try!(http::parse_request(stream)); | ||||
|         debug!("Request Line: {:?} {:?} {:?}", method, uri, version); | ||||
|         let headers = try!(Headers::from_raw(&mut stream)); | ||||
|         debug!("{:?}", headers); | ||||
|  | ||||
|         let body = if method == Get || method == Head { | ||||
| @@ -66,13 +67,13 @@ impl<'a> Request<'a> { | ||||
|     /// Deconstruct a Request into its constituent parts. | ||||
|     pub fn deconstruct(self) -> (SocketAddr, Method, Headers, | ||||
|                                  RequestUri, HttpVersion, | ||||
|                                  HttpReader<&'a mut (Read + 'a)>,) { | ||||
|                                  HttpReader<&'a mut BufReader<&'b mut NetworkStream>>) { | ||||
|         (self.remote_addr, self.method, self.headers, | ||||
|          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> { | ||||
|         self.body.read(buf) | ||||
|     } | ||||
| @@ -81,10 +82,11 @@ impl<'a> Read for Request<'a> { | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use header::{Host, TransferEncoding, Encoding}; | ||||
|     use net::NetworkStream; | ||||
|     use mock::MockStream; | ||||
|     use super::Request; | ||||
|  | ||||
|     use std::io::{self, Read}; | ||||
|     use std::io::{self, Read, BufReader}; | ||||
|     use std::net::SocketAddr; | ||||
|  | ||||
|     fn sock(s: &str) -> SocketAddr { | ||||
| @@ -99,25 +101,28 @@ mod tests { | ||||
|  | ||||
|     #[test] | ||||
|     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\ | ||||
|             Host: example.domain\r\n\ | ||||
|             \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(); | ||||
|         assert_eq!(read_to_string(req), Ok("".to_string())); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     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\ | ||||
|             Host: example.domain\r\n\ | ||||
|             \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(); | ||||
|         assert_eq!(read_to_string(req), Ok("".to_string())); | ||||
| @@ -125,12 +130,13 @@ mod tests { | ||||
|  | ||||
|     #[test] | ||||
|     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\ | ||||
|             Host: example.domain\r\n\ | ||||
|             \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(); | ||||
|         assert_eq!(read_to_string(req), Ok("".to_string())); | ||||
| @@ -138,7 +144,7 @@ mod tests { | ||||
|  | ||||
|     #[test] | ||||
|     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\ | ||||
|             Host: example.domain\r\n\ | ||||
|             Transfer-Encoding: chunked\r\n\ | ||||
| @@ -152,6 +158,7 @@ mod tests { | ||||
|             0\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(); | ||||
|  | ||||
| @@ -177,7 +184,7 @@ mod tests { | ||||
|     /// is returned. | ||||
|     #[test] | ||||
|     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\ | ||||
|             Host: example.domain\r\n\ | ||||
|             Transfer-Encoding: chunked\r\n\ | ||||
| @@ -187,6 +194,7 @@ mod tests { | ||||
|             0\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(); | ||||
|  | ||||
| @@ -197,7 +205,7 @@ mod tests { | ||||
|     /// returned. | ||||
|     #[test] | ||||
|     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\ | ||||
|             Host: example.domain\r\n\ | ||||
|             Transfer-Encoding: chunked\r\n\ | ||||
| @@ -207,6 +215,7 @@ mod tests { | ||||
|             0\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(); | ||||
|  | ||||
| @@ -217,7 +226,7 @@ mod tests { | ||||
|     /// the chunk size, the chunk is correctly read. | ||||
|     #[test] | ||||
|     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\ | ||||
|             Host: example.domain\r\n\ | ||||
|             Transfer-Encoding: chunked\r\n\ | ||||
| @@ -227,6 +236,7 @@ mod tests { | ||||
|             0\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(); | ||||
|  | ||||
|   | ||||
							
								
								
									
										41
									
								
								src/uri.rs
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								src/uri.rs
									
									
									
									
									
								
							| @@ -1,5 +1,9 @@ | ||||
| //! HTTP RequestUris | ||||
| use std::str::FromStr; | ||||
| use url::Url; | ||||
| use url::ParseError as UrlError; | ||||
|  | ||||
| use error::HttpError; | ||||
|  | ||||
| /// The Request-URI of a Request's StartLine. | ||||
| /// | ||||
| @@ -45,3 +49,40 @@ pub enum RequestUri { | ||||
|     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