Merge pull request #370 from hyperium/httparse

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

View File

@@ -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
View File

@@ -0,0 +1,88 @@
//! HttpError and HttpResult module.
use std::error::{Error, FromError};
use std::fmt;
use std::io::Error as IoError;
use httparse;
use url;
use self::HttpError::{HttpMethodError, HttpUriError, HttpVersionError,
HttpHeaderError, HttpStatusError, HttpIoError,
HttpTooLargeError};
/// Result type often returned from methods that can have `HttpError`s.
pub type HttpResult<T> = Result<T, HttpError>;
/// A set of errors that can occur parsing HTTP streams.
#[derive(Debug, PartialEq, Clone)]
pub enum HttpError {
/// An invalid `Method`, such as `GE,T`.
HttpMethodError,
/// An invalid `RequestUri`, such as `exam ple.domain`.
HttpUriError(url::ParseError),
/// An invalid `HttpVersion`, such as `HTP/1.1`
HttpVersionError,
/// An invalid `Header`.
HttpHeaderError,
/// A message head is too large to be reasonable.
HttpTooLargeError,
/// An invalid `Status`, such as `1337 ELITE`.
HttpStatusError,
/// An `IoError` that occured while trying to read or write to a network stream.
HttpIoError(IoError),
}
impl fmt::Display for HttpError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.description())
}
}
impl Error for HttpError {
fn description(&self) -> &str {
match *self {
HttpMethodError => "Invalid Method specified",
HttpUriError(_) => "Invalid Request URI specified",
HttpVersionError => "Invalid HTTP version specified",
HttpHeaderError => "Invalid Header provided",
HttpTooLargeError => "Message head is too large",
HttpStatusError => "Invalid Status provided",
HttpIoError(_) => "An IoError occurred while connecting to the specified network",
}
}
fn cause(&self) -> Option<&Error> {
match *self {
HttpIoError(ref error) => Some(error as &Error),
HttpUriError(ref error) => Some(error as &Error),
_ => None,
}
}
}
impl FromError<IoError> for HttpError {
fn from_error(err: IoError) -> HttpError {
HttpIoError(err)
}
}
impl FromError<url::ParseError> for HttpError {
fn from_error(err: url::ParseError) -> HttpError {
HttpUriError(err)
}
}
impl FromError<httparse::Error> for HttpError {
fn from_error(err: httparse::Error) -> HttpError {
match err {
httparse::Error::HeaderName => HttpHeaderError,
httparse::Error::HeaderValue => HttpHeaderError,
httparse::Error::NewLine => HttpHeaderError,
httparse::Error::Status => HttpStatusError,
httparse::Error::Token => HttpHeaderError,
httparse::Error::TooManyHeaders => HttpTooLargeError,
httparse::Error::Version => HttpVersionError,
}
}
}

View File

@@ -32,9 +32,9 @@ impl<S: Scheme + 'static> Header for Authorization<S> where <S as FromStr>::Err:
match (from_utf8(unsafe { &raw.get_unchecked(0)[..] }), Scheme::scheme(None::<S>)) {
(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()));
}

View File

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

View File

@@ -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();
});
}
}

View File

@@ -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>>();

View File

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

View File

@@ -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>;

View File

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

View File

@@ -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();

View File

@@ -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())));
}