perf(header): use MemSlice when parsing headers

This commit is contained in:
Guillaume Gomez
2017-01-22 13:33:47 +01:00
committed by Sean McArthur
parent 5c890321ee
commit 1b556389c0
9 changed files with 291 additions and 76 deletions

View File

@@ -40,6 +40,7 @@ impl Header for ContentLength {
static NAME: &'static str = "Content-Length";
NAME
}
fn parse_header(raw: &Raw) -> ::Result<ContentLength> {
// If multiple Content-Length headers were sent, everything can still
// be alright if they all contain the same value, and all parse
@@ -49,9 +50,9 @@ impl Header for ContentLength {
.fold(None, |prev, x| {
match (prev, x) {
(None, x) => Some(x),
(e@Some(Err(_)), _ ) => e,
(e @ Some(Err(_)), _ ) => e,
(Some(Ok(prev)), Ok(x)) if prev == x => Some(Ok(prev)),
_ => Some(Err(::Error::Header))
_ => Some(Err(::Error::Header)),
}
})
.unwrap_or(Err(::Error::Header))

View File

@@ -179,14 +179,14 @@ mod tests {
#[test]
fn hyper_headers_from_raw_delay() {
let headers = Headers::from_raw(&[httparse::Header { name: "Retry-After", value: b"300" }]).unwrap();
let headers = make_header!(b"Retry-After", b"300");
let retry_after = headers.get::<RetryAfter>().unwrap();
assert_eq!(retry_after, &RetryAfter::Delay(Duration::seconds(300)));
}
#[test]
fn hyper_headers_from_raw_datetime() {
let headers = Headers::from_raw(&[httparse::Header { name: "Retry-After", value: b"Sun, 06 Nov 1994 08:49:37 GMT" }]).unwrap();
let headers = make_header!(b"Retry-After", b"Sun, 06 Nov 1994 08:49:37 GMT");
let retry_after = headers.get::<RetryAfter>().unwrap();
let expected = "Sun, 06 Nov 1994 08:49:37 GMT".parse::<HttpDate>().unwrap();

View File

@@ -88,6 +88,7 @@ use self::internals::{Item, VecMap, Entry};
pub use self::shared::*;
pub use self::common::*;
pub use self::raw::Raw;
use http::buf::MemSlice;
mod common;
mod internals;
@@ -353,20 +354,20 @@ impl Headers {
}
#[doc(hidden)]
pub fn from_raw(raw: &[httparse::Header]) -> ::Result<Headers> {
pub fn from_raw(raw: &[httparse::Header], buf: MemSlice) -> ::Result<Headers> {
let mut headers = Headers::new();
for header in raw {
trace!("raw header: {:?}={:?}", header.name, &header.value[..]);
let name = HeaderName(UniCase(maybe_literal(header.name)));
let trim = header.value.iter().rev().take_while(|&&x| x == b' ').count();
let value = &header.value[.. header.value.len() - trim];
let value_start = header.value.as_ptr() as usize - buf.get().as_ptr() as usize;
let value_end = value_start + header.value.len() - trim;
match headers.data.entry(name) {
Entry::Vacant(entry) => {
entry.insert(Item::new_raw(self::raw::parsed(value)));
entry.insert(Item::new_raw(self::raw::parsed(buf.slice(value_start..value_end))));
}
Entry::Occupied(entry) => {
entry.into_mut().mut_raw().push(value);
entry.into_mut().mut_raw().push_slice(buf.slice(value_start..value_end));
}
};
}
@@ -663,24 +664,23 @@ mod tests {
#[cfg(feature = "nightly")]
use test::Bencher;
// Slice.position_elem was unstable
fn index_of(slice: &[u8], byte: u8) -> Option<usize> {
for (index, &b) in slice.iter().enumerate() {
if b == byte {
return Some(index);
}
}
None
}
macro_rules! raw {
($($line:expr),*) => ({
[$({
let line = $line;
let pos = index_of(line, b':').expect("raw splits on ':', not found");
// Slice.position_elem was unstable
fn index_of(slice: &MemSlice, byte: u8) -> Option<usize> {
for (index, &b) in slice.as_ref().iter().enumerate() {
if b == byte {
return Some(index);
}
}
None
}
let pos = index_of(&$line, b':').expect("raw splits on ':', not found");
httparse::Header {
name: ::std::str::from_utf8(&line[..pos]).unwrap(),
value: &line[pos + 2..]
name: ::std::str::from_utf8(&$line[..pos]).unwrap(),
value: &$line[pos + 2..]
}
}),*]
})
@@ -688,7 +688,7 @@ mod tests {
#[test]
fn test_from_raw() {
let headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
let headers = make_header!(b"Content-Length", b"10");
assert_eq!(headers.get(), Some(&ContentLength(10)));
}
@@ -738,20 +738,20 @@ mod tests {
#[test]
fn test_different_structs_for_same_header() {
let headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
let headers = make_header!(b"Content-Length: 10");
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(&raw!(b"Content-Length: 10 ")).unwrap();
let headers = make_header!(b"Content-Length: 10 ");
assert_eq!(headers.get::<ContentLength>(), Some(&ContentLength(10)));
}
#[test]
fn test_multiple_reads() {
let headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
let headers = make_header!(b"Content-Length: 10");
let ContentLength(one) = *headers.get::<ContentLength>().unwrap();
let ContentLength(two) = *headers.get::<ContentLength>().unwrap();
assert_eq!(one, two);
@@ -759,15 +759,14 @@ mod tests {
#[test]
fn test_different_reads() {
let headers = Headers::from_raw(
&raw!(b"Content-Length: 10", b"Content-Type: text/plain")).unwrap();
let headers = make_header!(b"Content-Length: 10\r\nContent-Type: text/plain");
let ContentLength(_) = *headers.get::<ContentLength>().unwrap();
let ContentType(_) = *headers.get::<ContentType>().unwrap();
}
#[test]
fn test_get_mutable() {
let mut headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
let mut headers = make_header!(b"Content-Length: 10");
*headers.get_mut::<ContentLength>().unwrap() = ContentLength(20);
assert_eq!(headers.get_raw("content-length").unwrap(), &[b"20".to_vec()][..]);
assert_eq!(*headers.get::<ContentLength>().unwrap(), ContentLength(20));
@@ -786,7 +785,7 @@ mod tests {
#[test]
fn test_headers_to_string_raw() {
let mut headers = Headers::from_raw(&raw!(b"Content-Length: 10")).unwrap();
let mut headers = make_header!(b"Content-Length: 10");
headers.set_raw("x-foo", vec![b"foo".to_vec(), b"bar".to_vec()]);
let s = headers.to_string();
assert_eq!(s, "Content-Length: 10\r\nx-foo: foo\r\nx-foo: bar\r\n");
@@ -903,8 +902,12 @@ mod tests {
#[cfg(feature = "nightly")]
#[bench]
fn bench_headers_from_raw(b: &mut Bencher) {
let raw = raw!(b"Content-Length: 10");
b.iter(|| Headers::from_raw(&raw).unwrap())
use ::http::buf::MemSlice;
let buf = MemSlice::from(b"Content-Length: 10" as &[u8]);
let buf_clone = buf.clone();
let raw = raw!(buf_clone);
b.iter(|| Headers::from_raw(&raw, buf.clone()).unwrap())
}
#[cfg(feature = "nightly")]

View File

@@ -1,5 +1,6 @@
use std::borrow::Cow;
use std::fmt;
use http::buf::MemSlice;
/// A raw header value.
#[derive(Clone, PartialEq, Eq)]
@@ -19,8 +20,8 @@ impl Raw {
#[inline]
pub fn one(&self) -> Option<&[u8]> {
match self.0 {
Lines::One(ref line) => Some(line),
Lines::Many(ref lines) if lines.len() == 1 => Some(&lines[0]),
Lines::One(ref line) => Some(line.as_ref()),
Lines::Many(ref lines) if lines.len() == 1 => Some(lines[0].as_ref()),
_ => None
}
}
@@ -29,12 +30,8 @@ impl Raw {
#[inline]
pub fn iter(&self) -> RawLines {
RawLines {
inner: match self.0 {
Lines::One(ref line) => unsafe {
::std::slice::from_raw_parts(line, 1)
}.iter(),
Lines::Many(ref lines) => lines.iter()
}
inner: &self.0,
pos: 0,
}
}
@@ -51,15 +48,34 @@ impl Raw {
}
}
}
#[doc(hidden)]
pub fn push_slice(&mut self, val: MemSlice) {
let lines = ::std::mem::replace(&mut self.0, Lines::Many(Vec::new()));
match lines {
Lines::One(line) => {
self.0 = Lines::Many(vec![line, Line::Shared(val)]);
}
Lines::Many(mut lines) => {
lines.push(Line::Shared(val));
self.0 = Lines::Many(lines);
}
}
}
}
#[derive(Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
enum Lines {
One(Line),
Many(Vec<Line>)
Many(Vec<Line>),
}
type Line = Cow<'static, [u8]>;
#[derive(Debug, Clone, PartialEq, Eq)]
enum Line {
Static(&'static [u8]),
Owned(Vec<u8>),
Shared(MemSlice),
}
fn eq<A: AsRef<[u8]>, B: AsRef<[u8]>>(a: &[A], b: &[B]) -> bool {
if a.len() != b.len() {
@@ -123,24 +139,55 @@ impl From<String> for Raw {
impl From<Vec<u8>> for Raw {
#[inline]
fn from(val: Vec<u8>) -> Raw {
Raw(Lines::One(Cow::Owned(val)))
Raw(Lines::One(Line::from(val)))
}
}
impl From<&'static str> for Raw {
fn from(val: &'static str) -> Raw {
Raw(Lines::One(Cow::Borrowed(val.as_bytes())))
Raw(Lines::One(Line::Static(val.as_bytes())))
}
}
impl From<&'static [u8]> for Raw {
fn from(val: &'static [u8]) -> Raw {
Raw(Lines::One(Cow::Borrowed(val)))
Raw(Lines::One(Line::Static(val)))
}
}
pub fn parsed(val: &[u8]) -> Raw {
Raw(Lines::One(maybe_literal(val.into())))
impl From<MemSlice> for Raw {
#[inline]
fn from(val: MemSlice) -> Raw {
Raw(Lines::One(Line::Shared(val)))
}
}
impl From<Vec<u8>> for Line {
#[inline]
fn from(val: Vec<u8>) -> Line {
Line::Owned(val)
}
}
impl From<MemSlice> for Line {
#[inline]
fn from(val: MemSlice) -> Line {
Line::Shared(val)
}
}
impl AsRef<[u8]> for Line {
fn as_ref(&self) -> &[u8] {
match *self {
Line::Static(ref s) => s,
Line::Owned(ref v) => v.as_ref(),
Line::Shared(ref m) => m.as_ref(),
}
}
}
pub fn parsed(val: MemSlice) -> Raw {
Raw(Lines::One(From::from(val)))
}
impl fmt::Debug for Raw {
@@ -154,6 +201,7 @@ impl fmt::Debug for Raw {
impl ::std::ops::Index<usize> for Raw {
type Output = [u8];
fn index(&self, idx: usize) -> &[u8] {
match self.0 {
Lines::One(ref line) => if idx == 0 {
@@ -168,12 +216,12 @@ impl ::std::ops::Index<usize> for Raw {
macro_rules! literals {
($($len:expr => $($value:expr),+;)+) => (
fn maybe_literal<'a>(s: Cow<'a, [u8]>) -> Cow<'static, [u8]> {
fn maybe_literal<'a>(s: Cow<'a, [u8]>) -> Line {
match s.len() {
$($len => {
$(
if s.as_ref() == $value {
return Cow::Borrowed($value);
return Line::Static($value);
}
)+
})+
@@ -181,7 +229,7 @@ macro_rules! literals {
_ => ()
}
Cow::Owned(s.into_owned())
Line::from(s.into_owned())
}
#[test]
@@ -216,7 +264,8 @@ impl<'a> IntoIterator for &'a Raw {
#[derive(Debug)]
pub struct RawLines<'a> {
inner: ::std::slice::Iter<'a, Cow<'static, [u8]>>
inner: &'a Lines,
pos: usize,
}
impl<'a> Iterator for RawLines<'a> {
@@ -224,6 +273,17 @@ impl<'a> Iterator for RawLines<'a> {
#[inline]
fn next(&mut self) -> Option<&'a [u8]> {
self.inner.next().map(AsRef::as_ref)
let current_pos = self.pos;
self.pos += 1;
match *self.inner {
Lines::One(ref line) => {
if current_pos == 0 {
Some(line.as_ref())
} else {
None
}
}
Lines::Many(ref lines) => lines.get(current_pos).map(|l| l.as_ref()),
}
}
}