feat(header): introduce header::Raw (#869)

The Raw type repesents the raw bytes of a header-value.

Having a special type allows a couple of benefits:

- The exact representation has become private, allowing "uglier"
internals. Specifically, since the common case is for a header to only
have 1 line of bytes, an enum is used to skip allocating a Vec for only
1 line. Additionally, a Cow<'static, [u8]> is used, so static bytes
don't require a copy. Finally, since we can use static bytes, when
parsing, we can compare the incoming bytes against a couple of the most
common header-values, and possibly remove another copy.

- As its own type, the `Headers.set_raw` method can be generic over
`Into<Raw>`, which allows for more ergnomic method calls.

BREAKING CHANGE: `Header::parse_header` now receives `&Raw`, instead of
  a `&[Vec<u8>]`. `Raw` provides several methods to ease using it, but
  may require some changes to existing code.
This commit is contained in:
Sean McArthur
2016-07-23 12:54:16 -07:00
committed by GitHub
parent d67dbc6028
commit 50ccdaa7e7
27 changed files with 489 additions and 245 deletions

229
src/header/raw.rs Normal file
View File

@@ -0,0 +1,229 @@
use std::borrow::Cow;
use std::fmt;
/// A raw header value.
#[derive(Clone, PartialEq, Eq)]
pub struct Raw(Lines);
impl Raw {
/// Returns the amount of lines.
#[inline]
pub fn len(&self) -> usize {
match self.0 {
Lines::One(..) => 1,
Lines::Many(ref lines) => lines.len()
}
}
/// Returns the line if there is only 1.
#[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]),
_ => None
}
}
/// Iterate the lines of raw bytes.
#[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()
}
}
}
/// Append a line to this `Raw` header value.
pub fn push(&mut self, val: &[u8]) {
let lines = ::std::mem::replace(&mut self.0, Lines::Many(Vec::new()));
match lines {
Lines::One(line) => {
self.0 = Lines::Many(vec![line, maybe_literal(val.into())]);
}
Lines::Many(mut lines) => {
lines.push(maybe_literal(val.into()));
self.0 = Lines::Many(lines);
}
}
}
}
#[derive(Clone, PartialEq, Eq)]
enum Lines {
One(Line),
Many(Vec<Line>)
}
type Line = Cow<'static, [u8]>;
fn eq<A: AsRef<[u8]>, B: AsRef<[u8]>>(a: &[A], b: &[B]) -> bool {
if a.len() != b.len() {
false
} else {
for (a, b) in a.iter().zip(b.iter()) {
if a.as_ref() != b.as_ref() {
return false
}
}
true
}
}
impl PartialEq<[Vec<u8>]> for Raw {
fn eq(&self, bytes: &[Vec<u8>]) -> bool {
match self.0 {
Lines::One(ref line) => eq(&[line], bytes),
Lines::Many(ref lines) => eq(lines, bytes)
}
}
}
impl PartialEq<[u8]> for Raw {
fn eq(&self, bytes: &[u8]) -> bool {
match self.0 {
Lines::One(ref line) => line.as_ref() == bytes,
Lines::Many(..) => false
}
}
}
impl PartialEq<str> for Raw {
fn eq(&self, s: &str) -> bool {
match self.0 {
Lines::One(ref line) => line.as_ref() == s.as_bytes(),
Lines::Many(..) => false
}
}
}
impl From<Vec<Vec<u8>>> for Raw {
#[inline]
fn from(val: Vec<Vec<u8>>) -> Raw {
Raw(Lines::Many(
val.into_iter()
.map(|vec| maybe_literal(vec.into()))
.collect()
))
}
}
impl From<String> for Raw {
#[inline]
fn from(val: String) -> Raw {
let vec: Vec<u8> = val.into();
vec.into()
}
}
impl From<Vec<u8>> for Raw {
#[inline]
fn from(val: Vec<u8>) -> Raw {
Raw(Lines::One(Cow::Owned(val)))
}
}
impl From<&'static str> for Raw {
fn from(val: &'static str) -> Raw {
Raw(Lines::One(Cow::Borrowed(val.as_bytes())))
}
}
impl From<&'static [u8]> for Raw {
fn from(val: &'static [u8]) -> Raw {
Raw(Lines::One(Cow::Borrowed(val)))
}
}
pub fn parsed(val: &[u8]) -> Raw {
Raw(Lines::One(maybe_literal(val.into())))
}
impl fmt::Debug for Raw {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
Lines::One(ref line) => fmt::Debug::fmt(&[line], f),
Lines::Many(ref lines) => fmt::Debug::fmt(lines, f)
}
}
}
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 {
line.as_ref()
} else {
panic!("index out of bounds: {}", idx)
},
Lines::Many(ref lines) => lines[idx].as_ref()
}
}
}
macro_rules! literals {
($($len:expr => $($value:expr),+;)+) => (
fn maybe_literal<'a>(s: Cow<'a, [u8]>) -> Cow<'static, [u8]> {
match s.len() {
$($len => {
$(
if s.as_ref() == $value {
return Cow::Borrowed($value);
}
)+
})+
_ => ()
}
Cow::Owned(s.into_owned())
}
#[test]
fn test_literal_lens() {
$(
$({
let s = $value;
assert!(s.len() == $len, "{:?} has len of {}, listed as {}", s, s.len(), $len);
})+
)+
}
);
}
literals! {
1 => b"*", b"0";
3 => b"*/*";
4 => b"gzip";
5 => b"close";
7 => b"chunked";
10 => b"keep-alive";
}
impl<'a> IntoIterator for &'a Raw {
type IntoIter = RawLines<'a>;
type Item = &'a [u8];
fn into_iter(self) -> RawLines<'a> {
self.iter()
}
}
#[derive(Debug)]
pub struct RawLines<'a> {
inner: ::std::slice::Iter<'a, Cow<'static, [u8]>>
}
impl<'a> Iterator for RawLines<'a> {
type Item = &'a [u8];
#[inline]
fn next(&mut self) -> Option<&'a [u8]> {
self.inner.next().map(AsRef::as_ref)
}
}