Merge pull request #48 from reem/immutable-header-getters
Store Header Items behind an RWLock
This commit is contained in:
@@ -111,7 +111,7 @@ impl Request<Fresh> {
|
|||||||
let mut chunked = true;
|
let mut chunked = true;
|
||||||
let mut len = 0;
|
let mut len = 0;
|
||||||
|
|
||||||
match self.headers.get_ref::<common::ContentLength>() {
|
match self.headers.get::<common::ContentLength>() {
|
||||||
Some(cl) => {
|
Some(cl) => {
|
||||||
chunked = false;
|
chunked = false;
|
||||||
len = cl.len();
|
len = cl.len();
|
||||||
@@ -121,16 +121,19 @@ impl Request<Fresh> {
|
|||||||
|
|
||||||
// cant do in match above, thanks borrowck
|
// cant do in match above, thanks borrowck
|
||||||
if chunked {
|
if chunked {
|
||||||
//TODO: use CollectionViews (when implemented) to prevent double hash/lookup
|
let encodings = match self.headers.get_mut::<common::TransferEncoding>() {
|
||||||
let encodings = match self.headers.get::<common::TransferEncoding>() {
|
Some(&common::TransferEncoding(ref mut encodings)) => {
|
||||||
Some(common::TransferEncoding(mut encodings)) => {
|
|
||||||
//TODO: check if chunked is already in encodings. use HashSet?
|
//TODO: check if chunked is already in encodings. use HashSet?
|
||||||
encodings.push(common::transfer_encoding::Chunked);
|
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() {
|
for (name, header) in self.headers.iter() {
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ impl Response {
|
|||||||
pub fn new(stream: Box<NetworkStream + Send>) -> HttpResult<Response> {
|
pub fn new(stream: Box<NetworkStream + Send>) -> HttpResult<Response> {
|
||||||
let mut stream = BufferedReader::new(stream.abstract());
|
let mut stream = BufferedReader::new(stream.abstract());
|
||||||
let (version, status) = try!(read_status_line(&mut stream));
|
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!("{} {}", version, status);
|
||||||
debug!("{}", headers);
|
debug!("{}", headers);
|
||||||
|
|
||||||
let body = if headers.has::<TransferEncoding>() {
|
let body = if headers.has::<TransferEncoding>() {
|
||||||
match headers.get_ref::<TransferEncoding>() {
|
match headers.get::<TransferEncoding>() {
|
||||||
Some(&TransferEncoding(ref codings)) => {
|
Some(&TransferEncoding(ref codings)) => {
|
||||||
if codings.len() > 1 {
|
if codings.len() > 1 {
|
||||||
debug!("TODO: #2 handle other codings: {}", codings);
|
debug!("TODO: #2 handle other codings: {}", codings);
|
||||||
@@ -49,7 +49,7 @@ impl Response {
|
|||||||
None => unreachable!()
|
None => unreachable!()
|
||||||
}
|
}
|
||||||
} else if headers.has::<ContentLength>() {
|
} else if headers.has::<ContentLength>() {
|
||||||
match headers.get_ref::<ContentLength>() {
|
match headers.get::<ContentLength>() {
|
||||||
Some(&ContentLength(len)) => SizedReader(stream, len),
|
Some(&ContentLength(len)) => SizedReader(stream, len),
|
||||||
None => unreachable!()
|
None => unreachable!()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ use std::raw::TraitObject;
|
|||||||
use std::str::{from_utf8, SendStr, Slice, Owned};
|
use std::str::{from_utf8, SendStr, Slice, Owned};
|
||||||
use std::string::raw;
|
use std::string::raw;
|
||||||
use std::collections::hashmap::{HashMap, Entries, Occupied, Vacant};
|
use std::collections::hashmap::{HashMap, Entries, Occupied, Vacant};
|
||||||
|
use std::sync::RWLock;
|
||||||
|
|
||||||
use uany::UncheckedAnyDowncast;
|
use uany::{UncheckedAnyDowncast, UncheckedAnyMutDowncast};
|
||||||
use typeable::Typeable;
|
use typeable::Typeable;
|
||||||
|
|
||||||
use http::read_header;
|
use http::read_header;
|
||||||
@@ -24,7 +25,7 @@ use {HttpResult};
|
|||||||
pub mod common;
|
pub mod common;
|
||||||
|
|
||||||
/// A trait for any object that will represent a header field and value.
|
/// 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.
|
/// Returns the name of the header field this belongs to.
|
||||||
///
|
///
|
||||||
/// The market `Option` is to hint to the type system which implementation
|
/// 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 {
|
fn header_name<T: Header>() -> &'static str {
|
||||||
let name = Header::header_name(None::<T>);
|
let name = Header::header_name(None::<T>);
|
||||||
name
|
name
|
||||||
@@ -68,7 +77,7 @@ fn header_name<T: Header>() -> &'static str {
|
|||||||
|
|
||||||
/// A map of header fields on requests and responses.
|
/// A map of header fields on requests and responses.
|
||||||
pub struct Headers {
|
pub struct Headers {
|
||||||
data: HashMap<CaseInsensitive, Item>
|
data: HashMap<CaseInsensitive, RWLock<Item>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Headers {
|
impl Headers {
|
||||||
@@ -92,13 +101,14 @@ impl Headers {
|
|||||||
raw::from_utf8(name)
|
raw::from_utf8(name)
|
||||||
};
|
};
|
||||||
|
|
||||||
let item = match headers.data.entry(CaseInsensitive(Owned(name))) {
|
let name = CaseInsensitive(Owned(name));
|
||||||
Vacant(entry) => entry.set(Raw(vec![])),
|
let item = match headers.data.entry(name) {
|
||||||
|
Vacant(entry) => entry.set(RWLock::new(Raw(vec![]))),
|
||||||
Occupied(entry) => entry.into_mut()
|
Occupied(entry) => entry.into_mut()
|
||||||
};
|
};
|
||||||
|
|
||||||
match *item {
|
match &mut *item.write() {
|
||||||
Raw(ref mut raw) => raw.push(value),
|
&Raw(ref mut raw) => raw.push(value),
|
||||||
// Unreachable
|
// Unreachable
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
@@ -113,21 +123,7 @@ impl Headers {
|
|||||||
///
|
///
|
||||||
/// The field is determined by the type of the value being set.
|
/// The field is determined by the type of the value being set.
|
||||||
pub fn set<H: Header>(&mut self, value: H) {
|
pub fn set<H: Header>(&mut self, value: H) {
|
||||||
self.data.insert(CaseInsensitive(Slice(header_name::<H>())), Typed(box value as Box<Header>));
|
self.data.insert(CaseInsensitive(Slice(header_name::<H>())), RWLock::new(Typed(box value as Box<Header + Send + Sync>)));
|
||||||
}
|
|
||||||
|
|
||||||
/// 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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the raw value of a header, if it exists and has not
|
/// 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,
|
/// If the header field has already been parsed into a typed header,
|
||||||
/// then you *must* access it through that representation.
|
/// 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:
|
/// Example:
|
||||||
/// ```
|
/// ```
|
||||||
/// # use hyper::header::Headers;
|
/// # use hyper::header::Headers;
|
||||||
/// # let mut headers = Headers::new();
|
/// # let mut headers = Headers::new();
|
||||||
/// let raw_content_type = unsafe { headers.get_raw("content-type") };
|
/// 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| {
|
self.data.find(&CaseInsensitive(Slice(name))).and_then(|item| {
|
||||||
match *item {
|
match *item.read() {
|
||||||
Raw(ref raw) => Some(raw.as_slice()),
|
Raw(ref raw) => Some(raw.as_slice() as *const [Vec<u8>]),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the header field's value, if it exists.
|
/// Get a reference to the header field's value, if it exists.
|
||||||
pub fn get_ref<H: Header>(&mut self) -> Option<&H> {
|
pub fn get<H: Header>(&self) -> Option<&H> {
|
||||||
self.data.find_mut(&CaseInsensitive(Slice(header_name::<H>()))).and_then(|item| {
|
self.get_or_parse::<H>().map(|item| {
|
||||||
debug!("get_ref, name={}, val={}", header_name::<H>(), item);
|
let read = item.read();
|
||||||
let header = match *item {
|
debug!("downcasting {}", *read);
|
||||||
// Huge borrowck hack here, should be refactored to just return here.
|
let ret = match *read {
|
||||||
Typed(ref typed) if typed.is::<H>() => None,
|
Typed(ref val) => unsafe { val.downcast_ref_unchecked() },
|
||||||
// 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()) }
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
_ => 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.
|
/// An `Iterator` over the fields in a `Headers` map.
|
||||||
pub struct HeadersItems<'a> {
|
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> {
|
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.
|
/// 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> {
|
impl<'a> fmt::Show for HeaderView<'a> {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let HeaderView(item) = *self;
|
let HeaderView(item) = *self;
|
||||||
item.fmt(fmt)
|
item.read().fmt(fmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +297,7 @@ impl Mutable for Headers {
|
|||||||
|
|
||||||
enum Item {
|
enum Item {
|
||||||
Raw(Vec<Vec<u8>>),
|
Raw(Vec<Vec<u8>>),
|
||||||
Typed(Box<Header>)
|
Typed(Box<Header + Send + Sync>)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Show for Item {
|
impl fmt::Show for Item {
|
||||||
@@ -341,8 +373,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_raw() {
|
fn test_from_raw() {
|
||||||
let mut headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap();
|
let headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap();
|
||||||
assert_eq!(headers.get_ref(), Some(&ContentLength(10)));
|
assert_eq!(headers.get(), Some(&ContentLength(10)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -380,8 +412,31 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_different_structs_for_same_header() {
|
fn test_different_structs_for_same_header() {
|
||||||
let mut headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap();
|
let headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap();
|
||||||
let ContentLength(_) = headers.get::<ContentLength>().unwrap();
|
let ContentLength(_) = *headers.get::<ContentLength>().unwrap();
|
||||||
assert!(headers.get::<CrazyLength>().is_none());
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,14 +39,14 @@ impl Request {
|
|||||||
let remote_addr = try_io!(stream.peer_name());
|
let remote_addr = try_io!(stream.peer_name());
|
||||||
let mut stream = BufferedReader::new(stream.abstract());
|
let mut stream = BufferedReader::new(stream.abstract());
|
||||||
let (method, uri, version) = try!(read_request_line(&mut stream));
|
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!("{} {} {}", method, uri, version);
|
||||||
debug!("{}", headers);
|
debug!("{}", headers);
|
||||||
|
|
||||||
|
|
||||||
let body = if headers.has::<ContentLength>() {
|
let body = if headers.has::<ContentLength>() {
|
||||||
match headers.get_ref::<ContentLength>() {
|
match headers.get::<ContentLength>() {
|
||||||
Some(&ContentLength(len)) => SizedReader(stream, len),
|
Some(&ContentLength(len)) => SizedReader(stream, len),
|
||||||
None => unreachable!()
|
None => unreachable!()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ impl Response<Fresh> {
|
|||||||
let mut chunked = true;
|
let mut chunked = true;
|
||||||
let mut len = 0;
|
let mut len = 0;
|
||||||
|
|
||||||
match self.headers.get_ref::<common::ContentLength>() {
|
match self.headers.get::<common::ContentLength>() {
|
||||||
Some(cl) => {
|
Some(cl) => {
|
||||||
chunked = false;
|
chunked = false;
|
||||||
len = cl.len();
|
len = cl.len();
|
||||||
@@ -82,16 +82,19 @@ impl Response<Fresh> {
|
|||||||
|
|
||||||
// cant do in match above, thanks borrowck
|
// cant do in match above, thanks borrowck
|
||||||
if chunked {
|
if chunked {
|
||||||
//TODO: use CollectionViews (when implemented) to prevent double hash/lookup
|
let encodings = match self.headers.get_mut::<common::TransferEncoding>() {
|
||||||
let encodings = match self.headers.get::<common::TransferEncoding>() {
|
Some(&common::TransferEncoding(ref mut encodings)) => {
|
||||||
Some(common::TransferEncoding(mut encodings)) => {
|
|
||||||
//TODO: check if chunked is already in encodings. use HashSet?
|
//TODO: check if chunked is already in encodings. use HashSet?
|
||||||
encodings.push(common::transfer_encoding::Chunked);
|
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() {
|
for (name, header) in self.headers.iter() {
|
||||||
|
|||||||
Reference in New Issue
Block a user