Merge pull request #1054 from hyperium/uri-memslice
perf(header): Make Uri use MemSlice internally
This commit is contained in:
		| @@ -7,6 +7,7 @@ use http::{Body, RequestHead}; | |||||||
| use method::Method; | use method::Method; | ||||||
| use uri::Uri; | use uri::Uri; | ||||||
| use version::HttpVersion; | use version::HttpVersion; | ||||||
|  | use std::str::FromStr; | ||||||
|  |  | ||||||
| /// A client request to a remote server. | /// A client request to a remote server. | ||||||
| pub struct Request { | pub struct Request { | ||||||
| @@ -79,7 +80,7 @@ impl fmt::Debug for Request { | |||||||
| } | } | ||||||
|  |  | ||||||
| pub fn split(req: Request) -> (RequestHead, Option<Body>) { | pub fn split(req: Request) -> (RequestHead, Option<Body>) { | ||||||
|     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 { |     let head = RequestHead { | ||||||
|         subject: ::http::RequestLine(req.method, uri), |         subject: ::http::RequestLine(req.method, uri), | ||||||
|         headers: req.headers, |         headers: req.headers, | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ use std::fmt; | |||||||
| use std::io::{self, Read}; | use std::io::{self, Read}; | ||||||
| use std::ops::{Index, Range, RangeFrom, RangeTo, RangeFull}; | use std::ops::{Index, Range, RangeFrom, RangeTo, RangeFull}; | ||||||
| use std::ptr; | use std::ptr; | ||||||
|  | use std::str; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
|  |  | ||||||
| pub struct MemBuf { | pub struct MemBuf { | ||||||
| @@ -49,8 +50,8 @@ impl MemBuf { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn slice(&self, len: usize) -> MemSlice { |     pub fn slice(&self, len: usize) -> MemSlice { | ||||||
|         assert!(self.end - self.start.get() >= len); |  | ||||||
|         let start = self.start.get(); |         let start = self.start.get(); | ||||||
|  |         assert!(!(self.end - start < len)); | ||||||
|         let end = start + len; |         let end = start + len; | ||||||
|         self.start.set(end); |         self.start.set(end); | ||||||
|         MemSlice { |         MemSlice { | ||||||
| @@ -196,6 +197,19 @@ impl From<Vec<u8>> 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 { | pub struct MemSlice { | ||||||
|     buf: Arc<UnsafeCell<Vec<u8>>>, |     buf: Arc<UnsafeCell<Vec<u8>>>, | ||||||
|     start: usize, |     start: usize, | ||||||
|   | |||||||
| @@ -577,6 +577,8 @@ mod tests { | |||||||
|     use super::{Conn, Writing}; |     use super::{Conn, Writing}; | ||||||
|     use ::uri::Uri; |     use ::uri::Uri; | ||||||
|  |  | ||||||
|  |     use std::str::FromStr; | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_conn_init_read() { |     fn test_conn_init_read() { | ||||||
|         let good_message = b"GET / HTTP/1.1\r\n\r\n".to_vec(); |         let good_message = b"GET / HTTP/1.1\r\n\r\n".to_vec(); | ||||||
| @@ -587,7 +589,7 @@ mod tests { | |||||||
|         match conn.poll().unwrap() { |         match conn.poll().unwrap() { | ||||||
|             Async::Ready(Some(Frame::Message { message, body: false })) => { |             Async::Ready(Some(Frame::Message { message, body: false })) => { | ||||||
|                 assert_eq!(message, MessageHead { |                 assert_eq!(message, MessageHead { | ||||||
|                     subject: ::http::RequestLine(::Get, Uri::new("/").unwrap()), |                     subject: ::http::RequestLine(::Get, Uri::from_str("/").unwrap()), | ||||||
|                     .. MessageHead::default() |                     .. MessageHead::default() | ||||||
|                 }) |                 }) | ||||||
|             }, |             }, | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ use httparse; | |||||||
| use header::{self, Headers, ContentLength, TransferEncoding}; | use header::{self, Headers, ContentLength, TransferEncoding}; | ||||||
| use http::{MessageHead, RawStatus, Http1Transaction, ParseResult, ServerTransaction, ClientTransaction, RequestLine}; | use http::{MessageHead, RawStatus, Http1Transaction, ParseResult, ServerTransaction, ClientTransaction, RequestLine}; | ||||||
| use http::h1::{Encoder, Decoder}; | use http::h1::{Encoder, Decoder}; | ||||||
| use http::buf::{MemBuf, MemSlice}; | use http::buf::{MemBuf, MemSlice, MemStr}; | ||||||
| use method::Method; | use method::Method; | ||||||
| use status::StatusCode; | use status::StatusCode; | ||||||
| use version::HttpVersion::{Http10, Http11}; | use version::HttpVersion::{Http10, Http11}; | ||||||
| @@ -33,18 +33,26 @@ impl Http1Transaction for ServerTransaction { | |||||||
|         Ok(match try!(req.parse(buf.bytes())) { |         Ok(match try!(req.parse(buf.bytes())) { | ||||||
|             httparse::Status::Complete(len) => { |             httparse::Status::Complete(len) => { | ||||||
|                 trace!("Request.parse Complete({})", len); |                 trace!("Request.parse Complete({})", len); | ||||||
|                 let mut headers = Headers::with_capacity(req.headers.len()); |  | ||||||
|                 let slice = buf.slice(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.extend(HeadersAsMemSliceIter { | ||||||
|                     headers: req.headers.iter(), |                     headers: req.headers.iter(), | ||||||
|                     slice: slice, |                     slice: slice, | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|                 Some((MessageHead { |                 Some((MessageHead { | ||||||
|                     version: if req.version.unwrap() == 1 { Http11 } else { Http10 }, |                     version: if req.version.unwrap() == 1 { Http11 } else { Http10 }, | ||||||
|                     subject: RequestLine( |                     subject: subject, | ||||||
|                         try!(req.method.unwrap().parse()), |  | ||||||
|                         try!(req.path.unwrap().parse()) |  | ||||||
|                     ), |  | ||||||
|                     headers: headers, |                     headers: headers, | ||||||
|                 }, len)) |                 }, len)) | ||||||
|             } |             } | ||||||
| @@ -143,7 +151,7 @@ impl Http1Transaction for ClientTransaction { | |||||||
|                     headers: headers, |                     headers: headers, | ||||||
|                 }, len)) |                 }, len)) | ||||||
|             }, |             }, | ||||||
|             httparse::Status::Partial => None |             httparse::Status::Partial => None, | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -326,5 +334,4 @@ mod tests { | |||||||
|             raw.restart(); |             raw.restart(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										111
									
								
								src/uri.rs
									
									
									
									
									
								
							
							
						
						
									
										111
									
								
								src/uri.rs
									
									
									
									
									
								
							| @@ -1,6 +1,8 @@ | |||||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||||
| use std::fmt::{Display, self}; | 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; | ||||||
| use url::ParseError as UrlError; | use url::ParseError as UrlError; | ||||||
|  |  | ||||||
| @@ -30,7 +32,7 @@ use Error; | |||||||
| /// scheme          authority                 path            query         fragment | /// scheme          authority                 path            query         fragment | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct Uri { | pub struct Uri { | ||||||
|     source: Cow<'static, str>, |     source: InternalUri, | ||||||
|     scheme_end: Option<usize>, |     scheme_end: Option<usize>, | ||||||
|     authority_end: Option<usize>, |     authority_end: Option<usize>, | ||||||
|     query: Option<usize>, |     query: Option<usize>, | ||||||
| @@ -39,31 +41,32 @@ pub struct Uri { | |||||||
|  |  | ||||||
| impl Uri { | impl Uri { | ||||||
|     /// Parse a string into a `Uri`. |     /// Parse a string into a `Uri`. | ||||||
|     pub fn new(s: &str) -> Result<Uri, Error> { |     fn new(s: InternalUri) -> Result<Uri, Error> { | ||||||
|         let bytes = s.as_bytes(); |         if s.len() == 0 { | ||||||
|         if bytes.len() == 0 { |  | ||||||
|             Err(Error::Uri(UrlError::RelativeUrlWithoutBase)) |             Err(Error::Uri(UrlError::RelativeUrlWithoutBase)) | ||||||
|         } else if bytes == b"*" { |         } else if s.as_bytes() == b"*" { | ||||||
|             Ok(Uri { |             Ok(Uri { | ||||||
|                 source: "*".into(), |                 source: InternalUri::Cow("*".into()), | ||||||
|                 scheme_end: None, |                 scheme_end: None, | ||||||
|                 authority_end: None, |                 authority_end: None, | ||||||
|                 query: None, |                 query: None, | ||||||
|                 fragment: None, |                 fragment: None, | ||||||
|             }) |             }) | ||||||
|         } else if bytes == b"/" { |         } else if s.as_bytes() == b"/" { | ||||||
|             Ok(Uri::default()) |             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 { |             Ok(Uri { | ||||||
|                 source: s.to_owned().into(), |                 source: s, | ||||||
|                 scheme_end: None, |                 scheme_end: None, | ||||||
|                 authority_end: None, |                 authority_end: None, | ||||||
|                 query: parse_query(s), |                 query: query, | ||||||
|                 fragment: parse_fragment(s), |                 fragment: fragment, | ||||||
|             }) |             }) | ||||||
|         } else if s.contains("://") { |         } else if s.contains("://") { | ||||||
|             let scheme = parse_scheme(s); |             let scheme = parse_scheme(&s); | ||||||
|             let auth = parse_authority(s); |             let auth = parse_authority(&s); | ||||||
|             if let Some(end) = scheme { |             if let Some(end) = scheme { | ||||||
|                 match &s[..end] { |                 match &s[..end] { | ||||||
|                     "ftp" | "gopher" | "http" | "https" | "ws" | "wss" => {}, |                     "ftp" | "gopher" | "http" | "https" | "ws" | "wss" => {}, | ||||||
| @@ -79,20 +82,23 @@ impl Uri { | |||||||
|                     None => return Err(Error::Method), |                     None => return Err(Error::Method), | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             let query = parse_query(&s); | ||||||
|  |             let fragment = parse_fragment(&s); | ||||||
|             Ok(Uri { |             Ok(Uri { | ||||||
|                 source: s.to_owned().into(), |                 source: s, | ||||||
|                 scheme_end: scheme, |                 scheme_end: scheme, | ||||||
|                 authority_end: auth, |                 authority_end: auth, | ||||||
|                 query: parse_query(s), |                 query: query, | ||||||
|                 fragment: parse_fragment(s), |                 fragment: fragment, | ||||||
|             }) |             }) | ||||||
|         } else if (s.contains("/") || s.contains("?")) && !s.contains("://") { |         } else if (s.contains("/") || s.contains("?")) && !s.contains("://") { | ||||||
|             return Err(Error::Method) |             return Err(Error::Method) | ||||||
|         } else { |         } else { | ||||||
|  |             let len = s.len(); | ||||||
|             Ok(Uri { |             Ok(Uri { | ||||||
|                 source: s.to_owned().into(), |                 source: s, | ||||||
|                 scheme_end: None, |                 scheme_end: None, | ||||||
|                 authority_end: Some(s.len()), |                 authority_end: Some(len), | ||||||
|                 query: None, |                 query: None, | ||||||
|                 fragment: None, |                 fragment: None, | ||||||
|             }) |             }) | ||||||
| @@ -108,9 +114,10 @@ impl Uri { | |||||||
|             if fragment_len > 0 { fragment_len + 1 } else { 0 }; |             if fragment_len > 0 { fragment_len + 1 } else { 0 }; | ||||||
|         if index >= end { |         if index >= end { | ||||||
|             if self.scheme().is_some() { |             if self.scheme().is_some() { | ||||||
|                 return "/" // absolute-form MUST have path |                 "/" // absolute-form MUST have path | ||||||
|             } |             } else { | ||||||
|                 "" |                 "" | ||||||
|  |             } | ||||||
|         } else { |         } else { | ||||||
|             &self.source[index..end] |             &self.source[index..end] | ||||||
|         } |         } | ||||||
| @@ -158,7 +165,8 @@ impl Uri { | |||||||
|         let fragment_len = self.fragment.unwrap_or(0); |         let fragment_len = self.fragment.unwrap_or(0); | ||||||
|         let fragment_len = if fragment_len > 0 { fragment_len + 1 } else { 0 }; |         let fragment_len = if fragment_len > 0 { fragment_len + 1 } else { 0 }; | ||||||
|         if let Some(len) = self.query { |         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 { |         } else { | ||||||
|             None |             None | ||||||
|         } |         } | ||||||
| @@ -167,7 +175,7 @@ impl Uri { | |||||||
|     #[cfg(test)] |     #[cfg(test)] | ||||||
|     fn fragment(&self) -> Option<&str> { |     fn fragment(&self) -> Option<&str> { | ||||||
|         if let Some(len) = self.fragment { |         if let Some(len) = self.fragment { | ||||||
|             Some(&self.source[self.source.len() - len..]) |             Some(&self.source[self.source.len() - len..self.source.len()]) | ||||||
|         } else { |         } else { | ||||||
|             None |             None | ||||||
|         } |         } | ||||||
| @@ -209,32 +217,43 @@ impl FromStr for Uri { | |||||||
|     type Err = Error; |     type Err = Error; | ||||||
|  |  | ||||||
|     fn from_str(s: &str) -> Result<Uri, Error> { |     fn from_str(s: &str) -> Result<Uri, Error> { | ||||||
|         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<Url> for Uri { | impl From<Url> for Uri { | ||||||
|     fn from(url: Url) -> Uri { |     fn from(url: Url) -> Uri { | ||||||
|         Uri::new(url.as_str()).expect("Uri::From<Url> 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<Url> failed") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl PartialEq for Uri { | impl PartialEq for Uri { | ||||||
|     fn eq(&self, other: &Uri) -> bool { |     fn eq(&self, other: &Uri) -> bool { | ||||||
|         self.source == other.source |         self.source.as_str() == other.source.as_str() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl Eq for Uri {} | ||||||
|  |  | ||||||
| impl AsRef<str> for Uri { | impl AsRef<str> for Uri { | ||||||
|     fn as_ref(&self) -> &str { |     fn as_ref(&self) -> &str { | ||||||
|         &self.source |         self.source.as_str() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for Uri { | impl Default for Uri { | ||||||
|     fn default() -> Uri { |     fn default() -> Uri { | ||||||
|         Uri { |         Uri { | ||||||
|             source: "/".into(), |             source: InternalUri::Cow("/".into()), | ||||||
|             scheme_end: None, |             scheme_end: None, | ||||||
|             authority_end: None, |             authority_end: None, | ||||||
|             query: None, |             query: None, | ||||||
| @@ -245,16 +264,44 @@ impl Default for Uri { | |||||||
|  |  | ||||||
| impl fmt::Debug for Uri { | impl fmt::Debug for Uri { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     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 { | impl Display for Uri { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     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, Error> { | ||||||
|  |     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 { | macro_rules! test_parse { | ||||||
|     ( |     ( | ||||||
|         $test_name:ident, |         $test_name:ident, | ||||||
| @@ -263,7 +310,7 @@ macro_rules! test_parse { | |||||||
|     ) => ( |     ) => ( | ||||||
|         #[test] |         #[test] | ||||||
|         fn $test_name() { |         fn $test_name() { | ||||||
|             let uri = Uri::new($str).unwrap(); |             let uri = Uri::from_str($str).unwrap(); | ||||||
|             $( |             $( | ||||||
|             assert_eq!(uri.$method(), $value); |             assert_eq!(uri.$method(), $value); | ||||||
|             )+ |             )+ | ||||||
| @@ -368,7 +415,7 @@ test_parse! { | |||||||
| #[test] | #[test] | ||||||
| fn test_uri_parse_error() { | fn test_uri_parse_error() { | ||||||
|     fn err(s: &str) { |     fn err(s: &str) { | ||||||
|         Uri::new(s).unwrap_err(); |         Uri::from_str(s).unwrap_err(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     err("http://"); |     err("http://"); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user