feat(headers): adds re-parsing ability when getting typed headers

BREAKING CHANGE: added requirement that all HeaderFormat implementations
  must also be fmt::Debug. This likely as easy as slapping
  #[derive(Debug)] on to any custom headers.
This commit is contained in:
Sean McArthur
2015-02-05 17:09:02 -08:00
parent 9e0770896b
commit df756871ed
10 changed files with 258 additions and 240 deletions

View File

@@ -19,5 +19,4 @@ openssl = "*"
rustc-serialize = "*"
time = "*"
unicase = "*"
unsafe-any = "*"
url = "*"

View File

@@ -50,7 +50,7 @@ impl Write for MockStream {
}
}
#[derive(Clone)]
#[derive(Clone, Debug)]
struct Foo;
impl hyper::header::Header for Foo {

View File

@@ -1,41 +0,0 @@
use std::cell::UnsafeCell;
use std::ops::Deref;
pub struct OptCell<T>(UnsafeCell<Option<T>>);
impl<T> OptCell<T> {
#[inline]
pub fn new(val: Option<T>) -> OptCell<T> {
OptCell(UnsafeCell::new(val))
}
#[inline]
pub fn set(&self, val: T) {
unsafe {
let opt = self.0.get();
debug_assert!((*opt).is_none());
*opt = Some(val)
}
}
#[inline]
pub unsafe fn get_mut(&mut self) -> &mut T {
let opt = &mut *self.0.get();
opt.as_mut().unwrap()
}
}
impl<T> Deref for OptCell<T> {
type Target = Option<T>;
#[inline]
fn deref<'a>(&'a self) -> &'a Option<T> {
unsafe { &*self.0.get() }
}
}
impl<T: Clone> Clone for OptCell<T> {
#[inline]
fn clone(&self) -> OptCell<T> {
OptCell::new((**self).clone())
}
}

View File

@@ -54,7 +54,7 @@ impl<S: Scheme + 'static> HeaderFormat for Authorization<S> where <S as FromStr>
}
/// An Authorization scheme to be used in the header.
pub trait Scheme: FromStr + Clone + Send + Sync {
pub trait Scheme: FromStr + fmt::Debug + Clone + Send + Sync {
/// An optional Scheme name.
///
/// For example, `Basic asdf` has the name `Basic`. The Option<Self> is

View File

@@ -0,0 +1,131 @@
use std::any::{Any, TypeId};
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::fmt;
use std::mem;
use std::ops::Deref;
pub struct OptCell<T>(UnsafeCell<Option<T>>);
impl<T> OptCell<T> {
#[inline]
pub fn new(val: Option<T>) -> OptCell<T> {
OptCell(UnsafeCell::new(val))
}
#[inline]
pub fn set(&self, val: T) {
unsafe {
let opt = self.0.get();
debug_assert!((*opt).is_none());
*opt = Some(val)
}
}
#[inline]
pub unsafe fn get_mut(&mut self) -> &mut T {
let opt = &mut *self.0.get();
opt.as_mut().unwrap()
}
}
impl<T> Deref for OptCell<T> {
type Target = Option<T>;
#[inline]
fn deref<'a>(&'a self) -> &'a Option<T> {
unsafe { &*self.0.get() }
}
}
impl<T: Clone> Clone for OptCell<T> {
#[inline]
fn clone(&self) -> OptCell<T> {
OptCell::new((**self).clone())
}
}
pub struct PtrMapCell<V: ?Sized>(UnsafeCell<PtrMap<Box<V>>>);
#[derive(Clone, Debug)]
enum PtrMap<T> {
Empty,
One(TypeId, T),
Many(HashMap<TypeId, T>)
}
impl<V: ?Sized + fmt::Debug + Any + 'static> PtrMapCell<V> {
#[inline]
pub fn new() -> PtrMapCell<V> {
PtrMapCell(UnsafeCell::new(PtrMap::Empty))
}
#[inline]
pub fn get(&self, key: TypeId) -> Option<&V> {
let map = unsafe { &*self.0.get() };
match *map {
PtrMap::Empty => None,
PtrMap::One(id, ref v) => if id == key {
Some(v)
} else {
None
},
PtrMap::Many(ref hm) => hm.get(&key)
}.map(|val| &**val)
}
#[inline]
pub fn get_mut(&mut self, key: TypeId) -> Option<&mut V> {
let mut map = unsafe { &mut *self.0.get() };
match *map {
PtrMap::Empty => None,
PtrMap::One(id, ref mut v) => if id == key {
Some(v)
} else {
None
},
PtrMap::Many(ref mut hm) => hm.get_mut(&key)
}.map(|val| &mut **val)
}
#[inline]
pub unsafe fn insert(&self, key: TypeId, val: Box<V>) {
let mut map = &mut *self.0.get();
match *map {
PtrMap::Empty => *map = PtrMap::One(key, val),
PtrMap::One(..) => {
let one = mem::replace(map, PtrMap::Empty);
match one {
PtrMap::One(id, one) => {
debug_assert!(id != key);
let mut hm = HashMap::with_capacity(2);
hm.insert(id, one);
hm.insert(key, val);
mem::replace(map, PtrMap::Many(hm));
},
_ => unreachable!()
}
},
PtrMap::Many(ref mut hm) => { hm.insert(key, val); }
}
}
#[inline]
pub unsafe fn one(&self) -> &V {
let map = &*self.0.get();
match *map {
PtrMap::One(_, ref one) => one,
_ => panic!("not PtrMap::One value, {:?}", *map)
}
}
}
impl<V: ?Sized + fmt::Debug + Any + 'static> Clone for PtrMapCell<V> where Box<V>: Clone {
#[inline]
fn clone(&self) -> PtrMapCell<V> {
let cell = PtrMapCell::new();
unsafe {
*cell.0.get() = (&*self.0.get()).clone()
}
cell
}
}

View File

@@ -0,0 +1,107 @@
use std::any::TypeId;
use std::fmt;
use std::str::from_utf8;
use super::cell::{OptCell, PtrMapCell};
use header::{Header, HeaderFormat};
#[derive(Clone)]
pub struct Item {
raw: OptCell<Vec<Vec<u8>>>,
typed: PtrMapCell<HeaderFormat + Send + Sync>
}
impl Item {
#[inline]
pub fn new_raw(data: Vec<Vec<u8>>) -> Item {
Item {
raw: OptCell::new(Some(data)),
typed: PtrMapCell::new(),
}
}
#[inline]
pub fn new_typed(ty: Box<HeaderFormat + Send + Sync>) -> Item {
let map = PtrMapCell::new();
unsafe { map.insert((&*ty).get_type_id(), ty); }
Item {
raw: OptCell::new(None),
typed: map,
}
}
#[inline]
pub fn mut_raw(&mut self) -> &mut Vec<Vec<u8>> {
self.typed = PtrMapCell::new();
unsafe {
self.raw.get_mut()
}
}
pub fn raw(&self) -> &[Vec<u8>] {
if let Some(ref raw) = *self.raw {
return &raw[..];
}
let raw = vec![unsafe { self.typed.one() }.to_string().into_bytes()];
self.raw.set(raw);
let raw = self.raw.as_ref().unwrap();
&raw[..]
}
pub fn typed<H: Header + HeaderFormat>(&self) -> Option<&H> {
let tid = TypeId::of::<H>();
match self.typed.get(tid) {
Some(val) => Some(val),
None => {
match parse::<H>(self.raw.as_ref().expect("item.raw must exist")) {
Some(typed) => {
unsafe { self.typed.insert(tid, typed); }
self.typed.get(tid)
},
None => None
}
}
}.map(|typed| unsafe { typed.downcast_ref_unchecked() })
}
pub fn typed_mut<H: Header + HeaderFormat>(&mut self) -> Option<&mut H> {
let tid = TypeId::of::<H>();
if self.typed.get_mut(tid).is_none() {
match parse::<H>(self.raw.as_ref().expect("item.raw must exist")) {
Some(typed) => {
unsafe { self.typed.insert(tid, typed); }
},
None => ()
}
}
self.typed.get_mut(tid).map(|typed| unsafe { typed.downcast_mut_unchecked() })
}
}
#[inline]
fn parse<H: Header + HeaderFormat>(raw: &Vec<Vec<u8>>) -> Option<Box<HeaderFormat + Send + Sync>> {
Header::parse_header(&raw[..]).map(|h: H| box h as Box<HeaderFormat + Send + Sync>)
}
impl fmt::Display for Item {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self.raw {
Some(ref raw) => {
for part in raw.iter() {
match from_utf8(&part[..]) {
Ok(s) => try!(fmt.write_str(s)),
Err(e) => {
error!("raw header value is not utf8. header={:?}, error={:?}", part, e);
return Err(fmt::Error);
}
}
}
Ok(())
},
None => fmt::Display::fmt(&unsafe { self.typed.one() }, fmt)
}
}
}

View File

@@ -0,0 +1,4 @@
pub use self::item::Item;
mod cell;
mod item;

View File

@@ -4,29 +4,27 @@
//! why we're using Rust in the first place. To set or get any header, an object
//! must implement the `Header` trait from this module. Several common headers
//! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others.
use std::any::{Any, TypeId};
use std::any::Any;
use std::borrow::Cow::{Borrowed, Owned};
use std::fmt;
use std::io::Read;
use std::raw::TraitObject;
use std::str::from_utf8;
use std::collections::HashMap;
use std::collections::hash_map::{Iter, Entry};
use std::iter::{FromIterator, IntoIterator};
use std::borrow::{Cow, IntoCow};
use std::{mem, raw};
use uany::{UnsafeAnyExt};
use unicase::UniCase;
use self::cell::OptCell;
use self::internals::Item;
use {http, HttpResult, HttpError};
pub use self::shared::{Encoding, EntityTag, Quality, QualityItem, qitem};
pub use self::common::*;
mod cell;
mod common;
mod internals;
mod shared;
pub mod parsing;
@@ -55,7 +53,7 @@ pub trait Header: Clone + Any + Send + Sync {
/// A trait for any object that will represent a header field and value.
///
/// This trait represents the formatting of a Header for output to a TcpStream.
pub trait HeaderFormat: HeaderClone + Any + Send + Sync {
pub trait HeaderFormat: fmt::Debug + HeaderClone + Any + Send + Sync {
/// Format a header to be output into a TcpStream.
///
/// This method is not allowed to introduce an Err not produced
@@ -77,13 +75,6 @@ impl<T: HeaderFormat + Send + Sync + Clone> HeaderClone for T {
}
impl HeaderFormat {
#[inline]
fn is<T: 'static>(&self) -> bool {
self.get_type_id() == TypeId::of::<T>()
}
}
impl UnsafeAnyExt for HeaderFormat {
#[inline]
unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T {
mem::transmute(mem::transmute::<&HeaderFormat, raw::TraitObject>(self).data)
@@ -93,11 +84,6 @@ impl UnsafeAnyExt for HeaderFormat {
unsafe fn downcast_mut_unchecked<T: 'static>(&mut self) -> &mut T {
mem::transmute(mem::transmute::<&mut HeaderFormat, raw::TraitObject>(self).data)
}
#[inline]
unsafe fn downcast_unchecked<T: 'static>(self: Box<HeaderFormat>) -> Box<T> {
mem::transmute(mem::transmute::<Box<HeaderFormat>, raw::TraitObject>(self).data)
}
}
impl Clone for Box<HeaderFormat + Send + Sync> {
@@ -179,19 +165,8 @@ impl Headers {
/// ```
pub fn get_raw(&self, name: &str) -> Option<&[Vec<u8>]> {
self.data
// FIXME(reem): Find a better way to do this lookup without find_equiv.
.get(&UniCase(Borrowed(unsafe { mem::transmute::<&str, &str>(name) })))
.and_then(|item| {
if let Some(ref raw) = *item.raw {
return Some(&raw[..]);
}
let raw = vec![item.typed.as_ref().unwrap().to_string().into_bytes()];
item.raw.set(raw);
let raw = item.raw.as_ref().unwrap();
Some(&raw[..])
})
.map(Item::raw)
}
/// Set the raw value of a header, bypassing any typed headers.
@@ -214,26 +189,12 @@ impl Headers {
/// Get a reference to the header field's value, if it exists.
pub fn get<H: Header + HeaderFormat>(&self) -> Option<&H> {
self.get_or_parse::<H>().map(|item| {
unsafe {
downcast(&*item)
}
})
self.data.get(&UniCase(Borrowed(header_name::<H>()))).and_then(Item::typed::<H>)
}
/// Get a mutable reference to the header field's value, if it exists.
pub fn get_mut<H: Header + HeaderFormat>(&mut self) -> Option<&mut H> {
self.get_or_parse_mut::<H>().map(|item| {
unsafe { downcast_mut(item) }
})
}
fn get_or_parse<H: Header + HeaderFormat>(&self) -> Option<&Item> {
self.data.get(&UniCase(Borrowed(header_name::<H>()))).and_then(get_or_parse::<H>)
}
fn get_or_parse_mut<H: Header + HeaderFormat>(&mut self) -> Option<&mut Item> {
self.data.get_mut(&UniCase(Borrowed(header_name::<H>()))).and_then(get_or_parse_mut::<H>)
self.data.get_mut(&UniCase(Borrowed(header_name::<H>()))).and_then(Item::typed_mut::<H>)
}
/// Returns a boolean of whether a certain header is in the map.
@@ -329,11 +290,7 @@ impl<'a> HeaderView<'a> {
/// Cast the value to a certain Header type.
#[inline]
pub fn value<H: Header + HeaderFormat>(&self) -> Option<&'a H> {
get_or_parse::<H>(self.1).map(|item| {
unsafe {
downcast(&*item)
}
})
self.1.typed::<H>()
}
/// Get just the header value as a String.
@@ -371,141 +328,7 @@ impl<'a> FromIterator<HeaderView<'a>> for Headers {
}
}
#[derive(Clone)]
struct Item {
raw: OptCell<Vec<Vec<u8>>>,
typed: OptCell<Box<HeaderFormat + Send + Sync>>
}
impl Item {
#[inline]
fn new_raw(data: Vec<Vec<u8>>) -> Item {
Item {
raw: OptCell::new(Some(data)),
typed: OptCell::new(None),
}
}
#[inline]
fn new_typed(ty: Box<HeaderFormat + Send + Sync>) -> Item {
Item {
raw: OptCell::new(None),
typed: OptCell::new(Some(ty)),
}
}
#[inline]
fn mut_raw(&mut self) -> &mut Vec<Vec<u8>> {
self.typed = OptCell::new(None);
unsafe {
self.raw.get_mut()
}
}
#[inline]
fn mut_typed(&mut self) -> &mut Box<HeaderFormat + Send + Sync> {
self.raw = OptCell::new(None);
unsafe {
self.typed.get_mut()
}
}
}
fn get_or_parse<H: Header + HeaderFormat>(item: &Item) -> Option<&Item> {
match *item.typed {
Some(ref typed) if typed.is::<H>() => return Some(item),
Some(ref typed) => {
warn!("attempted to access {:?} as wrong type", typed);
return None;
}
_ => ()
}
parse::<H>(item);
if item.typed.is_some() {
Some(item)
} else {
None
}
}
fn get_or_parse_mut<H: Header + HeaderFormat>(item: &mut Item) -> Option<&mut Item> {
let is_correct_type = match *item.typed {
Some(ref typed) if typed.is::<H>() => Some(true),
Some(ref typed) => {
warn!("attempted to access {:?} as wrong type", typed);
Some(false)
}
_ => None
};
match is_correct_type {
Some(true) => return Some(item),
Some(false) => return None,
None => ()
}
parse::<H>(&item);
if item.typed.is_some() {
Some(item)
} else {
None
}
}
fn parse<H: Header + HeaderFormat>(item: &Item) {
match *item.raw {
Some(ref raw) => match Header::parse_header(&raw[..]) {
Some::<H>(h) => item.typed.set(box h as Box<HeaderFormat + Send + Sync>),
None => ()
},
None => unreachable!()
}
}
#[inline]
unsafe fn downcast<H: Header + HeaderFormat>(item: &Item) -> &H {
item.typed.as_ref().expect("item.typed must be set").downcast_ref_unchecked()
}
#[inline]
unsafe fn downcast_mut<H: Header + HeaderFormat>(item: &mut Item) -> &mut H {
item.mut_typed().downcast_mut_unchecked()
}
impl fmt::Display for Item {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self.typed {
Some(ref h) => h.fmt_header(fmt),
None => match *self.raw {
Some(ref raw) => {
for part in raw.iter() {
match from_utf8(&part[..]) {
Ok(s) => try!(fmt.write_str(s)),
Err(e) => {
error!("raw header value is not utf8. header={:?}, error={:?}", part, e);
return Err(fmt::Error);
}
}
}
Ok(())
},
None => unreachable!()
}
}
}
}
impl fmt::Debug for Box<HeaderFormat + Send + Sync> {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
(**self).fmt_header(fmt)
}
}
impl fmt::Display for Box<HeaderFormat + Send + Sync> {
impl<'a> fmt::Display for &'a (HeaderFormat + Send + Sync) {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
(**self).fmt_header(fmt)
@@ -568,7 +391,7 @@ mod tests {
assert_eq!(accept, Some(Accept(vec![application_vendor, text_plain])));
}
#[derive(Clone, Debug)]
#[derive(Clone, PartialEq, Debug)]
struct CrazyLength(Option<bool>, usize);
impl Header for CrazyLength {
@@ -600,15 +423,14 @@ mod tests {
#[test]
fn test_different_structs_for_same_header() {
let headers = Headers::from_raw(&mut b"Content-Length: 10\r\n\r\n").unwrap();
let ContentLength(_) = *headers.get::<ContentLength>().unwrap();
assert!(headers.get::<CrazyLength>().is_none());
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(&mut b"Content-Length: 10 \r\n\r\n").unwrap();
let ContentLength(_) = *headers.get::<ContentLength>().unwrap();
assert!(headers.get::<CrazyLength>().is_none());
assert_eq!(headers.get::<ContentLength>(), Some(&ContentLength(10)));
}
#[test]

View File

@@ -130,7 +130,6 @@ extern crate "rustc-serialize" as serialize;
extern crate time;
extern crate url;
extern crate openssl;
extern crate "unsafe-any" as uany;
extern crate cookie;
extern crate unicase;

View File

@@ -8,7 +8,6 @@ use std::path::Path;
use std::raw::{self, TraitObject};
use std::sync::Arc;
use uany::UnsafeAnyExt;
use openssl::ssl::{Ssl, SslStream, SslContext};
use openssl::ssl::SslVerifyMode::SslVerifyNone;
use openssl::ssl::SslMethod::Sslv23;
@@ -99,7 +98,7 @@ impl Clone for Box<NetworkStream + Send> {
fn clone(&self) -> Box<NetworkStream + Send> { self.clone_box() }
}
impl UnsafeAnyExt for NetworkStream {
impl NetworkStream {
unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T {
mem::transmute(mem::transmute::<&NetworkStream,
raw::TraitObject>(self).data)
@@ -351,8 +350,6 @@ fn lift_ssl_error(ssl: SslError) -> io::Error {
#[cfg(test)]
mod tests {
use uany::UnsafeAnyExt;
use mock::MockStream;
use super::NetworkStream;