Merge pull request #370 from hyperium/httparse
perf(http): changes http parsing to use httparse crate
This commit is contained in:
@@ -13,6 +13,7 @@ keywords = ["http", "hyper", "hyperium"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cookie = "*"
|
cookie = "*"
|
||||||
|
httparse = "*"
|
||||||
log = ">= 0.2.0"
|
log = ">= 0.2.0"
|
||||||
mime = "*"
|
mime = "*"
|
||||||
openssl = "*"
|
openssl = "*"
|
||||||
@@ -20,3 +21,7 @@ rustc-serialize = "*"
|
|||||||
time = "*"
|
time = "*"
|
||||||
unicase = "*"
|
unicase = "*"
|
||||||
url = "*"
|
url = "*"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
env_logger = "*"
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
|
|
||||||
|
extern crate env_logger;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use hyper::Client;
|
use hyper::Client;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
env_logger::init().unwrap();
|
||||||
|
|
||||||
let url = match env::args().nth(1) {
|
let url = match env::args().nth(1) {
|
||||||
Some(url) => url,
|
Some(url) => url,
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
#![feature(io, net)]
|
#![feature(io, net)]
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
|
extern crate env_logger;
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
@@ -15,6 +16,7 @@ fn hello(_: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
env_logger::init().unwrap();
|
||||||
let _listening = hyper::Server::http(hello)
|
let _listening = hyper::Server::http(hello)
|
||||||
.listen(IpAddr::new_v4(127, 0, 0, 1), 3000).unwrap();
|
.listen(IpAddr::new_v4(127, 0, 0, 1), 3000).unwrap();
|
||||||
println!("Listening on http://127.0.0.1:3000");
|
println!("Listening on http://127.0.0.1:3000");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
#![feature(io, net)]
|
#![feature(io, net)]
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
#[macro_use] extern crate log;
|
extern crate env_logger;
|
||||||
|
|
||||||
use std::io::{Write, copy};
|
use std::io::{Write, copy};
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
@@ -15,7 +15,7 @@ macro_rules! try_return(
|
|||||||
($e:expr) => {{
|
($e:expr) => {{
|
||||||
match $e {
|
match $e {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => { error!("Error: {}", e); return; }
|
Err(e) => { println!("Error: {}", e); return; }
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
);
|
);
|
||||||
@@ -51,6 +51,7 @@ fn echo(mut req: Request, mut res: Response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
env_logger::init().unwrap();
|
||||||
let server = Server::http(echo);
|
let server = Server::http(echo);
|
||||||
let _guard = server.listen(IpAddr::new_v4(127, 0, 0, 1), 1337).unwrap();
|
let _guard = server.listen(IpAddr::new_v4(127, 0, 0, 1), 1337).unwrap();
|
||||||
println!("Listening on http://127.0.0.1:1337");
|
println!("Listening on http://127.0.0.1:1337");
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use header;
|
|||||||
use header::{ContentLength, TransferEncoding};
|
use header::{ContentLength, TransferEncoding};
|
||||||
use header::Encoding::Chunked;
|
use header::Encoding::Chunked;
|
||||||
use net::{NetworkStream, HttpStream};
|
use net::{NetworkStream, HttpStream};
|
||||||
use http::{read_status_line, HttpReader, RawStatus};
|
use http::{self, HttpReader, RawStatus};
|
||||||
use http::HttpReader::{SizedReader, ChunkedReader, EofReader};
|
use http::HttpReader::{SizedReader, ChunkedReader, EofReader};
|
||||||
use status;
|
use status;
|
||||||
use version;
|
use version;
|
||||||
@@ -36,15 +36,17 @@ impl Response {
|
|||||||
/// Creates a new response from a server.
|
/// Creates a new response from a server.
|
||||||
pub fn new(stream: Box<NetworkStream + Send>) -> HttpResult<Response> {
|
pub fn new(stream: Box<NetworkStream + Send>) -> HttpResult<Response> {
|
||||||
let mut stream = BufReader::new(stream);
|
let mut stream = BufReader::new(stream);
|
||||||
let (version, raw_status) = try!(read_status_line(&mut stream));
|
|
||||||
|
let head = try!(http::parse_response(&mut stream));
|
||||||
|
let raw_status = head.subject;
|
||||||
|
let headers = head.headers;
|
||||||
|
|
||||||
let status = match FromPrimitive::from_u16(raw_status.0) {
|
let status = match FromPrimitive::from_u16(raw_status.0) {
|
||||||
Some(status) => status,
|
Some(status) => status,
|
||||||
None => return Err(HttpStatusError)
|
None => return Err(HttpStatusError)
|
||||||
};
|
};
|
||||||
debug!("{:?} {:?}", version, status);
|
debug!("version={:?}, status={:?}", head.version, status);
|
||||||
|
debug!("headers={:?}", headers);
|
||||||
let headers = try!(header::Headers::from_raw(&mut stream));
|
|
||||||
debug!("Headers: [\n{:?}]", headers);
|
|
||||||
|
|
||||||
let body = if headers.has::<TransferEncoding>() {
|
let body = if headers.has::<TransferEncoding>() {
|
||||||
match headers.get::<TransferEncoding>() {
|
match headers.get::<TransferEncoding>() {
|
||||||
@@ -74,7 +76,7 @@ impl Response {
|
|||||||
|
|
||||||
Ok(Response {
|
Ok(Response {
|
||||||
status: status,
|
status: status,
|
||||||
version: version,
|
version: head.version,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: body,
|
body: body,
|
||||||
status_raw: raw_status,
|
status_raw: raw_status,
|
||||||
|
|||||||
88
src/error.rs
Normal file
88
src/error.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
//! HttpError and HttpResult module.
|
||||||
|
use std::error::{Error, FromError};
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::Error as IoError;
|
||||||
|
|
||||||
|
use httparse;
|
||||||
|
use url;
|
||||||
|
|
||||||
|
use self::HttpError::{HttpMethodError, HttpUriError, HttpVersionError,
|
||||||
|
HttpHeaderError, HttpStatusError, HttpIoError,
|
||||||
|
HttpTooLargeError};
|
||||||
|
|
||||||
|
|
||||||
|
/// Result type often returned from methods that can have `HttpError`s.
|
||||||
|
pub type HttpResult<T> = Result<T, HttpError>;
|
||||||
|
|
||||||
|
/// A set of errors that can occur parsing HTTP streams.
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum HttpError {
|
||||||
|
/// An invalid `Method`, such as `GE,T`.
|
||||||
|
HttpMethodError,
|
||||||
|
/// An invalid `RequestUri`, such as `exam ple.domain`.
|
||||||
|
HttpUriError(url::ParseError),
|
||||||
|
/// An invalid `HttpVersion`, such as `HTP/1.1`
|
||||||
|
HttpVersionError,
|
||||||
|
/// An invalid `Header`.
|
||||||
|
HttpHeaderError,
|
||||||
|
/// A message head is too large to be reasonable.
|
||||||
|
HttpTooLargeError,
|
||||||
|
/// An invalid `Status`, such as `1337 ELITE`.
|
||||||
|
HttpStatusError,
|
||||||
|
/// An `IoError` that occured while trying to read or write to a network stream.
|
||||||
|
HttpIoError(IoError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for HttpError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_str(self.description())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for HttpError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
HttpMethodError => "Invalid Method specified",
|
||||||
|
HttpUriError(_) => "Invalid Request URI specified",
|
||||||
|
HttpVersionError => "Invalid HTTP version specified",
|
||||||
|
HttpHeaderError => "Invalid Header provided",
|
||||||
|
HttpTooLargeError => "Message head is too large",
|
||||||
|
HttpStatusError => "Invalid Status provided",
|
||||||
|
HttpIoError(_) => "An IoError occurred while connecting to the specified network",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&Error> {
|
||||||
|
match *self {
|
||||||
|
HttpIoError(ref error) => Some(error as &Error),
|
||||||
|
HttpUriError(ref error) => Some(error as &Error),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromError<IoError> for HttpError {
|
||||||
|
fn from_error(err: IoError) -> HttpError {
|
||||||
|
HttpIoError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromError<url::ParseError> for HttpError {
|
||||||
|
fn from_error(err: url::ParseError) -> HttpError {
|
||||||
|
HttpUriError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromError<httparse::Error> for HttpError {
|
||||||
|
fn from_error(err: httparse::Error) -> HttpError {
|
||||||
|
match err {
|
||||||
|
httparse::Error::HeaderName => HttpHeaderError,
|
||||||
|
httparse::Error::HeaderValue => HttpHeaderError,
|
||||||
|
httparse::Error::NewLine => HttpHeaderError,
|
||||||
|
httparse::Error::Status => HttpStatusError,
|
||||||
|
httparse::Error::Token => HttpHeaderError,
|
||||||
|
httparse::Error::TooManyHeaders => HttpTooLargeError,
|
||||||
|
httparse::Error::Version => HttpVersionError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,9 +32,9 @@ impl<S: Scheme + 'static> Header for Authorization<S> where <S as FromStr>::Err:
|
|||||||
match (from_utf8(unsafe { &raw.get_unchecked(0)[..] }), Scheme::scheme(None::<S>)) {
|
match (from_utf8(unsafe { &raw.get_unchecked(0)[..] }), Scheme::scheme(None::<S>)) {
|
||||||
(Ok(header), Some(scheme))
|
(Ok(header), Some(scheme))
|
||||||
if header.starts_with(scheme) && header.len() > scheme.len() + 1 => {
|
if header.starts_with(scheme) && header.len() > scheme.len() + 1 => {
|
||||||
header[scheme.len() + 1..].parse::<S>().map(|s| Authorization(s)).ok()
|
header[scheme.len() + 1..].parse::<S>().map(Authorization).ok()
|
||||||
},
|
},
|
||||||
(Ok(header), None) => header.parse::<S>().map(|s| Authorization(s)).ok(),
|
(Ok(header), None) => header.parse::<S>().map(Authorization).ok(),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -143,7 +143,7 @@ impl FromStr for Basic {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Authorization, Basic};
|
use super::{Authorization, Basic};
|
||||||
use super::super::super::{Headers};
|
use super::super::super::{Headers, Header};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_raw_auth() {
|
fn test_raw_auth() {
|
||||||
@@ -154,8 +154,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_raw_auth_parse() {
|
fn test_raw_auth_parse() {
|
||||||
let headers = Headers::from_raw(&mut b"Authorization: foo bar baz\r\n\r\n").unwrap();
|
let header: Authorization<String> = Header::parse_header(&[b"foo bar baz".to_vec()]).unwrap();
|
||||||
assert_eq!(&headers.get::<Authorization<String>>().unwrap().0[..], "foo bar baz");
|
assert_eq!(header.0, "foo bar baz");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -174,17 +174,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_auth_parse() {
|
fn test_basic_auth_parse() {
|
||||||
let headers = Headers::from_raw(&mut b"Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\r\n").unwrap();
|
let auth: Authorization<Basic> = Header::parse_header(&[b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".to_vec()]).unwrap();
|
||||||
let auth = headers.get::<Authorization<Basic>>().unwrap();
|
assert_eq!(auth.0.username, "Aladdin");
|
||||||
assert_eq!(&auth.0.username[..], "Aladdin");
|
|
||||||
assert_eq!(auth.0.password, Some("open sesame".to_string()));
|
assert_eq!(auth.0.password, Some("open sesame".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_auth_parse_no_password() {
|
fn test_basic_auth_parse_no_password() {
|
||||||
let headers = Headers::from_raw(&mut b"Authorization: Basic QWxhZGRpbjo=\r\n\r\n").unwrap();
|
let auth: Authorization<Basic> = Header::parse_header(&[b"Basic QWxhZGRpbjo=".to_vec()]).unwrap();
|
||||||
let auth = headers.get::<Authorization<Basic>>().unwrap();
|
assert_eq!(auth.0.username, "Aladdin");
|
||||||
assert_eq!(auth.0.username.as_slice(), "Aladdin");
|
|
||||||
assert_eq!(auth.0.password, Some("".to_string()));
|
assert_eq!(auth.0.password, Some("".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
//! must implement the `Header` trait from this module. Several common headers
|
//! must implement the `Header` trait from this module. Several common headers
|
||||||
//! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others.
|
//! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others.
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::borrow::Cow::{Borrowed, Owned};
|
use std::borrow::Cow::{Borrowed};
|
||||||
|
use std::borrow::ToOwned;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::Read;
|
|
||||||
use std::raw::TraitObject;
|
use std::raw::TraitObject;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::hash_map::{Iter, Entry};
|
use std::collections::hash_map::{Iter, Entry};
|
||||||
@@ -15,10 +15,11 @@ use std::iter::{FromIterator, IntoIterator};
|
|||||||
use std::borrow::{Cow, IntoCow};
|
use std::borrow::{Cow, IntoCow};
|
||||||
use std::{mem, raw};
|
use std::{mem, raw};
|
||||||
|
|
||||||
|
use httparse;
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
|
|
||||||
use self::internals::Item;
|
use self::internals::Item;
|
||||||
use {http, HttpResult, HttpError};
|
use error::HttpResult;
|
||||||
|
|
||||||
pub use self::shared::{Charset, Encoding, EntityTag, Quality, QualityItem, qitem, q};
|
pub use self::shared::{Charset, Encoding, EntityTag, Quality, QualityItem, qitem, q};
|
||||||
pub use self::common::*;
|
pub use self::common::*;
|
||||||
@@ -105,10 +106,6 @@ pub struct Headers {
|
|||||||
data: HashMap<HeaderName, Item>
|
data: HashMap<HeaderName, Item>
|
||||||
}
|
}
|
||||||
|
|
||||||
// To prevent DOS from a server sending a never ending header.
|
|
||||||
// The value was copied from curl.
|
|
||||||
const MAX_HEADERS_LENGTH: u32 = 100 * 1024;
|
|
||||||
|
|
||||||
impl Headers {
|
impl Headers {
|
||||||
|
|
||||||
/// Creates a new, empty headers map.
|
/// Creates a new, empty headers map.
|
||||||
@@ -119,27 +116,18 @@ impl Headers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn from_raw<R: Read>(rdr: &mut R) -> HttpResult<Headers> {
|
pub fn from_raw<'a>(raw: &[httparse::Header<'a>]) -> HttpResult<Headers> {
|
||||||
let mut headers = Headers::new();
|
let mut headers = Headers::new();
|
||||||
let mut count = 0u32;
|
for header in raw {
|
||||||
loop {
|
debug!("raw header: {:?}={:?}", header.name, &header.value[..]);
|
||||||
match try!(http::read_header(rdr)) {
|
let name = UniCase(header.name.to_owned().into_cow());
|
||||||
Some((name, value)) => {
|
let mut item = match headers.data.entry(name) {
|
||||||
debug!("raw header: {:?}={:?}", name, &value[..]);
|
Entry::Vacant(entry) => entry.insert(Item::new_raw(vec![])),
|
||||||
count += (name.len() + value.len()) as u32;
|
Entry::Occupied(entry) => entry.into_mut()
|
||||||
if count > MAX_HEADERS_LENGTH {
|
};
|
||||||
debug!("Max header size reached, aborting");
|
let trim = header.value.iter().rev().take_while(|&&x| x == b' ').count();
|
||||||
return Err(HttpError::HttpHeaderError)
|
let value = &header.value[.. header.value.len() - trim];
|
||||||
}
|
item.mut_raw().push(value.to_vec());
|
||||||
let name = UniCase(Owned(name));
|
|
||||||
let mut item = match headers.data.entry(name) {
|
|
||||||
Entry::Vacant(entry) => entry.insert(Item::new_raw(vec![])),
|
|
||||||
Entry::Occupied(entry) => entry.into_mut()
|
|
||||||
};
|
|
||||||
item.mut_raw().push(value);
|
|
||||||
},
|
|
||||||
None => break,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(headers)
|
Ok(headers)
|
||||||
}
|
}
|
||||||
@@ -364,12 +352,26 @@ mod tests {
|
|||||||
use mime::SubLevel::Plain;
|
use mime::SubLevel::Plain;
|
||||||
use super::{Headers, Header, HeaderFormat, ContentLength, ContentType,
|
use super::{Headers, Header, HeaderFormat, ContentLength, ContentType,
|
||||||
Accept, Host, qitem};
|
Accept, Host, qitem};
|
||||||
|
use httparse;
|
||||||
|
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
|
macro_rules! raw {
|
||||||
|
($($line:expr),*) => ({
|
||||||
|
[$({
|
||||||
|
let line = $line;
|
||||||
|
let pos = line.position_elem(&b':').expect("raw splits on :, not found");
|
||||||
|
httparse::Header {
|
||||||
|
name: ::std::str::from_utf8(&line[..pos]).unwrap(),
|
||||||
|
value: &line[pos + 2..]
|
||||||
|
}
|
||||||
|
}),*]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_raw() {
|
fn test_from_raw() {
|
||||||
let headers = Headers::from_raw(&mut b"Content-Length: 10\r\n\r\n").unwrap();
|
let headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
|
||||||
assert_eq!(headers.get(), Some(&ContentLength(10)));
|
assert_eq!(headers.get(), Some(&ContentLength(10)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,20 +424,20 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_different_structs_for_same_header() {
|
fn test_different_structs_for_same_header() {
|
||||||
let headers = Headers::from_raw(&mut b"Content-Length: 10\r\n\r\n").unwrap();
|
let headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
|
||||||
assert_eq!(headers.get::<ContentLength>(), Some(&ContentLength(10)));
|
assert_eq!(headers.get::<ContentLength>(), Some(&ContentLength(10)));
|
||||||
assert_eq!(headers.get::<CrazyLength>(), Some(&CrazyLength(Some(false), 10)));
|
assert_eq!(headers.get::<CrazyLength>(), Some(&CrazyLength(Some(false), 10)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_trailing_whitespace() {
|
fn test_trailing_whitespace() {
|
||||||
let headers = Headers::from_raw(&mut b"Content-Length: 10 \r\n\r\n").unwrap();
|
let headers = Headers::from_raw(&raw!(b"Content-Length: 10 ")).unwrap();
|
||||||
assert_eq!(headers.get::<ContentLength>(), Some(&ContentLength(10)));
|
assert_eq!(headers.get::<ContentLength>(), Some(&ContentLength(10)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_reads() {
|
fn test_multiple_reads() {
|
||||||
let headers = Headers::from_raw(&mut b"Content-Length: 10\r\n\r\n").unwrap();
|
let headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
|
||||||
let ContentLength(one) = *headers.get::<ContentLength>().unwrap();
|
let ContentLength(one) = *headers.get::<ContentLength>().unwrap();
|
||||||
let ContentLength(two) = *headers.get::<ContentLength>().unwrap();
|
let ContentLength(two) = *headers.get::<ContentLength>().unwrap();
|
||||||
assert_eq!(one, two);
|
assert_eq!(one, two);
|
||||||
@@ -443,14 +445,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_different_reads() {
|
fn test_different_reads() {
|
||||||
let headers = Headers::from_raw(&mut b"Content-Length: 10\r\nContent-Type: text/plain\r\n\r\n").unwrap();
|
let headers = Headers::from_raw(&raw!(b"Content-Length: 10", b"Content-Type: text/plain")).unwrap();
|
||||||
let ContentLength(_) = *headers.get::<ContentLength>().unwrap();
|
let ContentLength(_) = *headers.get::<ContentLength>().unwrap();
|
||||||
let ContentType(_) = *headers.get::<ContentType>().unwrap();
|
let ContentType(_) = *headers.get::<ContentType>().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_mutable() {
|
fn test_get_mutable() {
|
||||||
let mut headers = Headers::from_raw(&mut b"Content-Length: 10\r\nContent-Type: text/plain\r\n\r\n").unwrap();
|
let mut headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
|
||||||
*headers.get_mut::<ContentLength>().unwrap() = ContentLength(20);
|
*headers.get_mut::<ContentLength>().unwrap() = ContentLength(20);
|
||||||
assert_eq!(*headers.get::<ContentLength>().unwrap(), ContentLength(20));
|
assert_eq!(*headers.get::<ContentLength>().unwrap(), ContentLength(20));
|
||||||
}
|
}
|
||||||
@@ -471,7 +473,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_headers_show_raw() {
|
fn test_headers_show_raw() {
|
||||||
let headers = Headers::from_raw(&mut b"Content-Length: 10\r\n\r\n").unwrap();
|
let headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
|
||||||
let s = headers.to_string();
|
let s = headers.to_string();
|
||||||
assert_eq!(s, "Content-Length: 10\r\n");
|
assert_eq!(s, "Content-Length: 10\r\n");
|
||||||
}
|
}
|
||||||
@@ -538,7 +540,8 @@ mod tests {
|
|||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_headers_from_raw(b: &mut Bencher) {
|
fn bench_headers_from_raw(b: &mut Bencher) {
|
||||||
b.iter(|| Headers::from_raw(&mut b"Content-Length: 10\r\n\r\n").unwrap())
|
let raw = raw!(b"Content-Length: 10");
|
||||||
|
b.iter(|| Headers::from_raw(&raw).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|||||||
641
src/http.rs
641
src/http.rs
@@ -1,22 +1,15 @@
|
|||||||
//! Pieces pertaining to the HTTP message protocol.
|
//! Pieces pertaining to the HTTP message protocol.
|
||||||
use std::borrow::Cow::{self, Borrowed, Owned};
|
use std::borrow::{Cow, IntoCow, ToOwned};
|
||||||
use std::borrow::IntoCow;
|
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::io::{self, Read, Write, Cursor};
|
use std::io::{self, Read, Write, BufRead};
|
||||||
use std::num::from_u16;
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use url::Url;
|
use httparse;
|
||||||
use url::ParseError as UrlError;
|
|
||||||
|
|
||||||
use method;
|
use header::Headers;
|
||||||
use status::StatusCode;
|
use method::Method;
|
||||||
use uri;
|
use uri::RequestUri;
|
||||||
use uri::RequestUri::{AbsolutePath, AbsoluteUri, Authority, Star};
|
use version::HttpVersion::{self, Http10, Http11};
|
||||||
use version::HttpVersion;
|
use HttpError:: HttpTooLargeError;
|
||||||
use version::HttpVersion::{Http09, Http10, Http11};
|
|
||||||
use HttpError::{HttpHeaderError, HttpMethodError, HttpStatusError,
|
|
||||||
HttpUriError, HttpVersionError};
|
|
||||||
use HttpResult;
|
use HttpResult;
|
||||||
|
|
||||||
use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader};
|
use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader};
|
||||||
@@ -314,338 +307,74 @@ impl<W: Write> Write for HttpWriter<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a request into an Incoming message head.
|
||||||
|
pub fn parse_request<T: BufRead>(buf: &mut T) -> HttpResult<Incoming<(Method, RequestUri)>> {
|
||||||
|
let (inc, len) = {
|
||||||
|
let slice = try!(buf.fill_buf());
|
||||||
|
let mut headers = [httparse::Header { name: "", value: b"" }; 64];
|
||||||
|
let mut req = httparse::Request::new(&mut headers);
|
||||||
|
match try!(req.parse(slice)) {
|
||||||
|
httparse::Status::Complete(len) => {
|
||||||
|
(Incoming {
|
||||||
|
version: if req.version.unwrap() == 1 { Http11 } else { Http10 },
|
||||||
|
subject: (
|
||||||
|
try!(req.method.unwrap().parse()),
|
||||||
|
try!(req.path.unwrap().parse())
|
||||||
|
),
|
||||||
|
headers: try!(Headers::from_raw(req.headers))
|
||||||
|
}, len)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// request head is bigger than a BufRead's buffer? 400 that!
|
||||||
|
return Err(HttpTooLargeError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
buf.consume(len);
|
||||||
|
Ok(inc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a response into an Incoming message head.
|
||||||
|
pub fn parse_response<T: BufRead>(buf: &mut T) -> HttpResult<Incoming<RawStatus>> {
|
||||||
|
let (inc, len) = {
|
||||||
|
let mut headers = [httparse::Header { name: "", value: b"" }; 64];
|
||||||
|
let mut res = httparse::Response::new(&mut headers);
|
||||||
|
match try!(res.parse(try!(buf.fill_buf()))) {
|
||||||
|
httparse::Status::Complete(len) => {
|
||||||
|
(Incoming {
|
||||||
|
version: if res.version.unwrap() == 1 { Http11 } else { Http10 },
|
||||||
|
subject: RawStatus(
|
||||||
|
res.code.unwrap(), res.reason.unwrap().to_owned().into_cow()
|
||||||
|
),
|
||||||
|
headers: try!(Headers::from_raw(res.headers))
|
||||||
|
}, len)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// response head is bigger than a BufRead's buffer?
|
||||||
|
return Err(HttpTooLargeError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
buf.consume(len);
|
||||||
|
Ok(inc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An Incoming Message head. Includes request/status line, and headers.
|
||||||
|
pub struct Incoming<S> {
|
||||||
|
/// HTTP version of the message.
|
||||||
|
pub version: HttpVersion,
|
||||||
|
/// Subject (request line or status line) of Incoming message.
|
||||||
|
pub subject: S,
|
||||||
|
/// Headers of the Incoming message.
|
||||||
|
pub headers: Headers
|
||||||
|
}
|
||||||
|
|
||||||
pub const SP: u8 = b' ';
|
pub const SP: u8 = b' ';
|
||||||
pub const CR: u8 = b'\r';
|
pub const CR: u8 = b'\r';
|
||||||
pub const LF: u8 = b'\n';
|
pub const LF: u8 = b'\n';
|
||||||
pub const STAR: u8 = b'*';
|
pub const STAR: u8 = b'*';
|
||||||
pub const LINE_ENDING: &'static str = "\r\n";
|
pub const LINE_ENDING: &'static str = "\r\n";
|
||||||
|
|
||||||
/// Determines if byte is a token char.
|
|
||||||
///
|
|
||||||
/// > ```notrust
|
|
||||||
/// > token = 1*tchar
|
|
||||||
/// >
|
|
||||||
/// > tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
|
|
||||||
/// > / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
|
|
||||||
/// > / DIGIT / ALPHA
|
|
||||||
/// > ; any VCHAR, except delimiters
|
|
||||||
/// > ```
|
|
||||||
#[inline]
|
|
||||||
pub fn is_token(b: u8) -> bool {
|
|
||||||
match b {
|
|
||||||
b'a'...b'z' |
|
|
||||||
b'A'...b'Z' |
|
|
||||||
b'0'...b'9' |
|
|
||||||
b'!' |
|
|
||||||
b'#' |
|
|
||||||
b'$' |
|
|
||||||
b'%' |
|
|
||||||
b'&' |
|
|
||||||
b'\''|
|
|
||||||
b'*' |
|
|
||||||
b'+' |
|
|
||||||
b'-' |
|
|
||||||
b'.' |
|
|
||||||
b'^' |
|
|
||||||
b'_' |
|
|
||||||
b'`' |
|
|
||||||
b'|' |
|
|
||||||
b'~' => true,
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read token bytes from `stream` into `buf` until a space is encountered.
|
|
||||||
/// Returns `Ok(true)` if we read until a space,
|
|
||||||
/// `Ok(false)` if we got to the end of `buf` without encountering a space,
|
|
||||||
/// otherwise returns any error encountered reading the stream.
|
|
||||||
///
|
|
||||||
/// The remaining contents of `buf` are left untouched.
|
|
||||||
fn read_method_token_until_space<R: Read>(stream: &mut R, buf: &mut [u8]) -> HttpResult<bool> {
|
|
||||||
macro_rules! byte (
|
|
||||||
($rdr:ident) => ({
|
|
||||||
let mut slot = [0];
|
|
||||||
match try!($rdr.read(&mut slot)) {
|
|
||||||
1 => slot[0],
|
|
||||||
_ => return Err(HttpMethodError),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut cursor = Cursor::new(buf);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let b = byte!(stream);
|
|
||||||
|
|
||||||
if b == SP {
|
|
||||||
break;
|
|
||||||
} else if !is_token(b) {
|
|
||||||
return Err(HttpMethodError);
|
|
||||||
// Read to end but there's still more
|
|
||||||
} else {
|
|
||||||
match cursor.write(&[b]) {
|
|
||||||
Ok(1) => (),
|
|
||||||
_ => return Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cursor.position() == 0 {
|
|
||||||
return Err(HttpMethodError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a `Method` from a raw stream, such as `GET`.
|
|
||||||
/// ### Note:
|
|
||||||
/// Extension methods are only parsed to 16 characters.
|
|
||||||
pub fn read_method<R: Read>(stream: &mut R) -> HttpResult<method::Method> {
|
|
||||||
let mut buf = [SP; 16];
|
|
||||||
|
|
||||||
if !try!(read_method_token_until_space(stream, &mut buf)) {
|
|
||||||
return Err(HttpMethodError);
|
|
||||||
}
|
|
||||||
|
|
||||||
let maybe_method = match &buf[0..7] {
|
|
||||||
b"GET " => Some(method::Method::Get),
|
|
||||||
b"PUT " => Some(method::Method::Put),
|
|
||||||
b"POST " => Some(method::Method::Post),
|
|
||||||
b"HEAD " => Some(method::Method::Head),
|
|
||||||
b"PATCH " => Some(method::Method::Patch),
|
|
||||||
b"TRACE " => Some(method::Method::Trace),
|
|
||||||
b"DELETE " => Some(method::Method::Delete),
|
|
||||||
b"CONNECT" => Some(method::Method::Connect),
|
|
||||||
b"OPTIONS" => Some(method::Method::Options),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("maybe_method = {:?}", maybe_method);
|
|
||||||
|
|
||||||
match (maybe_method, &buf[..]) {
|
|
||||||
(Some(method), _) => Ok(method),
|
|
||||||
(None, ext) => {
|
|
||||||
// We already checked that the buffer is ASCII
|
|
||||||
Ok(method::Method::Extension(unsafe { str::from_utf8_unchecked(ext) }.trim().to_string()))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a `RequestUri` from a raw stream.
|
|
||||||
pub fn read_uri<R: Read>(stream: &mut R) -> HttpResult<uri::RequestUri> {
|
|
||||||
macro_rules! byte (
|
|
||||||
($rdr:ident) => ({
|
|
||||||
let mut buf = [0];
|
|
||||||
match try!($rdr.read(&mut buf)) {
|
|
||||||
1 => buf[0],
|
|
||||||
_ => return Err(HttpUriError(UrlError::InvalidCharacter)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
let mut b = byte!(stream);
|
|
||||||
while b == SP {
|
|
||||||
b = byte!(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
if b == STAR {
|
|
||||||
try!(expect(byte!(stream), SP));
|
|
||||||
return Ok(Star)
|
|
||||||
} else {
|
|
||||||
s.push(b as char);
|
|
||||||
loop {
|
|
||||||
match byte!(stream) {
|
|
||||||
SP => {
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
CR | LF => {
|
|
||||||
return Err(HttpUriError(UrlError::InvalidCharacter))
|
|
||||||
},
|
|
||||||
b => s.push(b as char)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("uri buf = {:?}", s);
|
|
||||||
|
|
||||||
if s.as_slice().starts_with("/") {
|
|
||||||
Ok(AbsolutePath(s))
|
|
||||||
} else if s.as_slice().contains("/") {
|
|
||||||
Ok(AbsoluteUri(try!(Url::parse(s.as_slice()))))
|
|
||||||
} else {
|
|
||||||
let mut temp = "http://".to_string();
|
|
||||||
temp.push_str(s.as_slice());
|
|
||||||
try!(Url::parse(temp.as_slice()));
|
|
||||||
todo!("compare vs u.authority()");
|
|
||||||
Ok(Authority(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Read the `HttpVersion` from a raw stream, such as `HTTP/1.1`.
|
|
||||||
pub fn read_http_version<R: Read>(stream: &mut R) -> HttpResult<HttpVersion> {
|
|
||||||
macro_rules! byte (
|
|
||||||
($rdr:ident) => ({
|
|
||||||
let mut buf = [0];
|
|
||||||
match try!($rdr.read(&mut buf)) {
|
|
||||||
1 => buf[0],
|
|
||||||
_ => return Err(HttpVersionError),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
try!(expect(byte!(stream), b'H'));
|
|
||||||
try!(expect(byte!(stream), b'T'));
|
|
||||||
try!(expect(byte!(stream), b'T'));
|
|
||||||
try!(expect(byte!(stream), b'P'));
|
|
||||||
try!(expect(byte!(stream), b'/'));
|
|
||||||
|
|
||||||
match byte!(stream) {
|
|
||||||
b'0' => {
|
|
||||||
try!(expect(byte!(stream), b'.'));
|
|
||||||
try!(expect(byte!(stream), b'9'));
|
|
||||||
Ok(Http09)
|
|
||||||
},
|
|
||||||
b'1' => {
|
|
||||||
try!(expect(byte!(stream), b'.'));
|
|
||||||
match byte!(stream) {
|
|
||||||
b'0' => Ok(Http10),
|
|
||||||
b'1' => Ok(Http11),
|
|
||||||
_ => Err(HttpVersionError)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => Err(HttpVersionError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAX_HEADER_NAME_LENGTH: usize = 100;
|
|
||||||
const MAX_HEADER_FIELD_LENGTH: usize = 4096;
|
|
||||||
|
|
||||||
/// The raw bytes when parsing a header line.
|
|
||||||
///
|
|
||||||
/// A String and Vec<u8>, divided by COLON (`:`). The String is guaranteed
|
|
||||||
/// to be all `token`s. See `is_token` source for all valid characters.
|
|
||||||
pub type RawHeaderLine = (String, Vec<u8>);
|
|
||||||
|
|
||||||
/// Read a RawHeaderLine from a Reader.
|
|
||||||
///
|
|
||||||
/// From [spec](https://tools.ietf.org/html/http#section-3.2):
|
|
||||||
///
|
|
||||||
/// > Each header field consists of a case-insensitive field name followed
|
|
||||||
/// > by a colon (":"), optional leading whitespace, the field value, and
|
|
||||||
/// > optional trailing whitespace.
|
|
||||||
/// >
|
|
||||||
/// > ```notrust
|
|
||||||
/// > header-field = field-name ":" OWS field-value OWS
|
|
||||||
/// >
|
|
||||||
/// > field-name = token
|
|
||||||
/// > field-value = *( field-content / obs-fold )
|
|
||||||
/// > field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
|
||||||
/// > field-vchar = VCHAR / obs-text
|
|
||||||
/// >
|
|
||||||
/// > obs-fold = CRLF 1*( SP / HTAB )
|
|
||||||
/// > ; obsolete line folding
|
|
||||||
/// > ; see Section 3.2.4
|
|
||||||
/// > ```
|
|
||||||
pub fn read_header<R: Read>(stream: &mut R) -> HttpResult<Option<RawHeaderLine>> {
|
|
||||||
macro_rules! byte (
|
|
||||||
($rdr:ident) => ({
|
|
||||||
let mut buf = [0];
|
|
||||||
match try!($rdr.read(&mut buf)) {
|
|
||||||
1 => buf[0],
|
|
||||||
_ => return Err(HttpHeaderError),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut name = String::new();
|
|
||||||
let mut value = vec![];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match byte!(stream) {
|
|
||||||
CR if name.len() == 0 => {
|
|
||||||
match byte!(stream) {
|
|
||||||
LF => return Ok(None),
|
|
||||||
_ => return Err(HttpHeaderError)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
b':' => break,
|
|
||||||
b if is_token(b) => {
|
|
||||||
if name.len() > MAX_HEADER_NAME_LENGTH { return Err(HttpHeaderError); }
|
|
||||||
name.push(b as char)
|
|
||||||
},
|
|
||||||
_nontoken => return Err(HttpHeaderError)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("header name = {:?}", name);
|
|
||||||
|
|
||||||
let mut ows = true; //optional whitespace
|
|
||||||
|
|
||||||
todo!("handle obs-folding (gross!)");
|
|
||||||
loop {
|
|
||||||
match byte!(stream) {
|
|
||||||
CR => break,
|
|
||||||
LF => return Err(HttpHeaderError),
|
|
||||||
b' ' if ows => {},
|
|
||||||
b => {
|
|
||||||
ows = false;
|
|
||||||
if value.len() > MAX_HEADER_FIELD_LENGTH { return Err(HttpHeaderError); }
|
|
||||||
value.push(b)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Remove optional trailing whitespace
|
|
||||||
let real_len = value.len() - value.iter().rev().take_while(|&&x| b' ' == x).count();
|
|
||||||
value.truncate(real_len);
|
|
||||||
|
|
||||||
match byte!(stream) {
|
|
||||||
LF => Ok(Some((name, value))),
|
|
||||||
_ => Err(HttpHeaderError)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `request-line = method SP request-target SP HTTP-version CRLF`
|
|
||||||
pub type RequestLine = (method::Method, uri::RequestUri, HttpVersion);
|
|
||||||
|
|
||||||
/// Read the `RequestLine`, such as `GET / HTTP/1.1`.
|
|
||||||
pub fn read_request_line<R: Read>(stream: &mut R) -> HttpResult<RequestLine> {
|
|
||||||
macro_rules! byte (
|
|
||||||
($rdr:ident) => ({
|
|
||||||
let mut buf = [0];
|
|
||||||
match try!($rdr.read(&mut buf)) {
|
|
||||||
1 => buf[0],
|
|
||||||
_ => return Err(HttpVersionError),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
debug!("read request line");
|
|
||||||
let method = try!(read_method(stream));
|
|
||||||
debug!("method = {:?}", method);
|
|
||||||
let uri = try!(read_uri(stream));
|
|
||||||
debug!("uri = {:?}", uri);
|
|
||||||
let version = try!(read_http_version(stream));
|
|
||||||
debug!("version = {:?}", version);
|
|
||||||
|
|
||||||
if byte!(stream) != CR {
|
|
||||||
return Err(HttpVersionError);
|
|
||||||
}
|
|
||||||
if byte!(stream) != LF {
|
|
||||||
return Err(HttpVersionError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((method, uri, version))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `status-line = HTTP-version SP status-code SP reason-phrase CRLF`
|
|
||||||
///
|
|
||||||
/// However, reason-phrase is absolutely useless, so its tossed.
|
|
||||||
pub type StatusLine = (HttpVersion, RawStatus);
|
|
||||||
|
|
||||||
/// The raw status code and reason-phrase.
|
/// The raw status code and reason-phrase.
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct RawStatus(pub u16, pub Cow<'static, str>);
|
pub struct RawStatus(pub u16, pub Cow<'static, str>);
|
||||||
@@ -656,219 +385,12 @@ impl Clone for RawStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the StatusLine, such as `HTTP/1.1 200 OK`.
|
|
||||||
///
|
|
||||||
/// > The first line of a response message is the status-line, consisting
|
|
||||||
/// > of the protocol version, a space (SP), the status code, another
|
|
||||||
/// > space, a possibly empty textual phrase describing the status code,
|
|
||||||
/// > and ending with CRLF.
|
|
||||||
/// >
|
|
||||||
/// >```notrust
|
|
||||||
/// > status-line = HTTP-version SP status-code SP reason-phrase CRLF
|
|
||||||
/// > status-code = 3DIGIT
|
|
||||||
/// > reason-phrase = *( HTAB / SP / VCHAR / obs-text )
|
|
||||||
/// >```
|
|
||||||
pub fn read_status_line<R: Read>(stream: &mut R) -> HttpResult<StatusLine> {
|
|
||||||
macro_rules! byte (
|
|
||||||
($rdr:ident) => ({
|
|
||||||
let mut buf = [0];
|
|
||||||
match try!($rdr.read(&mut buf)) {
|
|
||||||
1 => buf[0],
|
|
||||||
_ => return Err(HttpVersionError),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let version = try!(read_http_version(stream));
|
|
||||||
if byte!(stream) != SP {
|
|
||||||
return Err(HttpVersionError);
|
|
||||||
}
|
|
||||||
let code = try!(read_status(stream));
|
|
||||||
|
|
||||||
Ok((version, code))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the StatusCode from a stream.
|
|
||||||
pub fn read_status<R: Read>(stream: &mut R) -> HttpResult<RawStatus> {
|
|
||||||
macro_rules! byte (
|
|
||||||
($rdr:ident) => ({
|
|
||||||
let mut buf = [0];
|
|
||||||
match try!($rdr.read(&mut buf)) {
|
|
||||||
1 => buf[0],
|
|
||||||
_ => return Err(HttpStatusError),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let code = [
|
|
||||||
byte!(stream),
|
|
||||||
byte!(stream),
|
|
||||||
byte!(stream),
|
|
||||||
];
|
|
||||||
|
|
||||||
let code = match str::from_utf8(code.as_slice()).ok().and_then(|x| x.parse().ok()) {
|
|
||||||
Some(num) => num,
|
|
||||||
None => return Err(HttpStatusError)
|
|
||||||
};
|
|
||||||
|
|
||||||
match byte!(stream) {
|
|
||||||
b' ' => (),
|
|
||||||
_ => return Err(HttpStatusError)
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut buf = [SP; 32];
|
|
||||||
let mut cursor = Cursor::new(&mut buf[..]);
|
|
||||||
{
|
|
||||||
'read: loop {
|
|
||||||
match byte!(stream) {
|
|
||||||
CR => match byte!(stream) {
|
|
||||||
LF => break,
|
|
||||||
_ => return Err(HttpStatusError)
|
|
||||||
},
|
|
||||||
b => match cursor.write(&[b]) {
|
|
||||||
Ok(0) | Err(_) => {
|
|
||||||
for _ in 0u8..128 {
|
|
||||||
match byte!(stream) {
|
|
||||||
CR => match byte!(stream) {
|
|
||||||
LF => break 'read,
|
|
||||||
_ => return Err(HttpStatusError)
|
|
||||||
},
|
|
||||||
_ => { /* ignore */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err(HttpStatusError)
|
|
||||||
}
|
|
||||||
Ok(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let reason = match str::from_utf8(cursor.into_inner()) {
|
|
||||||
Ok(s) => s.trim(),
|
|
||||||
Err(_) => return Err(HttpStatusError)
|
|
||||||
};
|
|
||||||
|
|
||||||
let reason = match from_u16::<StatusCode>(code) {
|
|
||||||
Some(status) => match status.canonical_reason() {
|
|
||||||
Some(phrase) => {
|
|
||||||
if phrase == reason {
|
|
||||||
Borrowed(phrase)
|
|
||||||
} else {
|
|
||||||
Owned(reason.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Owned(reason.to_string())
|
|
||||||
},
|
|
||||||
None => return Err(HttpStatusError)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(RawStatus(code, reason))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn expect(actual: u8, expected: u8) -> HttpResult<()> {
|
|
||||||
if actual == expected {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(HttpVersionError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::borrow::Cow::{Borrowed, Owned};
|
|
||||||
use test::Bencher;
|
|
||||||
use uri::RequestUri;
|
|
||||||
use uri::RequestUri::{Star, AbsoluteUri, AbsolutePath, Authority};
|
|
||||||
use method;
|
|
||||||
use version::HttpVersion;
|
|
||||||
use version::HttpVersion::{Http10, Http11};
|
|
||||||
use HttpError::{HttpVersionError, HttpMethodError};
|
|
||||||
use HttpResult;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use super::{read_method, read_uri, read_http_version, read_header,
|
use super::{read_chunk_size};
|
||||||
RawHeaderLine, read_status, RawStatus, read_chunk_size};
|
|
||||||
#[test]
|
|
||||||
fn test_read_method() {
|
|
||||||
fn read(s: &str, result: HttpResult<method::Method>) {
|
|
||||||
assert_eq!(read_method(&mut s.as_bytes()), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
read("GET /", Ok(method::Method::Get));
|
|
||||||
read("POST /", Ok(method::Method::Post));
|
|
||||||
read("PUT /", Ok(method::Method::Put));
|
|
||||||
read("HEAD /", Ok(method::Method::Head));
|
|
||||||
read("OPTIONS /", Ok(method::Method::Options));
|
|
||||||
read("CONNECT /", Ok(method::Method::Connect));
|
|
||||||
read("TRACE /", Ok(method::Method::Trace));
|
|
||||||
read("PATCH /", Ok(method::Method::Patch));
|
|
||||||
read("FOO /", Ok(method::Method::Extension("FOO".to_string())));
|
|
||||||
read("akemi!~#HOMURA /", Ok(method::Method::Extension("akemi!~#HOMURA".to_string())));
|
|
||||||
read(" ", Err(HttpMethodError));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_read_uri() {
|
|
||||||
fn read(s: &str, result: HttpResult<RequestUri>) {
|
|
||||||
assert_eq!(read_uri(&mut s.as_bytes()), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
read("* ", Ok(Star));
|
|
||||||
read("http://hyper.rs/ ", Ok(AbsoluteUri(Url::parse("http://hyper.rs/").unwrap())));
|
|
||||||
read("hyper.rs ", Ok(Authority("hyper.rs".to_string())));
|
|
||||||
read("/ ", Ok(AbsolutePath("/".to_string())));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_read_http_version() {
|
|
||||||
fn read(s: &str, result: HttpResult<HttpVersion>) {
|
|
||||||
assert_eq!(read_http_version(&mut s.as_bytes()), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
read("HTTP/1.0", Ok(Http10));
|
|
||||||
read("HTTP/1.1", Ok(Http11));
|
|
||||||
read("HTP/2.0", Err(HttpVersionError));
|
|
||||||
read("HTTP.2.0", Err(HttpVersionError));
|
|
||||||
read("HTTP 2.0", Err(HttpVersionError));
|
|
||||||
read("TTP 2.0", Err(HttpVersionError));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_read_status() {
|
|
||||||
fn read(s: &str, result: HttpResult<RawStatus>) {
|
|
||||||
assert_eq!(read_status(&mut s.as_bytes()), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ignore_string(s: &str, result: HttpResult<RawStatus>) {
|
|
||||||
match (read_status(&mut s.as_bytes()), result) {
|
|
||||||
(Ok(RawStatus(ref c1, _)), Ok(RawStatus(ref c2, _))) => {
|
|
||||||
assert_eq!(c1, c2);
|
|
||||||
},
|
|
||||||
(r1, r2) => assert_eq!(r1, r2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
read("200 OK\r\n", Ok(RawStatus(200, Borrowed("OK"))));
|
|
||||||
read("404 Not Found\r\n", Ok(RawStatus(404, Borrowed("Not Found"))));
|
|
||||||
read("200 crazy pants\r\n", Ok(RawStatus(200, Owned("crazy pants".to_string()))));
|
|
||||||
read("301 Moved Permanently\r\n", Ok(RawStatus(301, Owned("Moved Permanently".to_string()))));
|
|
||||||
read_ignore_string("301 Unreasonably long header that should not happen, \
|
|
||||||
but some men just want to watch the world burn\r\n",
|
|
||||||
Ok(RawStatus(301, Owned("Ignored".to_string()))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_read_header() {
|
|
||||||
fn read(s: &str, result: HttpResult<Option<RawHeaderLine>>) {
|
|
||||||
assert_eq!(read_header(&mut s.as_bytes()), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
read("Host: rust-lang.org\r\n", Ok(Some(("Host".to_string(),
|
|
||||||
b"rust-lang.org".to_vec()))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_write_chunked() {
|
fn test_write_chunked() {
|
||||||
@@ -934,16 +456,19 @@ mod tests {
|
|||||||
read_err("1;no CRLF");
|
read_err("1;no CRLF");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
use test::Bencher;
|
||||||
fn bench_read_method(b: &mut Bencher) {
|
|
||||||
b.bytes = b"CONNECT ".len() as u64;
|
|
||||||
b.iter(|| assert_eq!(read_method(&mut b"CONNECT "), Ok(method::Method::Connect)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_read_status(b: &mut Bencher) {
|
fn bench_parse_incoming(b: &mut Bencher) {
|
||||||
b.bytes = b"404 Not Found\r\n".len() as u64;
|
use std::io::BufReader;
|
||||||
b.iter(|| assert_eq!(read_status(&mut b"404 Not Found\r\n"), Ok(RawStatus(404, Borrowed("Not Found")))));
|
use mock::MockStream;
|
||||||
}
|
use net::NetworkStream;
|
||||||
|
use super::parse_request;
|
||||||
|
b.iter(|| {
|
||||||
|
let mut raw = MockStream::with_input(b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n");
|
||||||
|
let mut buf = BufReader::new(&mut raw as &mut NetworkStream);
|
||||||
|
|
||||||
|
parse_request(&mut buf).unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/lib.rs
72
src/lib.rs
@@ -1,6 +1,6 @@
|
|||||||
#![feature(core, collections, io, net, os, path,
|
#![feature(core, collections, io, net, os, path,
|
||||||
std_misc, box_syntax, unsafe_destructor)]
|
std_misc, box_syntax, unsafe_destructor)]
|
||||||
#![cfg_attr(test, deny(missing_docs))]
|
#![deny(missing_docs)]
|
||||||
#![cfg_attr(test, deny(warnings))]
|
#![cfg_attr(test, deny(warnings))]
|
||||||
#![cfg_attr(test, feature(alloc, test))]
|
#![cfg_attr(test, feature(alloc, test))]
|
||||||
|
|
||||||
@@ -132,6 +132,7 @@ extern crate url;
|
|||||||
extern crate openssl;
|
extern crate openssl;
|
||||||
extern crate cookie;
|
extern crate cookie;
|
||||||
extern crate unicase;
|
extern crate unicase;
|
||||||
|
extern crate httparse;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
@@ -143,17 +144,11 @@ extern crate test;
|
|||||||
pub use mimewrapper::mime;
|
pub use mimewrapper::mime;
|
||||||
pub use url::Url;
|
pub use url::Url;
|
||||||
pub use client::Client;
|
pub use client::Client;
|
||||||
|
pub use error::{HttpResult, HttpError};
|
||||||
pub use method::Method::{Get, Head, Post, Delete};
|
pub use method::Method::{Get, Head, Post, Delete};
|
||||||
pub use status::StatusCode::{Ok, BadRequest, NotFound};
|
pub use status::StatusCode::{Ok, BadRequest, NotFound};
|
||||||
pub use server::Server;
|
pub use server::Server;
|
||||||
|
|
||||||
use std::error::{Error, FromError};
|
|
||||||
use std::fmt;
|
|
||||||
use std::io::Error as IoError;
|
|
||||||
|
|
||||||
use self::HttpError::{HttpMethodError, HttpUriError, HttpVersionError,
|
|
||||||
HttpHeaderError, HttpStatusError, HttpIoError};
|
|
||||||
|
|
||||||
macro_rules! todo(
|
macro_rules! todo(
|
||||||
($($arg:tt)*) => (if cfg!(not(ndebug)) {
|
($($arg:tt)*) => (if cfg!(not(ndebug)) {
|
||||||
trace!("TODO: {:?}", format_args!($($arg)*))
|
trace!("TODO: {:?}", format_args!($($arg)*))
|
||||||
@@ -173,6 +168,7 @@ macro_rules! inspect(
|
|||||||
mod mock;
|
mod mock;
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
pub mod error;
|
||||||
pub mod method;
|
pub mod method;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
@@ -188,66 +184,6 @@ mod mimewrapper {
|
|||||||
extern crate mime;
|
extern crate mime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Result type often returned from methods that can have `HttpError`s.
|
|
||||||
pub type HttpResult<T> = Result<T, HttpError>;
|
|
||||||
|
|
||||||
/// A set of errors that can occur parsing HTTP streams.
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
pub enum HttpError {
|
|
||||||
/// An invalid `Method`, such as `GE,T`.
|
|
||||||
HttpMethodError,
|
|
||||||
/// An invalid `RequestUri`, such as `exam ple.domain`.
|
|
||||||
HttpUriError(url::ParseError),
|
|
||||||
/// An invalid `HttpVersion`, such as `HTP/1.1`
|
|
||||||
HttpVersionError,
|
|
||||||
/// An invalid `Header`.
|
|
||||||
HttpHeaderError,
|
|
||||||
/// An invalid `Status`, such as `1337 ELITE`.
|
|
||||||
HttpStatusError,
|
|
||||||
/// An `IoError` that occured while trying to read or write to a network stream.
|
|
||||||
HttpIoError(IoError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for HttpError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.write_str(self.description())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for HttpError {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
match *self {
|
|
||||||
HttpMethodError => "Invalid Method specified",
|
|
||||||
HttpUriError(_) => "Invalid Request URI specified",
|
|
||||||
HttpVersionError => "Invalid HTTP version specified",
|
|
||||||
HttpHeaderError => "Invalid Header provided",
|
|
||||||
HttpStatusError => "Invalid Status provided",
|
|
||||||
HttpIoError(_) => "An IoError occurred while connecting to the specified network",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cause(&self) -> Option<&Error> {
|
|
||||||
match *self {
|
|
||||||
HttpIoError(ref error) => Some(error as &Error),
|
|
||||||
HttpUriError(ref error) => Some(error as &Error),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromError<IoError> for HttpError {
|
|
||||||
fn from_error(err: IoError) -> HttpError {
|
|
||||||
HttpIoError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromError<url::ParseError> for HttpError {
|
|
||||||
fn from_error(err: url::ParseError) -> HttpError {
|
|
||||||
HttpUriError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unconditional_recursion)]
|
#[allow(unconditional_recursion)]
|
||||||
fn _assert_send<T: Send>() {
|
fn _assert_send<T: Send>() {
|
||||||
_assert_send::<client::Request<net::Fresh>>();
|
_assert_send::<client::Request<net::Fresh>>();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use error::HttpError;
|
||||||
use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch,
|
use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch,
|
||||||
Extension};
|
Extension};
|
||||||
|
|
||||||
@@ -68,10 +69,10 @@ impl Method {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Method {
|
impl FromStr for Method {
|
||||||
type Err = ();
|
type Err = HttpError;
|
||||||
fn from_str(s: &str) -> Result<Method, ()> {
|
fn from_str(s: &str) -> Result<Method, HttpError> {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
Err(())
|
Err(HttpError::HttpMethodError)
|
||||||
} else {
|
} else {
|
||||||
Ok(match s {
|
Ok(match s {
|
||||||
"OPTIONS" => Options,
|
"OPTIONS" => Options,
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ pub trait NetworkStream: Read + Write + Any + StreamClone + Send {
|
|||||||
fn peer_addr(&mut self) -> io::Result<SocketAddr>;
|
fn peer_addr(&mut self) -> io::Result<SocketAddr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait StreamClone {
|
pub trait StreamClone {
|
||||||
fn clone_box(&self) -> Box<NetworkStream + Send>;
|
fn clone_box(&self) -> Box<NetworkStream + Send>;
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ macro_rules! try_option(
|
|||||||
);
|
);
|
||||||
|
|
||||||
impl<'a, H: Handler, L: NetworkListener> Server<'a, H, L> {
|
impl<'a, H: Handler, L: NetworkListener> Server<'a, H, L> {
|
||||||
|
/// Creates a new server with the provided handler.
|
||||||
pub fn new(handler: H) -> Server<'a, H, L> {
|
pub fn new(handler: H) -> Server<'a, H, L> {
|
||||||
Server {
|
Server {
|
||||||
handler: handler,
|
handler: handler,
|
||||||
@@ -97,7 +98,7 @@ S: NetworkStream + Clone + Send> Server<'a, H, L> {
|
|||||||
|
|
||||||
debug!("threads = {:?}", threads);
|
debug!("threads = {:?}", threads);
|
||||||
let pool = ListenerPool::new(listener.clone());
|
let pool = ListenerPool::new(listener.clone());
|
||||||
let work = move |stream| handle_connection(stream, &handler);
|
let work = move |stream| keep_alive_loop(stream, &handler);
|
||||||
|
|
||||||
let guard = thread::scoped(move || pool.accept(work, threads));
|
let guard = thread::scoped(move || pool.accept(work, threads));
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ S: NetworkStream + Clone + Send> Server<'a, H, L> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn handle_connection<S, H>(mut stream: S, handler: &H)
|
fn keep_alive_loop<'h, S, H>(mut stream: S, handler: &'h H)
|
||||||
where S: NetworkStream + Clone, H: Handler {
|
where S: NetworkStream + Clone, H: Handler {
|
||||||
debug!("Incoming stream");
|
debug!("Incoming stream");
|
||||||
let addr = match stream.peer_addr() {
|
let addr = match stream.peer_addr() {
|
||||||
@@ -120,36 +121,47 @@ where S: NetworkStream + Clone, H: Handler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut rdr = BufReader::new(stream.clone());
|
let mut stream_clone = stream.clone();
|
||||||
|
let mut rdr = BufReader::new(&mut stream_clone as &mut NetworkStream);
|
||||||
let mut wrt = BufWriter::new(stream);
|
let mut wrt = BufWriter::new(stream);
|
||||||
|
|
||||||
let mut keep_alive = true;
|
let mut keep_alive = true;
|
||||||
while keep_alive {
|
while keep_alive {
|
||||||
let mut res = Response::new(&mut wrt);
|
keep_alive = handle_connection(addr, &mut rdr, &mut wrt, handler);
|
||||||
let req = match Request::new(&mut rdr, addr) {
|
|
||||||
Ok(req) => req,
|
|
||||||
Err(e@HttpIoError(_)) => {
|
|
||||||
debug!("ioerror in keepalive loop = {:?}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
//TODO: send a 400 response
|
|
||||||
error!("request error = {:?}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
keep_alive = match (req.version, req.headers.get::<Connection>()) {
|
|
||||||
(Http10, Some(conn)) if !conn.contains(&KeepAlive) => false,
|
|
||||||
(Http11, Some(conn)) if conn.contains(&Close) => false,
|
|
||||||
_ => true
|
|
||||||
};
|
|
||||||
res.version = req.version;
|
|
||||||
handler.handle(req, res);
|
|
||||||
debug!("keep_alive = {:?}", keep_alive);
|
debug!("keep_alive = {:?}", keep_alive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_connection<'a, 'aa, 'h, S, H>(
|
||||||
|
addr: SocketAddr,
|
||||||
|
rdr: &'a mut BufReader<&'aa mut NetworkStream>,
|
||||||
|
wrt: &mut BufWriter<S>,
|
||||||
|
handler: &'h H
|
||||||
|
) -> bool where 'aa: 'a, S: NetworkStream, H: Handler {
|
||||||
|
let mut res = Response::new(wrt);
|
||||||
|
let req = match Request::<'a, 'aa>::new(rdr, addr) {
|
||||||
|
Ok(req) => req,
|
||||||
|
Err(e@HttpIoError(_)) => {
|
||||||
|
debug!("ioerror in keepalive loop = {:?}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
//TODO: send a 400 response
|
||||||
|
error!("request error = {:?}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let keep_alive = match (req.version, req.headers.get::<Connection>()) {
|
||||||
|
(Http10, Some(conn)) if !conn.contains(&KeepAlive) => false,
|
||||||
|
(Http11, Some(conn)) if conn.contains(&Close) => false,
|
||||||
|
_ => true
|
||||||
|
};
|
||||||
|
res.version = req.version;
|
||||||
|
handler.handle(req, res);
|
||||||
|
keep_alive
|
||||||
|
}
|
||||||
|
|
||||||
/// A listening server, which can later be closed.
|
/// A listening server, which can later be closed.
|
||||||
pub struct Listening {
|
pub struct Listening {
|
||||||
_guard: JoinGuard<'static, ()>,
|
_guard: JoinGuard<'static, ()>,
|
||||||
@@ -171,11 +183,11 @@ pub trait Handler: Sync + Send {
|
|||||||
/// Receives a `Request`/`Response` pair, and should perform some action on them.
|
/// Receives a `Request`/`Response` pair, and should perform some action on them.
|
||||||
///
|
///
|
||||||
/// This could reading from the request, and writing to the response.
|
/// This could reading from the request, and writing to the response.
|
||||||
fn handle<'a>(&'a self, Request<'a>, Response<'a, Fresh>);
|
fn handle<'a, 'aa, 'b, 's>(&'s self, Request<'aa, 'a>, Response<'b, Fresh>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F> Handler for F where F: Fn(Request, Response<Fresh>), F: Sync + Send {
|
impl<F> Handler for F where F: Fn(Request, Response<Fresh>), F: Sync + Send {
|
||||||
fn handle(&self, req: Request, res: Response<Fresh>) {
|
fn handle<'a, 'aa, 'b, 's>(&'s self, req: Request<'a, 'aa>, res: Response<'b, Fresh>) {
|
||||||
(*self)(req, res)
|
self(req, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
//!
|
//!
|
||||||
//! These are requests that a `hyper::Server` receives, and include its method,
|
//! These are requests that a `hyper::Server` receives, and include its method,
|
||||||
//! target URI, headers, and message body.
|
//! target URI, headers, and message body.
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read, BufReader};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use {HttpResult};
|
use {HttpResult};
|
||||||
|
use net::NetworkStream;
|
||||||
use version::{HttpVersion};
|
use version::{HttpVersion};
|
||||||
use method::Method::{self, Get, Head};
|
use method::Method::{self, Get, Head};
|
||||||
use header::{Headers, ContentLength, TransferEncoding};
|
use header::{Headers, ContentLength, TransferEncoding};
|
||||||
use http::{read_request_line};
|
use http::{self, Incoming, HttpReader};
|
||||||
use http::HttpReader;
|
|
||||||
use http::HttpReader::{SizedReader, ChunkedReader, EmptyReader};
|
use http::HttpReader::{SizedReader, ChunkedReader, EmptyReader};
|
||||||
use uri::RequestUri;
|
use uri::RequestUri;
|
||||||
|
|
||||||
/// A request bundles several parts of an incoming `NetworkStream`, given to a `Handler`.
|
/// A request bundles several parts of an incoming `NetworkStream`, given to a `Handler`.
|
||||||
pub struct Request<'a> {
|
pub struct Request<'a, 'b: 'a> {
|
||||||
/// The IP address of the remote connection.
|
/// The IP address of the remote connection.
|
||||||
pub remote_addr: SocketAddr,
|
pub remote_addr: SocketAddr,
|
||||||
/// The `Method`, such as `Get`, `Post`, etc.
|
/// The `Method`, such as `Get`, `Post`, etc.
|
||||||
@@ -26,17 +26,18 @@ pub struct Request<'a> {
|
|||||||
pub uri: RequestUri,
|
pub uri: RequestUri,
|
||||||
/// The version of HTTP for this request.
|
/// The version of HTTP for this request.
|
||||||
pub version: HttpVersion,
|
pub version: HttpVersion,
|
||||||
body: HttpReader<&'a mut (Read + 'a)>
|
body: HttpReader<&'a mut BufReader<&'b mut NetworkStream>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<'a> Request<'a> {
|
impl<'a, 'b: 'a> Request<'a, 'b> {
|
||||||
/// Create a new Request, reading the StartLine and Headers so they are
|
/// Create a new Request, reading the StartLine and Headers so they are
|
||||||
/// immediately useful.
|
/// immediately useful.
|
||||||
pub fn new(mut stream: &'a mut (Read + 'a), addr: SocketAddr) -> HttpResult<Request<'a>> {
|
pub fn new(mut stream: &'a mut BufReader<&'b mut NetworkStream>, addr: SocketAddr)
|
||||||
let (method, uri, version) = try!(read_request_line(&mut stream));
|
-> HttpResult<Request<'a, 'b>> {
|
||||||
|
|
||||||
|
let Incoming { version, subject: (method, uri), headers } = try!(http::parse_request(stream));
|
||||||
debug!("Request Line: {:?} {:?} {:?}", method, uri, version);
|
debug!("Request Line: {:?} {:?} {:?}", method, uri, version);
|
||||||
let headers = try!(Headers::from_raw(&mut stream));
|
|
||||||
debug!("{:?}", headers);
|
debug!("{:?}", headers);
|
||||||
|
|
||||||
let body = if method == Get || method == Head {
|
let body = if method == Get || method == Head {
|
||||||
@@ -66,13 +67,13 @@ impl<'a> Request<'a> {
|
|||||||
/// Deconstruct a Request into its constituent parts.
|
/// Deconstruct a Request into its constituent parts.
|
||||||
pub fn deconstruct(self) -> (SocketAddr, Method, Headers,
|
pub fn deconstruct(self) -> (SocketAddr, Method, Headers,
|
||||||
RequestUri, HttpVersion,
|
RequestUri, HttpVersion,
|
||||||
HttpReader<&'a mut (Read + 'a)>,) {
|
HttpReader<&'a mut BufReader<&'b mut NetworkStream>>) {
|
||||||
(self.remote_addr, self.method, self.headers,
|
(self.remote_addr, self.method, self.headers,
|
||||||
self.uri, self.version, self.body)
|
self.uri, self.version, self.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Read for Request<'a> {
|
impl<'a, 'b> Read for Request<'a, 'b> {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
self.body.read(buf)
|
self.body.read(buf)
|
||||||
}
|
}
|
||||||
@@ -81,10 +82,11 @@ impl<'a> Read for Request<'a> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use header::{Host, TransferEncoding, Encoding};
|
use header::{Host, TransferEncoding, Encoding};
|
||||||
|
use net::NetworkStream;
|
||||||
use mock::MockStream;
|
use mock::MockStream;
|
||||||
use super::Request;
|
use super::Request;
|
||||||
|
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read, BufReader};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
fn sock(s: &str) -> SocketAddr {
|
fn sock(s: &str) -> SocketAddr {
|
||||||
@@ -99,25 +101,28 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_empty_body() {
|
fn test_get_empty_body() {
|
||||||
let mut stream = MockStream::with_input(b"\
|
let mut mock = MockStream::with_input(b"\
|
||||||
GET / HTTP/1.1\r\n\
|
GET / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
I'm a bad request.\r\n\
|
I'm a bad request.\r\n\
|
||||||
");
|
");
|
||||||
|
|
||||||
|
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
||||||
assert_eq!(read_to_string(req), Ok("".to_string()));
|
assert_eq!(read_to_string(req), Ok("".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_head_empty_body() {
|
fn test_head_empty_body() {
|
||||||
let mut stream = MockStream::with_input(b"\
|
let mut mock = MockStream::with_input(b"\
|
||||||
HEAD / HTTP/1.1\r\n\
|
HEAD / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
I'm a bad request.\r\n\
|
I'm a bad request.\r\n\
|
||||||
");
|
");
|
||||||
|
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
||||||
assert_eq!(read_to_string(req), Ok("".to_string()));
|
assert_eq!(read_to_string(req), Ok("".to_string()));
|
||||||
@@ -125,12 +130,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_post_empty_body() {
|
fn test_post_empty_body() {
|
||||||
let mut stream = MockStream::with_input(b"\
|
let mut mock = MockStream::with_input(b"\
|
||||||
POST / HTTP/1.1\r\n\
|
POST / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
I'm a bad request.\r\n\
|
I'm a bad request.\r\n\
|
||||||
");
|
");
|
||||||
|
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
||||||
assert_eq!(read_to_string(req), Ok("".to_string()));
|
assert_eq!(read_to_string(req), Ok("".to_string()));
|
||||||
@@ -138,7 +144,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_chunked_request() {
|
fn test_parse_chunked_request() {
|
||||||
let mut stream = MockStream::with_input(b"\
|
let mut mock = MockStream::with_input(b"\
|
||||||
POST / HTTP/1.1\r\n\
|
POST / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
Transfer-Encoding: chunked\r\n\
|
Transfer-Encoding: chunked\r\n\
|
||||||
@@ -152,6 +158,7 @@ mod tests {
|
|||||||
0\r\n\
|
0\r\n\
|
||||||
\r\n"
|
\r\n"
|
||||||
);
|
);
|
||||||
|
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
||||||
|
|
||||||
@@ -177,7 +184,7 @@ mod tests {
|
|||||||
/// is returned.
|
/// is returned.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_chunk_size_not_hex_digit() {
|
fn test_invalid_chunk_size_not_hex_digit() {
|
||||||
let mut stream = MockStream::with_input(b"\
|
let mut mock = MockStream::with_input(b"\
|
||||||
POST / HTTP/1.1\r\n\
|
POST / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
Transfer-Encoding: chunked\r\n\
|
Transfer-Encoding: chunked\r\n\
|
||||||
@@ -187,6 +194,7 @@ mod tests {
|
|||||||
0\r\n\
|
0\r\n\
|
||||||
\r\n"
|
\r\n"
|
||||||
);
|
);
|
||||||
|
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
||||||
|
|
||||||
@@ -197,7 +205,7 @@ mod tests {
|
|||||||
/// returned.
|
/// returned.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_chunk_size_extension() {
|
fn test_invalid_chunk_size_extension() {
|
||||||
let mut stream = MockStream::with_input(b"\
|
let mut mock = MockStream::with_input(b"\
|
||||||
POST / HTTP/1.1\r\n\
|
POST / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
Transfer-Encoding: chunked\r\n\
|
Transfer-Encoding: chunked\r\n\
|
||||||
@@ -207,6 +215,7 @@ mod tests {
|
|||||||
0\r\n\
|
0\r\n\
|
||||||
\r\n"
|
\r\n"
|
||||||
);
|
);
|
||||||
|
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
||||||
|
|
||||||
@@ -217,7 +226,7 @@ mod tests {
|
|||||||
/// the chunk size, the chunk is correctly read.
|
/// the chunk size, the chunk is correctly read.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chunk_size_with_extension() {
|
fn test_chunk_size_with_extension() {
|
||||||
let mut stream = MockStream::with_input(b"\
|
let mut mock = MockStream::with_input(b"\
|
||||||
POST / HTTP/1.1\r\n\
|
POST / HTTP/1.1\r\n\
|
||||||
Host: example.domain\r\n\
|
Host: example.domain\r\n\
|
||||||
Transfer-Encoding: chunked\r\n\
|
Transfer-Encoding: chunked\r\n\
|
||||||
@@ -227,6 +236,7 @@ mod tests {
|
|||||||
0\r\n\
|
0\r\n\
|
||||||
\r\n"
|
\r\n"
|
||||||
);
|
);
|
||||||
|
let mut stream = BufReader::new(&mut mock as &mut NetworkStream);
|
||||||
|
|
||||||
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap();
|
||||||
|
|
||||||
|
|||||||
41
src/uri.rs
41
src/uri.rs
@@ -1,5 +1,9 @@
|
|||||||
//! HTTP RequestUris
|
//! HTTP RequestUris
|
||||||
|
use std::str::FromStr;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use url::ParseError as UrlError;
|
||||||
|
|
||||||
|
use error::HttpError;
|
||||||
|
|
||||||
/// The Request-URI of a Request's StartLine.
|
/// The Request-URI of a Request's StartLine.
|
||||||
///
|
///
|
||||||
@@ -45,3 +49,40 @@ pub enum RequestUri {
|
|||||||
Star,
|
Star,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for RequestUri {
|
||||||
|
type Err = HttpError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<RequestUri, HttpError> {
|
||||||
|
match s.as_bytes() {
|
||||||
|
[] => Err(HttpError::HttpUriError(UrlError::InvalidCharacter)),
|
||||||
|
[b'*'] => Ok(RequestUri::Star),
|
||||||
|
[b'/', ..] => Ok(RequestUri::AbsolutePath(s.to_string())),
|
||||||
|
bytes if bytes.contains(&b'/') => {
|
||||||
|
Ok(RequestUri::AbsoluteUri(try!(Url::parse(s))))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut temp = "http://".to_string();
|
||||||
|
temp.push_str(s);
|
||||||
|
try!(Url::parse(&temp[..]));
|
||||||
|
todo!("compare vs u.authority()");
|
||||||
|
Ok(RequestUri::Authority(s.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uri_fromstr() {
|
||||||
|
use error::HttpResult;
|
||||||
|
fn read(s: &str, result: HttpResult<RequestUri>) {
|
||||||
|
assert_eq!(s.parse(), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
read("*", Ok(RequestUri::Star));
|
||||||
|
read("http://hyper.rs/", Ok(RequestUri::AbsoluteUri(Url::parse("http://hyper.rs/").unwrap())));
|
||||||
|
read("hyper.rs", Ok(RequestUri::Authority("hyper.rs".to_string())));
|
||||||
|
read("/", Ok(RequestUri::AbsolutePath("/".to_string())));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user