From df756871edf4143135644c211106c5a8f8f5adb0 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Thu, 5 Feb 2015 17:09:02 -0800 Subject: [PATCH] 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. --- Cargo.toml | 1 - benches/client.rs | 2 +- src/header/cell.rs | 41 ------ src/header/common/authorization.rs | 2 +- src/header/internals/cell.rs | 131 ++++++++++++++++++ src/header/internals/item.rs | 107 +++++++++++++++ src/header/internals/mod.rs | 4 + src/header/mod.rs | 204 ++--------------------------- src/lib.rs | 1 - src/net.rs | 5 +- 10 files changed, 258 insertions(+), 240 deletions(-) delete mode 100644 src/header/cell.rs create mode 100644 src/header/internals/cell.rs create mode 100644 src/header/internals/item.rs create mode 100644 src/header/internals/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 5958bc8470..cb4076f240 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,5 +19,4 @@ openssl = "*" rustc-serialize = "*" time = "*" unicase = "*" -unsafe-any = "*" url = "*" diff --git a/benches/client.rs b/benches/client.rs index 265b65ae41..f0c27ffbe6 100644 --- a/benches/client.rs +++ b/benches/client.rs @@ -50,7 +50,7 @@ impl Write for MockStream { } } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Foo; impl hyper::header::Header for Foo { diff --git a/src/header/cell.rs b/src/header/cell.rs deleted file mode 100644 index 1ede213d56..0000000000 --- a/src/header/cell.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::cell::UnsafeCell; -use std::ops::Deref; - -pub struct OptCell(UnsafeCell>); - -impl OptCell { - #[inline] - pub fn new(val: Option) -> OptCell { - 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 Deref for OptCell { - type Target = Option; - #[inline] - fn deref<'a>(&'a self) -> &'a Option { - unsafe { &*self.0.get() } - } -} - -impl Clone for OptCell { - #[inline] - fn clone(&self) -> OptCell { - OptCell::new((**self).clone()) - } -} diff --git a/src/header/common/authorization.rs b/src/header/common/authorization.rs index 5a572c12e0..78ba8a3f7d 100644 --- a/src/header/common/authorization.rs +++ b/src/header/common/authorization.rs @@ -54,7 +54,7 @@ impl HeaderFormat for Authorization where } /// 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 is diff --git a/src/header/internals/cell.rs b/src/header/internals/cell.rs new file mode 100644 index 0000000000..e76579f127 --- /dev/null +++ b/src/header/internals/cell.rs @@ -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(UnsafeCell>); + +impl OptCell { + #[inline] + pub fn new(val: Option) -> OptCell { + 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 Deref for OptCell { + type Target = Option; + #[inline] + fn deref<'a>(&'a self) -> &'a Option { + unsafe { &*self.0.get() } + } +} + +impl Clone for OptCell { + #[inline] + fn clone(&self) -> OptCell { + OptCell::new((**self).clone()) + } +} + +pub struct PtrMapCell(UnsafeCell>>); + +#[derive(Clone, Debug)] +enum PtrMap { + Empty, + One(TypeId, T), + Many(HashMap) +} + +impl PtrMapCell { + #[inline] + pub fn new() -> PtrMapCell { + 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) { + 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 Clone for PtrMapCell where Box: Clone { + #[inline] + fn clone(&self) -> PtrMapCell { + let cell = PtrMapCell::new(); + unsafe { + *cell.0.get() = (&*self.0.get()).clone() + } + cell + } +} diff --git a/src/header/internals/item.rs b/src/header/internals/item.rs new file mode 100644 index 0000000000..cfada29087 --- /dev/null +++ b/src/header/internals/item.rs @@ -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>>, + typed: PtrMapCell +} + +impl Item { + #[inline] + pub fn new_raw(data: Vec>) -> Item { + Item { + raw: OptCell::new(Some(data)), + typed: PtrMapCell::new(), + } + } + + #[inline] + pub fn new_typed(ty: Box) -> 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> { + self.typed = PtrMapCell::new(); + unsafe { + self.raw.get_mut() + } + } + + pub fn raw(&self) -> &[Vec] { + 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(&self) -> Option<&H> { + let tid = TypeId::of::(); + match self.typed.get(tid) { + Some(val) => Some(val), + None => { + match parse::(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(&mut self) -> Option<&mut H> { + let tid = TypeId::of::(); + if self.typed.get_mut(tid).is_none() { + match parse::(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(raw: &Vec>) -> Option> { + Header::parse_header(&raw[..]).map(|h: H| box h as Box) +} + +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) + } + } +} diff --git a/src/header/internals/mod.rs b/src/header/internals/mod.rs new file mode 100644 index 0000000000..0d0b29ff72 --- /dev/null +++ b/src/header/internals/mod.rs @@ -0,0 +1,4 @@ +pub use self::item::Item; + +mod cell; +mod item; diff --git a/src/header/mod.rs b/src/header/mod.rs index f19c6e6205..0a2595e79d 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -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 HeaderClone for T { } impl HeaderFormat { - #[inline] - fn is(&self) -> bool { - self.get_type_id() == TypeId::of::() - } -} - -impl UnsafeAnyExt for HeaderFormat { #[inline] unsafe fn downcast_ref_unchecked(&self) -> &T { mem::transmute(mem::transmute::<&HeaderFormat, raw::TraitObject>(self).data) @@ -93,11 +84,6 @@ impl UnsafeAnyExt for HeaderFormat { unsafe fn downcast_mut_unchecked(&mut self) -> &mut T { mem::transmute(mem::transmute::<&mut HeaderFormat, raw::TraitObject>(self).data) } - - #[inline] - unsafe fn downcast_unchecked(self: Box) -> Box { - mem::transmute(mem::transmute::, raw::TraitObject>(self).data) - } } impl Clone for Box { @@ -179,19 +165,8 @@ impl Headers { /// ``` pub fn get_raw(&self, name: &str) -> Option<&[Vec]> { 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(&self) -> Option<&H> { - self.get_or_parse::().map(|item| { - unsafe { - downcast(&*item) - } - }) + self.data.get(&UniCase(Borrowed(header_name::()))).and_then(Item::typed::) } /// Get a mutable reference to the header field's value, if it exists. pub fn get_mut(&mut self) -> Option<&mut H> { - self.get_or_parse_mut::().map(|item| { - unsafe { downcast_mut(item) } - }) - } - - fn get_or_parse(&self) -> Option<&Item> { - self.data.get(&UniCase(Borrowed(header_name::()))).and_then(get_or_parse::) - } - - fn get_or_parse_mut(&mut self) -> Option<&mut Item> { - self.data.get_mut(&UniCase(Borrowed(header_name::()))).and_then(get_or_parse_mut::) + self.data.get_mut(&UniCase(Borrowed(header_name::()))).and_then(Item::typed_mut::) } /// 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(&self) -> Option<&'a H> { - get_or_parse::(self.1).map(|item| { - unsafe { - downcast(&*item) - } - }) + self.1.typed::() } /// Get just the header value as a String. @@ -371,141 +328,7 @@ impl<'a> FromIterator> for Headers { } } -#[derive(Clone)] -struct Item { - raw: OptCell>>, - typed: OptCell> -} - -impl Item { - #[inline] - fn new_raw(data: Vec>) -> Item { - Item { - raw: OptCell::new(Some(data)), - typed: OptCell::new(None), - } - } - - #[inline] - fn new_typed(ty: Box) -> Item { - Item { - raw: OptCell::new(None), - typed: OptCell::new(Some(ty)), - } - } - - #[inline] - fn mut_raw(&mut self) -> &mut Vec> { - self.typed = OptCell::new(None); - unsafe { - self.raw.get_mut() - } - } - - #[inline] - fn mut_typed(&mut self) -> &mut Box { - self.raw = OptCell::new(None); - unsafe { - self.typed.get_mut() - } - } -} - - -fn get_or_parse(item: &Item) -> Option<&Item> { - match *item.typed { - Some(ref typed) if typed.is::() => return Some(item), - Some(ref typed) => { - warn!("attempted to access {:?} as wrong type", typed); - return None; - } - _ => () - } - - parse::(item); - if item.typed.is_some() { - Some(item) - } else { - None - } -} - -fn get_or_parse_mut(item: &mut Item) -> Option<&mut Item> { - let is_correct_type = match *item.typed { - Some(ref typed) if typed.is::() => 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::(&item); - if item.typed.is_some() { - Some(item) - } else { - None - } -} - -fn parse(item: &Item) { - match *item.raw { - Some(ref raw) => match Header::parse_header(&raw[..]) { - Some::(h) => item.typed.set(box h as Box), - None => () - }, - None => unreachable!() - } -} - -#[inline] -unsafe fn downcast(item: &Item) -> &H { - item.typed.as_ref().expect("item.typed must be set").downcast_ref_unchecked() -} - -#[inline] -unsafe fn downcast_mut(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 { - #[inline] - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - (**self).fmt_header(fmt) - } -} - -impl fmt::Display for Box { +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, 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::().unwrap(); - assert!(headers.get::().is_none()); + assert_eq!(headers.get::(), Some(&ContentLength(10))); + assert_eq!(headers.get::(), 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::().unwrap(); - assert!(headers.get::().is_none()); + assert_eq!(headers.get::(), Some(&ContentLength(10))); } #[test] diff --git a/src/lib.rs b/src/lib.rs index bf2d2195e8..f6fe69a132 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/net.rs b/src/net.rs index 9b034a3b62..9f9af4e6b6 100644 --- a/src/net.rs +++ b/src/net.rs @@ -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 { fn clone(&self) -> Box { self.clone_box() } } -impl UnsafeAnyExt for NetworkStream { +impl NetworkStream { unsafe fn downcast_ref_unchecked(&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;