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]
|
||||
cookie = "*"
|
||||
httparse = "*"
|
||||
log = ">= 0.2.0"
|
||||
mime = "*"
|
||||
openssl = "*"
|
||||
@@ -20,3 +21,7 @@ rustc-serialize = "*"
|
||||
time = "*"
|
||||
unicase = "*"
|
||||
url = "*"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "*"
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#![deny(warnings)]
|
||||
extern crate hyper;
|
||||
|
||||
extern crate env_logger;
|
||||
|
||||
use std::env;
|
||||
|
||||
use hyper::Client;
|
||||
|
||||
fn main() {
|
||||
env_logger::init().unwrap();
|
||||
|
||||
let url = match env::args().nth(1) {
|
||||
Some(url) => url,
|
||||
None => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![deny(warnings)]
|
||||
#![feature(io, net)]
|
||||
extern crate hyper;
|
||||
extern crate env_logger;
|
||||
|
||||
use std::io::Write;
|
||||
use std::net::IpAddr;
|
||||
@@ -15,6 +16,7 @@ fn hello(_: Request, res: Response) {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init().unwrap();
|
||||
let _listening = hyper::Server::http(hello)
|
||||
.listen(IpAddr::new_v4(127, 0, 0, 1), 3000).unwrap();
|
||||
println!("Listening on http://127.0.0.1:3000");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![deny(warnings)]
|
||||
#![feature(io, net)]
|
||||
extern crate hyper;
|
||||
#[macro_use] extern crate log;
|
||||
extern crate env_logger;
|
||||
|
||||
use std::io::{Write, copy};
|
||||
use std::net::IpAddr;
|
||||
@@ -15,7 +15,7 @@ macro_rules! try_return(
|
||||
($e:expr) => {{
|
||||
match $e {
|
||||
Ok(v) => v,
|
||||
Err(e) => { error!("Error: {}", e); return; }
|
||||
Err(e) => { println!("Error: {}", e); return; }
|
||||
}
|
||||
}}
|
||||
);
|
||||
@@ -51,6 +51,7 @@ fn echo(mut req: Request, mut res: Response) {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init().unwrap();
|
||||
let server = Server::http(echo);
|
||||
let _guard = server.listen(IpAddr::new_v4(127, 0, 0, 1), 1337).unwrap();
|
||||
println!("Listening on http://127.0.0.1:1337");
|
||||
|
||||
@@ -7,7 +7,7 @@ use header;
|
||||
use header::{ContentLength, TransferEncoding};
|
||||
use header::Encoding::Chunked;
|
||||
use net::{NetworkStream, HttpStream};
|
||||
use http::{read_status_line, HttpReader, RawStatus};
|
||||
use http::{self, HttpReader, RawStatus};
|
||||
use http::HttpReader::{SizedReader, ChunkedReader, EofReader};
|
||||
use status;
|
||||
use version;
|
||||
@@ -36,15 +36,17 @@ impl Response {
|
||||
/// Creates a new response from a server.
|
||||
pub fn new(stream: Box<NetworkStream + Send>) -> HttpResult<Response> {
|
||||
let mut stream = BufReader::new(stream);
|
||||
let (version, raw_status) = try!(read_status_line(&mut stream));
|
||||
|
||||
let head = try!(http::parse_response(&mut stream));
|
||||
let raw_status = head.subject;
|
||||
let headers = head.headers;
|
||||
|
||||
let status = match FromPrimitive::from_u16(raw_status.0) {
|
||||
Some(status) => status,
|
||||
None => return Err(HttpStatusError)
|
||||
};
|
||||
debug!("{:?} {:?}", version, status);
|
||||
|
||||
let headers = try!(header::Headers::from_raw(&mut stream));
|
||||
debug!("Headers: [\n{:?}]", headers);
|
||||
debug!("version={:?}, status={:?}", head.version, status);
|
||||
debug!("headers={:?}", headers);
|
||||
|
||||
let body = if headers.has::<TransferEncoding>() {
|
||||
match headers.get::<TransferEncoding>() {
|
||||
@@ -74,7 +76,7 @@ impl Response {
|
||||
|
||||
Ok(Response {
|
||||
status: status,
|
||||
version: version,
|
||||
version: head.version,
|
||||
headers: headers,
|
||||
body: body,
|
||||
status_raw: raw_status,
|
||||
|
||||
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));
|
||||
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()
|
||||
};
|
||||
item.mut_raw().push(value);
|
||||
},
|
||||
None => break,
|
||||
}
|
||||
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,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 keep_alive = true;
|
||||
while keep_alive {
|
||||
let mut res = Response::new(&mut wrt);
|
||||
let req = match Request::new(&mut rdr, addr) {
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
Err(e) => {
|
||||
//TODO: send a 400 response
|
||||
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,
|
||||
(Http11, Some(conn)) if conn.contains(&Close) => false,
|
||||
_ => true
|
||||
};
|
||||
res.version = req.version;
|
||||
handler.handle(req, res);
|
||||
debug!("keep_alive = {:?}", keep_alive);
|
||||
}
|
||||
keep_alive
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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