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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
113
src/uri.rs
113
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
|
||||||
}
|
}
|
||||||
@@ -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://");
|
||||||
|
|||||||
Reference in New Issue
Block a user