Merge pull request #1054 from hyperium/uri-memslice

perf(header): Make Uri use MemSlice internally
This commit is contained in:
Sean McArthur
2017-02-09 12:41:53 -08:00
committed by GitHub
5 changed files with 115 additions and 44 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
} }
@@ -180,7 +188,7 @@ fn parse_scheme(s: &str) -> Option<usize> {
fn parse_authority(s: &str) -> Option<usize> { fn parse_authority(s: &str) -> Option<usize> {
let i = s.find("://").and_then(|p| Some(p + 3)).unwrap_or(0); let i = s.find("://").and_then(|p| Some(p + 3)).unwrap_or(0);
Some(&s[i..].split("/") Some(&s[i..].split("/")
.next() .next()
.unwrap_or(s) .unwrap_or(s)
@@ -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://");