-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(headers): use a Vec + a map for Cookie #1153
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
use header::{Header, Raw}; | ||
use std::fmt::{self, Display}; | ||
use header::{Header, Raw, VecMap}; | ||
use std::fmt; | ||
use std::str::from_utf8; | ||
use std::borrow::Borrow; | ||
|
||
/// `Cookie` header, defined in [RFC6265](http://tools.ietf.org/html/rfc6265#section-5.4) | ||
/// | ||
|
@@ -27,10 +28,15 @@ use std::str::from_utf8; | |
/// ]) | ||
/// ); | ||
/// ``` | ||
#[derive(Clone, PartialEq, Debug)] | ||
pub struct Cookie(pub Vec<String>); | ||
#[allow(missing_debug_implementations)] | ||
#[derive(Clone)] | ||
pub struct Cookie { | ||
cookies: Vec<(String, String)>, | ||
index: VecMap<String, String>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Also, a let mut cookie = Cookie::new();
// no allocation or copy required
cookie.set("foo", "bar");
// dynamic string, use a String
cookie.set(nonce_key.to_string(), nonce_val.to_string()); |
||
} | ||
|
||
|
||
__hyper__deref!(Cookie => Vec<String>); | ||
//__hyper__deref!(Cookie => Vec<String>); | ||
|
||
impl Header for Cookie { | ||
fn header_name() -> &'static str { | ||
|
@@ -39,16 +45,28 @@ impl Header for Cookie { | |
} | ||
|
||
fn parse_header(raw: &Raw) -> ::Result<Cookie> { | ||
let mut cookies = Vec::with_capacity(raw.len()); | ||
let mut cookies = Cookie::with_capacity(raw.len()); | ||
for cookies_raw in raw.iter() { | ||
let cookies_str = try!(from_utf8(&cookies_raw[..])); | ||
for cookie_str in cookies_str.split(';') { | ||
cookies.push(cookie_str.trim().to_owned()) | ||
let cookie_trimmed = cookie_str.trim(); | ||
if cookie_trimmed.len() == 0 { | ||
continue; | ||
} | ||
let mut kv_iterator = cookie_trimmed.splitn(2, '='); | ||
// split returns at least one element - unwrap is safe | ||
let k = kv_iterator.next().unwrap().trim(); | ||
let v = match kv_iterator.next() { | ||
Some(value) => value.trim(), | ||
None => "", | ||
}; | ||
|
||
cookies.push(k, v); | ||
} | ||
} | ||
|
||
cookies.shrink_to_fit(); | ||
if !cookies.is_empty() { | ||
Ok(Cookie(cookies)) | ||
Ok(cookies) | ||
} else { | ||
Err(::Error::Header) | ||
} | ||
|
@@ -59,18 +77,82 @@ impl Header for Cookie { | |
} | ||
} | ||
|
||
impl Cookie { | ||
/// Create an empty Cookie header. | ||
pub fn new() -> Cookie { | ||
Cookie::with_capacity(0) | ||
} | ||
|
||
/// Create a Cookie header of a certain size. | ||
pub fn with_capacity(capacity: usize) -> Cookie { | ||
Cookie { | ||
cookies: Vec::with_capacity(capacity), | ||
index: VecMap::with_capacity(capacity), | ||
} | ||
} | ||
|
||
/// Shrink the Cookie header internal elements to the currently used size. | ||
pub fn shrink_to_fit(&mut self) { | ||
self.cookies.shrink_to_fit(); | ||
} | ||
|
||
/// Append a new cookie to the Cookie header. | ||
pub fn push<T: Into<String>>(&mut self, name_tref: T, value_tref: T) { | ||
let name = name_tref.into(); | ||
let value = value_tref.into(); | ||
self.cookies.push((name.clone(), value.clone())); | ||
if self.index.get(&name) == None { | ||
self.index.insert(name, value); | ||
} | ||
} | ||
|
||
|
||
/// Get value of cookie from name. If duplicate names were pushed to the | ||
/// Cookie header, this function will only return the first one. | ||
pub fn get<T: Borrow<String>>(&self, name: T) -> Option<String> { | ||
match self.index.get(name.borrow()) { | ||
Some(value_ref) => Some((*value_ref).clone()), | ||
None => None, | ||
} | ||
} | ||
|
||
|
||
/// Clear the current Cookie, and add one with specified name and value. | ||
pub fn set<T: Into<String>>(&mut self, name_tref: T, value_tref: T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding of the suggestion for this method was to clear the values of only this key. This would seem confusing: let mut cookie = Cookie::new();
cookie.set("foo", "bar");
cookie.set("baz", "quux");
// where did my foo=bar go!
assert_eq!(cookie.to_string(), "baz=quux"); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understood the suggestion as push()/append() to add an extra cookie name/value to the header, and set() to for the entire header to this one value. |
||
self.cookies.clear(); | ||
self.index.clear(); | ||
self.push(name_tref, value_tref); | ||
} | ||
|
||
/// Check if there are any exiting cookie. | ||
pub fn is_empty(&self) -> bool { | ||
self.cookies.is_empty() | ||
} | ||
} | ||
|
||
impl fmt::Display for Cookie { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
let cookies = &self.0; | ||
for (i, cookie) in cookies.iter().enumerate() { | ||
if i != 0 { | ||
try!(f.write_str("; ")); | ||
let mut first = true; | ||
for pair in self.cookies.clone() { | ||
let cookie = format!("{}={}", pair.0, pair.1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would allocate a brand new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great point. Thanks. |
||
if !first { | ||
try!(f.write_str(" ")); | ||
} else { | ||
first = false | ||
} | ||
try!(Display::fmt(&cookie, f)); | ||
try!(f.write_str(&cookie)); | ||
try!(f.write_str(";")); | ||
|
||
|
||
} | ||
Ok(()) | ||
|
||
} | ||
} | ||
|
||
bench_header!(bench, Cookie, { vec![b"foo=bar; baz=quux".to_vec()] }); | ||
|
||
/* | ||
bench_header!(bench, Cookie, { | ||
vec![b"foo=bar; baz=quux".to_vec()] | ||
}); | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'd still want a
Debug
implementation. It can useformatter.debug_map()
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, but with my implementation, I would have had to add a Debug trait to VecMap. I wanted to get the code out fast for sake of discussion, rather than spend too many hours on an implementation that wasn't going to be used.