diff --git a/src/client/request.rs b/src/client/request.rs index 79f708e2..28617e93 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -7,6 +7,7 @@ use http::{Body, RequestHead}; use method::Method; use uri::Uri; use version::HttpVersion; +use std::str::FromStr; /// A client request to a remote server. pub struct Request { @@ -79,7 +80,7 @@ impl fmt::Debug for Request { } pub fn split(req: Request) -> (RequestHead, Option) { - let uri = Uri::new(&req.url[::url::Position::BeforePath..::url::Position::AfterQuery]).expect("url is uri"); + let uri = Uri::from_str(&req.url[::url::Position::BeforePath..::url::Position::AfterQuery]).expect("url is uri"); let head = RequestHead { subject: ::http::RequestLine(req.method, uri), headers: req.headers, diff --git a/src/http/buf.rs b/src/http/buf.rs index 29c72f1e..e50623bb 100644 --- a/src/http/buf.rs +++ b/src/http/buf.rs @@ -4,6 +4,7 @@ use std::fmt; use std::io::{self, Read}; use std::ops::{Index, Range, RangeFrom, RangeTo, RangeFull}; use std::ptr; +use std::str; use std::sync::Arc; pub struct MemBuf { @@ -49,8 +50,8 @@ impl MemBuf { } pub fn slice(&self, len: usize) -> MemSlice { - assert!(self.end - self.start.get() >= len); let start = self.start.get(); + assert!(!(self.end - start < len)); let end = start + len; self.start.set(end); MemSlice { @@ -196,6 +197,19 @@ impl From> for MemBuf { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MemStr(MemSlice); + +impl MemStr { + pub unsafe fn from_utf8_unchecked(slice: MemSlice) -> MemStr { + MemStr(slice) + } + + pub fn as_str(&self) -> &str { + unsafe { str::from_utf8_unchecked(self.0.as_ref()) } + } +} + pub struct MemSlice { buf: Arc>>, start: usize, diff --git a/src/http/conn.rs b/src/http/conn.rs index c54f4c81..3e2b80ce 100644 --- a/src/http/conn.rs +++ b/src/http/conn.rs @@ -577,6 +577,8 @@ mod tests { use super::{Conn, Writing}; use ::uri::Uri; + use std::str::FromStr; + #[test] fn test_conn_init_read() { let good_message = b"GET / HTTP/1.1\r\n\r\n".to_vec(); @@ -587,7 +589,7 @@ mod tests { match conn.poll().unwrap() { Async::Ready(Some(Frame::Message { message, body: false })) => { assert_eq!(message, MessageHead { - subject: ::http::RequestLine(::Get, Uri::new("/").unwrap()), + subject: ::http::RequestLine(::Get, Uri::from_str("/").unwrap()), .. MessageHead::default() }) }, diff --git a/src/http/h1/parse.rs b/src/http/h1/parse.rs index 1170e926..78046034 100644 --- a/src/http/h1/parse.rs +++ b/src/http/h1/parse.rs @@ -6,7 +6,7 @@ use httparse; use header::{self, Headers, ContentLength, TransferEncoding}; use http::{MessageHead, RawStatus, Http1Transaction, ParseResult, ServerTransaction, ClientTransaction, RequestLine}; use http::h1::{Encoder, Decoder}; -use http::buf::{MemBuf, MemSlice}; +use http::buf::{MemBuf, MemSlice, MemStr}; use method::Method; use status::StatusCode; use version::HttpVersion::{Http10, Http11}; @@ -33,18 +33,26 @@ impl Http1Transaction for ServerTransaction { Ok(match try!(req.parse(buf.bytes())) { httparse::Status::Complete(len) => { trace!("Request.parse Complete({})", len); - let mut headers = Headers::with_capacity(req.headers.len()); let slice = buf.slice(len); + let path = req.path.unwrap(); + let path_start = path.as_ptr() as usize - slice.as_ref().as_ptr() as usize; + let path_end = path_start + path.len(); + let path = slice.slice(path_start..path_end); + // path was found to be utf8 by httparse + let path = unsafe { MemStr::from_utf8_unchecked(path) }; + let subject = RequestLine( + try!(req.method.unwrap().parse()), + try!(::uri::from_mem_str(path)), + ); + let mut headers = Headers::with_capacity(req.headers.len()); headers.extend(HeadersAsMemSliceIter { headers: req.headers.iter(), slice: slice, }); + Some((MessageHead { version: if req.version.unwrap() == 1 { Http11 } else { Http10 }, - subject: RequestLine( - try!(req.method.unwrap().parse()), - try!(req.path.unwrap().parse()) - ), + subject: subject, headers: headers, }, len)) } @@ -143,7 +151,7 @@ impl Http1Transaction for ClientTransaction { headers: headers, }, len)) }, - httparse::Status::Partial => None + httparse::Status::Partial => None, }) } @@ -326,5 +334,4 @@ mod tests { raw.restart(); }); } - } diff --git a/src/uri.rs b/src/uri.rs index fe7c682d..aa3eb914 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -1,6 +1,8 @@ use std::borrow::Cow; use std::fmt::{Display, self}; -use std::str::FromStr; +use std::ops::Deref; +use std::str::{self, FromStr}; +use http::buf::MemStr; use Url; use url::ParseError as UrlError; @@ -30,7 +32,7 @@ use Error; /// scheme authority path query fragment #[derive(Clone)] pub struct Uri { - source: Cow<'static, str>, + source: InternalUri, scheme_end: Option, authority_end: Option, query: Option, @@ -39,31 +41,32 @@ pub struct Uri { impl Uri { /// Parse a string into a `Uri`. - pub fn new(s: &str) -> Result { - let bytes = s.as_bytes(); - if bytes.len() == 0 { + fn new(s: InternalUri) -> Result { + if s.len() == 0 { Err(Error::Uri(UrlError::RelativeUrlWithoutBase)) - } else if bytes == b"*" { + } else if s.as_bytes() == b"*" { Ok(Uri { - source: "*".into(), + source: InternalUri::Cow("*".into()), scheme_end: None, authority_end: None, query: None, fragment: None, }) - } else if bytes == b"/" { + } else if s.as_bytes() == b"/" { Ok(Uri::default()) - } else if bytes.starts_with(b"/") { + } else if s.as_bytes()[0] == b'/' { + let query = parse_query(&s); + let fragment = parse_fragment(&s); Ok(Uri { - source: s.to_owned().into(), + source: s, scheme_end: None, authority_end: None, - query: parse_query(s), - fragment: parse_fragment(s), + query: query, + fragment: fragment, }) } else if s.contains("://") { - let scheme = parse_scheme(s); - let auth = parse_authority(s); + let scheme = parse_scheme(&s); + let auth = parse_authority(&s); if let Some(end) = scheme { match &s[..end] { "ftp" | "gopher" | "http" | "https" | "ws" | "wss" => {}, @@ -79,20 +82,23 @@ impl Uri { None => return Err(Error::Method), } } + let query = parse_query(&s); + let fragment = parse_fragment(&s); Ok(Uri { - source: s.to_owned().into(), + source: s, scheme_end: scheme, authority_end: auth, - query: parse_query(s), - fragment: parse_fragment(s), + query: query, + fragment: fragment, }) } else if (s.contains("/") || s.contains("?")) && !s.contains("://") { return Err(Error::Method) } else { + let len = s.len(); Ok(Uri { - source: s.to_owned().into(), + source: s, scheme_end: None, - authority_end: Some(s.len()), + authority_end: Some(len), query: None, fragment: None, }) @@ -108,9 +114,10 @@ impl Uri { if fragment_len > 0 { fragment_len + 1 } else { 0 }; if index >= end { if self.scheme().is_some() { - return "/" // absolute-form MUST have path + "/" // absolute-form MUST have path + } else { + "" } - "" } else { &self.source[index..end] } @@ -158,7 +165,8 @@ impl Uri { let fragment_len = self.fragment.unwrap_or(0); let fragment_len = if fragment_len > 0 { fragment_len + 1 } else { 0 }; if let Some(len) = self.query { - Some(&self.source[self.source.len() - len - fragment_len..self.source.len() - fragment_len]) + Some(&self.source[self.source.len() - len - fragment_len.. + self.source.len() - fragment_len]) } else { None } @@ -167,7 +175,7 @@ impl Uri { #[cfg(test)] fn fragment(&self) -> Option<&str> { if let Some(len) = self.fragment { - Some(&self.source[self.source.len() - len..]) + Some(&self.source[self.source.len() - len..self.source.len()]) } else { None } @@ -180,7 +188,7 @@ fn parse_scheme(s: &str) -> Option { fn parse_authority(s: &str) -> Option { let i = s.find("://").and_then(|p| Some(p + 3)).unwrap_or(0); - + Some(&s[i..].split("/") .next() .unwrap_or(s) @@ -209,32 +217,43 @@ impl FromStr for Uri { type Err = Error; fn from_str(s: &str) -> Result { - Uri::new(s) + //TODO: refactor such that the to_owned() is only required at the end + //of successful parsing, so an Err doesn't needlessly clone the string. + Uri::new(InternalUri::Cow(Cow::Owned(s.to_owned()))) } } impl From for Uri { fn from(url: Url) -> Uri { - Uri::new(url.as_str()).expect("Uri::From failed") + let s = InternalUri::Cow(Cow::Owned(url.into_string())); + // TODO: we could build the indices with some simple subtraction, + // instead of re-parsing an already parsed string. + // For example: + // let scheme_end = url[..url::Position::AfterScheme].as_ptr() as usize + // - url.as_str().as_ptr() as usize; + // etc + Uri::new(s).expect("Uri::From failed") } } impl PartialEq for Uri { fn eq(&self, other: &Uri) -> bool { - self.source == other.source + self.source.as_str() == other.source.as_str() } } +impl Eq for Uri {} + impl AsRef for Uri { fn as_ref(&self) -> &str { - &self.source + self.source.as_str() } } impl Default for Uri { fn default() -> Uri { Uri { - source: "/".into(), + source: InternalUri::Cow("/".into()), scheme_end: None, authority_end: None, query: None, @@ -245,16 +264,44 @@ impl Default for Uri { impl fmt::Debug for Uri { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.source.as_ref(), f) + fmt::Debug::fmt(self.as_ref(), f) } } impl Display for Uri { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(&self.source) + f.write_str(self.as_ref()) } } +pub fn from_mem_str(s: MemStr) -> Result { + Uri::new(InternalUri::Shared(s)) +} + +#[derive(Clone)] +enum InternalUri { + Cow(Cow<'static, str>), + Shared(MemStr), +} + +impl InternalUri { + fn as_str(&self) -> &str { + match *self { + InternalUri::Cow(ref s) => s.as_ref(), + InternalUri::Shared(ref m) => m.as_str(), + } + } +} + +impl Deref for InternalUri { + type Target = str; + + fn deref(&self) -> &str { + self.as_str() + } +} + + macro_rules! test_parse { ( $test_name:ident, @@ -263,7 +310,7 @@ macro_rules! test_parse { ) => ( #[test] fn $test_name() { - let uri = Uri::new($str).unwrap(); + let uri = Uri::from_str($str).unwrap(); $( assert_eq!(uri.$method(), $value); )+ @@ -368,7 +415,7 @@ test_parse! { #[test] fn test_uri_parse_error() { fn err(s: &str) { - Uri::new(s).unwrap_err(); + Uri::from_str(s).unwrap_err(); } err("http://");