Merge pull request #48 from reem/immutable-header-getters

Store Header Items behind an RWLock
This commit is contained in:
Sean McArthur
2014-09-27 11:23:19 -07:00
5 changed files with 145 additions and 84 deletions

View File

@@ -111,7 +111,7 @@ impl Request<Fresh> {
let mut chunked = true;
let mut len = 0;
match self.headers.get_ref::<common::ContentLength>() {
match self.headers.get::<common::ContentLength>() {
Some(cl) => {
chunked = false;
len = cl.len();
@@ -121,16 +121,19 @@ impl Request<Fresh> {
// cant do in match above, thanks borrowck
if chunked {
//TODO: use CollectionViews (when implemented) to prevent double hash/lookup
let encodings = match self.headers.get::<common::TransferEncoding>() {
Some(common::TransferEncoding(mut encodings)) => {
let encodings = match self.headers.get_mut::<common::TransferEncoding>() {
Some(&common::TransferEncoding(ref mut encodings)) => {
//TODO: check if chunked is already in encodings. use HashSet?
encodings.push(common::transfer_encoding::Chunked);
encodings
false
},
None => vec![common::transfer_encoding::Chunked]
None => true
};
self.headers.set(common::TransferEncoding(encodings));
if encodings {
self.headers.set::<common::TransferEncoding>(
common::TransferEncoding(vec![common::transfer_encoding::Chunked]))
}
}
for (name, header) in self.headers.iter() {

View File

@@ -27,13 +27,13 @@ impl Response {
pub fn new(stream: Box<NetworkStream + Send>) -> HttpResult<Response> {
let mut stream = BufferedReader::new(stream.abstract());
let (version, status) = try!(read_status_line(&mut stream));
let mut headers = try!(header::Headers::from_raw(&mut stream));
let headers = try!(header::Headers::from_raw(&mut stream));
debug!("{} {}", version, status);
debug!("{}", headers);
let body = if headers.has::<TransferEncoding>() {
match headers.get_ref::<TransferEncoding>() {
match headers.get::<TransferEncoding>() {
Some(&TransferEncoding(ref codings)) => {
if codings.len() > 1 {
debug!("TODO: #2 handle other codings: {}", codings);
@@ -49,7 +49,7 @@ impl Response {
None => unreachable!()
}
} else if headers.has::<ContentLength>() {
match headers.get_ref::<ContentLength>() {
match headers.get::<ContentLength>() {
Some(&ContentLength(len)) => SizedReader(stream, len),
None => unreachable!()
}

View File

@@ -13,8 +13,9 @@ use std::raw::TraitObject;
use std::str::{from_utf8, SendStr, Slice, Owned};
use std::string::raw;
use std::collections::hashmap::{HashMap, Entries, Occupied, Vacant};
use std::sync::RWLock;
use uany::UncheckedAnyDowncast;
use uany::{UncheckedAnyDowncast, UncheckedAnyMutDowncast};
use typeable::Typeable;
use http::read_header;
@@ -24,7 +25,7 @@ use {HttpResult};
pub mod common;
/// A trait for any object that will represent a header field and value.
pub trait Header: Typeable {
pub trait Header: Typeable + Send + Sync {
/// Returns the name of the header field this belongs to.
///
/// The market `Option` is to hint to the type system which implementation
@@ -61,6 +62,14 @@ impl<'a> UncheckedAnyDowncast<'a> for &'a Header {
}
}
impl<'a> UncheckedAnyMutDowncast<'a> for &'a mut Header {
#[inline]
unsafe fn downcast_mut_unchecked<T: 'static>(self) -> &'a mut T {
let to: TraitObject = transmute_copy(&self);
transmute(to.data)
}
}
fn header_name<T: Header>() -> &'static str {
let name = Header::header_name(None::<T>);
name
@@ -68,7 +77,7 @@ fn header_name<T: Header>() -> &'static str {
/// A map of header fields on requests and responses.
pub struct Headers {
data: HashMap<CaseInsensitive, Item>
data: HashMap<CaseInsensitive, RWLock<Item>>
}
impl Headers {
@@ -92,13 +101,14 @@ impl Headers {
raw::from_utf8(name)
};
let item = match headers.data.entry(CaseInsensitive(Owned(name))) {
Vacant(entry) => entry.set(Raw(vec![])),
let name = CaseInsensitive(Owned(name));
let item = match headers.data.entry(name) {
Vacant(entry) => entry.set(RWLock::new(Raw(vec![]))),
Occupied(entry) => entry.into_mut()
};
match *item {
Raw(ref mut raw) => raw.push(value),
match &mut *item.write() {
&Raw(ref mut raw) => raw.push(value),
// Unreachable
_ => {}
};
@@ -113,21 +123,7 @@ impl Headers {
///
/// The field is determined by the type of the value being set.
pub fn set<H: Header>(&mut self, value: H) {
self.data.insert(CaseInsensitive(Slice(header_name::<H>())), Typed(box value as Box<Header>));
}
/// Get a clone of the header field's value, if it exists.
///
/// Example:
///
/// ```
/// # use hyper::header::Headers;
/// # use hyper::header::common::ContentType;
/// # let mut headers = Headers::new();
/// let content_type = headers.get::<ContentType>();
/// ```
pub fn get<H: Header + Clone>(&mut self) -> Option<H> {
self.get_ref().map(|v: &H| v.clone())
self.data.insert(CaseInsensitive(Slice(header_name::<H>())), RWLock::new(Typed(box value as Box<Header + Send + Sync>)));
}
/// Access the raw value of a header, if it exists and has not
@@ -136,56 +132,92 @@ impl Headers {
/// If the header field has already been parsed into a typed header,
/// then you *must* access it through that representation.
///
/// This operation is unsafe because the raw representation can be
/// invalidated by lasting too long or by the header being parsed
/// while you still have a reference to the data.
///
/// Example:
/// ```
/// # use hyper::header::Headers;
/// # let mut headers = Headers::new();
/// let raw_content_type = unsafe { headers.get_raw("content-type") };
/// ```
pub unsafe fn get_raw(&self, name: &'static str) -> Option<&[Vec<u8>]> {
pub unsafe fn get_raw(&self, name: &'static str) -> Option<*const [Vec<u8>]> {
self.data.find(&CaseInsensitive(Slice(name))).and_then(|item| {
match *item {
Raw(ref raw) => Some(raw.as_slice()),
match *item.read() {
Raw(ref raw) => Some(raw.as_slice() as *const [Vec<u8>]),
_ => None
}
})
}
/// Get a reference to the header field's value, if it exists.
pub fn get_ref<H: Header>(&mut self) -> Option<&H> {
self.data.find_mut(&CaseInsensitive(Slice(header_name::<H>()))).and_then(|item| {
debug!("get_ref, name={}, val={}", header_name::<H>(), item);
let header = match *item {
// Huge borrowck hack here, should be refactored to just return here.
Typed(ref typed) if typed.is::<H>() => None,
// Typed, wrong type
Typed(_) => return None,
Raw(ref raw) => match Header::parse_header(raw.as_slice()) {
Some::<H>(h) => {
Some(h)
},
None => return None
},
};
match header {
Some(header) => {
*item = Typed(box header as Box<Header>);
Some(item)
},
None => {
Some(item)
}
}
}).and_then(|item| {
debug!("downcasting {}", item);
let ret = match *item {
Typed(ref val) => {
unsafe { Some(val.downcast_ref_unchecked()) }
},
pub fn get<H: Header>(&self) -> Option<&H> {
self.get_or_parse::<H>().map(|item| {
let read = item.read();
debug!("downcasting {}", *read);
let ret = match *read {
Typed(ref val) => unsafe { val.downcast_ref_unchecked() },
_ => unreachable!()
};
ret
unsafe { transmute::<&H, &H>(ret) }
})
}
/// Get a mutable reference to the header field's value, if it exists.
pub fn get_mut<H: Header>(&mut self) -> Option<&mut H> {
self.get_or_parse::<H>().map(|item| {
let mut write = item.write();
debug!("downcasting {}", *write);
let ret = match *&mut *write {
Typed(ref mut val) => unsafe { val.downcast_mut_unchecked() },
_ => unreachable!()
};
unsafe { transmute::<&mut H, &mut H>(ret) }
})
}
fn get_or_parse<H: Header>(&self) -> Option<&RWLock<Item>> {
self.data.find(&CaseInsensitive(Slice(header_name::<H>()))).and_then(|item| {
let done = match *item.read() {
// Huge borrowck hack here, should be refactored to just return here.
Typed(ref typed) if typed.is::<H>() => true,
// Typed, wrong type.
Typed(_) => return None,
// Raw, work to do.
Raw(_) => false,
};
// borrowck hack continued
if done { return Some(item); }
// Take out a write lock to do the parsing and mutation.
let mut write = item.write();
let header = match *write {
// Since this lock can queue, it's possible another thread just
// did the work for us.
//
// Check they inserted the correct type and move on.
Typed(ref typed) if typed.is::<H>() => return Some(item),
// Wrong type, another thread got here before us and parsed
// as a different representation.
Typed(_) => return None,
// We are first in the queue or the only ones, so do the actual
// work of parsing and mutation.
Raw(ref raw) => match Header::parse_header(raw.as_slice()) {
Some::<H>(h) => h,
None => return None
}
};
// Mutate in the raw case.
*write = Typed(box header as Box<Header + Send + Sync>);
Some(item)
})
}
@@ -229,7 +261,7 @@ impl fmt::Show for Headers {
/// An `Iterator` over the fields in a `Headers` map.
pub struct HeadersItems<'a> {
inner: Entries<'a, CaseInsensitive, Item>
inner: Entries<'a, CaseInsensitive, RWLock<Item>>
}
impl<'a> Iterator<(&'a str, HeaderView<'a>)> for HeadersItems<'a> {
@@ -242,12 +274,12 @@ impl<'a> Iterator<(&'a str, HeaderView<'a>)> for HeadersItems<'a> {
}
/// Returned with the `HeadersItems` iterator.
pub struct HeaderView<'a>(&'a Item);
pub struct HeaderView<'a>(&'a RWLock<Item>);
impl<'a> fmt::Show for HeaderView<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let HeaderView(item) = *self;
item.fmt(fmt)
item.read().fmt(fmt)
}
}
@@ -265,7 +297,7 @@ impl Mutable for Headers {
enum Item {
Raw(Vec<Vec<u8>>),
Typed(Box<Header>)
Typed(Box<Header + Send + Sync>)
}
impl fmt::Show for Item {
@@ -341,8 +373,8 @@ mod tests {
#[test]
fn test_from_raw() {
let mut headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap();
assert_eq!(headers.get_ref(), Some(&ContentLength(10)));
let headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap();
assert_eq!(headers.get(), Some(&ContentLength(10)));
}
#[test]
@@ -380,8 +412,31 @@ mod tests {
#[test]
fn test_different_structs_for_same_header() {
let mut headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap();
let ContentLength(_) = headers.get::<ContentLength>().unwrap();
let headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap();
let ContentLength(_) = *headers.get::<ContentLength>().unwrap();
assert!(headers.get::<CrazyLength>().is_none());
}
#[test]
fn test_multiple_reads() {
let headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap();
let ContentLength(one) = *headers.get::<ContentLength>().unwrap();
let ContentLength(two) = *headers.get::<ContentLength>().unwrap();
assert_eq!(one, two);
}
#[test]
fn test_different_reads() {
let headers = Headers::from_raw(&mut mem("Content-Length: 10\r\nContent-Type: text/plain\r\n\r\n")).unwrap();
let ContentLength(_) = *headers.get::<ContentLength>().unwrap();
let ContentType(_) = *headers.get::<ContentType>().unwrap();
}
#[test]
fn test_get_mutable() {
let mut headers = Headers::from_raw(&mut mem("Content-Length: 10\r\nContent-Type: text/plain\r\n\r\n")).unwrap();
*headers.get_mut::<ContentLength>().unwrap() = ContentLength(20);
assert_eq!(*headers.get::<ContentLength>().unwrap(), ContentLength(20));
}
}

View File

@@ -39,14 +39,14 @@ impl Request {
let remote_addr = try_io!(stream.peer_name());
let mut stream = BufferedReader::new(stream.abstract());
let (method, uri, version) = try!(read_request_line(&mut stream));
let mut headers = try!(Headers::from_raw(&mut stream));
let headers = try!(Headers::from_raw(&mut stream));
debug!("{} {} {}", method, uri, version);
debug!("{}", headers);
let body = if headers.has::<ContentLength>() {
match headers.get_ref::<ContentLength>() {
match headers.get::<ContentLength>() {
Some(&ContentLength(len)) => SizedReader(stream, len),
None => unreachable!()
}

View File

@@ -72,7 +72,7 @@ impl Response<Fresh> {
let mut chunked = true;
let mut len = 0;
match self.headers.get_ref::<common::ContentLength>() {
match self.headers.get::<common::ContentLength>() {
Some(cl) => {
chunked = false;
len = cl.len();
@@ -82,16 +82,19 @@ impl Response<Fresh> {
// cant do in match above, thanks borrowck
if chunked {
//TODO: use CollectionViews (when implemented) to prevent double hash/lookup
let encodings = match self.headers.get::<common::TransferEncoding>() {
Some(common::TransferEncoding(mut encodings)) => {
let encodings = match self.headers.get_mut::<common::TransferEncoding>() {
Some(&common::TransferEncoding(ref mut encodings)) => {
//TODO: check if chunked is already in encodings. use HashSet?
encodings.push(common::transfer_encoding::Chunked);
encodings
false
},
None => vec![common::transfer_encoding::Chunked]
None => true
};
self.headers.set(common::TransferEncoding(encodings));
if encodings {
self.headers.set::<common::TransferEncoding>(
common::TransferEncoding(vec![common::transfer_encoding::Chunked]))
}
}
for (name, header) in self.headers.iter() {