Cache parsed ContentType and Accept headers.

This is a breaking change. `Request::content_type` now returns a borrow
to `ContentType`. `FromRequest` for `ContentType` is no longer
implemented. Instead, `FromRequest` for `&ContentType` is implemented.
This commit is contained in:
Sergio Benitez 2017-04-13 02:36:51 -07:00
parent e50164115b
commit a25a3c69c6
4 changed files with 39 additions and 25 deletions

View File

@ -25,7 +25,7 @@ url = "1"
hyper = { version = "0.10.4", default-features = false }
toml = { version = "0.2", default-features = false }
num_cpus = "1"
state = "0.2"
state = "0.2.1"
time = "0.1"
memchr = "1"
base64 = "0.4"

View File

@ -104,7 +104,7 @@ impl<'a, S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
/// fn from_data(req: &Request, data: Data) -> data::Outcome<Self, String> {
/// // Ensure the content type is correct before opening the data.
/// let person_ct = ContentType::new("application", "x-person");
/// if req.content_type() != Some(person_ct) {
/// if req.content_type() != Some(&person_ct) {
/// return Outcome::Forward(data);
/// }
///

View File

@ -220,7 +220,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Session<'a> {
}
}
impl<'a, 'r> FromRequest<'a, 'r> for Accept {
impl<'a, 'r> FromRequest<'a, 'r> for &'a Accept {
type Error = ();
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
@ -231,7 +231,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Accept {
}
}
impl<'a, 'r> FromRequest<'a, 'r> for ContentType {
impl<'a, 'r> FromRequest<'a, 'r> for &'a ContentType {
type Error = ();
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {

View File

@ -6,7 +6,7 @@ use std::str;
use term_painter::Color::*;
use term_painter::ToStyle;
use state::Container;
use state::{Container, Storage};
use error::Error;
use super::{FromParam, FromSegments};
@ -15,7 +15,6 @@ use router::Route;
use http::uri::{URI, Segments};
use http::{Method, Header, HeaderMap, Cookies, Session, CookieJar, Key};
use http::{RawStr, ContentType, Accept, MediaType};
use http::parse::media_type;
use http::hyper;
struct PresetState<'r> {
@ -28,6 +27,8 @@ struct RequestState<'r> {
params: RefCell<Vec<(usize, usize)>>,
cookies: RefCell<CookieJar>,
session: RefCell<CookieJar>,
accept: Storage<Option<Accept>>,
content_type: Storage<Option<ContentType>>,
}
/// The type of an incoming web request.
@ -71,6 +72,8 @@ impl<'r> Request<'r> {
params: RefCell::new(Vec::new()),
cookies: RefCell::new(CookieJar::new()),
session: RefCell::new(CookieJar::new()),
accept: Storage::new(),
content_type: Storage::new(),
}
}
}
@ -238,11 +241,11 @@ impl<'r> Request<'r> {
/// let mut request = Request::new(Method::Get, "/uri");
/// assert!(request.headers().is_empty());
///
/// request.add_header(ContentType::HTML);
/// assert_eq!(request.content_type(), Some(ContentType::HTML));
/// request.add_header(ContentType::Any);
/// assert_eq!(request.headers().get_one("Content-Type"), Some("*/*"));
///
/// request.replace_header(ContentType::JSON);
/// assert_eq!(request.content_type(), Some(ContentType::JSON));
/// request.replace_header(ContentType::PNG);
/// assert_eq!(request.headers().get_one("Content-Type"), Some("image/png"));
/// ```
#[inline(always)]
pub fn replace_header<H: Into<Header<'r>>>(&mut self, header: H) {
@ -307,7 +310,9 @@ impl<'r> Request<'r> {
}
/// Returns `Some` of the Content-Type header of `self`. If the header is
/// not present, returns `None`.
/// not present, returns `None`. The Content-Type header is cached after the
/// first call to this function. As a result, subsequent calls will always
/// return the same value.
///
/// # Example
///
@ -317,38 +322,47 @@ impl<'r> Request<'r> {
///
/// let mut request = Request::new(Method::Get, "/uri");
/// assert_eq!(request.content_type(), None);
/// ```
///
/// request.replace_header(ContentType::JSON);
/// assert_eq!(request.content_type(), Some(ContentType::JSON));
/// ```rust
/// use rocket::Request;
/// use rocket::http::{Method, ContentType};
///
/// let mut request = Request::new(Method::Get, "/uri");
/// request.add_header(ContentType::JSON);
/// assert_eq!(request.content_type(), Some(&ContentType::JSON));
/// ```
#[inline(always)]
pub fn content_type(&self) -> Option<ContentType> {
// FIXME: Don't reparse each time! Use RC? Smarter than that?
// Use state::Storage!
self.headers().get_one("Content-Type").and_then(|value| value.parse().ok())
pub fn content_type(&self) -> Option<&ContentType> {
self.extra.content_type.get_or_set(|| {
self.headers().get_one("Content-Type").and_then(|v| v.parse().ok())
}).as_ref()
}
#[inline(always)]
pub fn accept(&self) -> Option<Accept> {
pub fn accept(&self) -> Option<&Accept> {
self.extra.accept.get_or_set(|| {
self.headers().get_one("Accept").and_then(|v| v.parse().ok())
}).as_ref()
}
#[inline(always)]
pub fn accept_first(&self) -> Option<MediaType> {
self.headers().get_one("Accept").and_then(|mut v| media_type(&mut v).ok())
pub fn accept_first(&self) -> Option<&MediaType> {
self.accept().and_then(|accept| accept.first()).map(|wmt| wmt.media_type())
}
#[inline(always)]
pub fn format(&self) -> Option<MediaType> {
pub fn format(&self) -> Option<&MediaType> {
static ANY: MediaType = MediaType::Any;
if self.method.supports_payload() {
self.content_type().map(|ct| ct.into_media_type())
self.content_type().map(|ct| ct.media_type())
} else {
// FIXME: Should we be using `accept_first` or `preferred`? Or
// should we be checking neither and instead pass things through
// where the client accepts the thing at all?
self.accept()
.map(|accept| accept.preferred().media_type().clone())
.or(Some(MediaType::Any))
.map(|accept| accept.preferred().media_type())
.or(Some(&ANY))
}
}