use std::cell::RefCell; use std::fmt; use term_painter::Color::*; use term_painter::ToStyle; use error::Error; use super::{FromParam, FromSegments}; use router::Route; use http::uri::{URI, URIBuf, Segments}; use http::hyper::{header, HyperCookie, HyperHeaders, HyperMethod, HyperRequestUri}; use http::{Method, ContentType, Cookies}; /// The type of an incoming web request. /// /// This should be used sparingly in Rocket applications. In particular, it /// should likely only be used when writing /// [FromRequest](trait.FromRequest.html) implementations. It contains all of /// the information for a given web request except for the body data. This /// includes the HTTP method, URI, cookies, headers, and more. pub struct Request { /// The HTTP method associated with the request. pub method: Method, uri: URIBuf, // FIXME: Should be URI (without hyper). params: RefCell>, cookies: Cookies, headers: HyperHeaders, // Don't use hyper's headers. } impl Request { /// Retrieves and parses into `T` the `n`th dynamic parameter from the /// request. Returns `Error::NoKey` if `n` is greater than the number of /// params. Returns `Error::BadParse` if the parameter type `T` can't be /// parsed from the parameter. /// /// # Example /// /// To retrieve parameter `n` as some type `T` that implements /// [FromParam](trait.FromParam.html) inside a /// [FromRequest](trait.FromRequest.html) implementation: /// /// ```rust,ignore /// fn from_request(request: &'r Request<'c>) -> .. { /// let my_param: T = request.get_param(n); /// } /// ``` pub fn get_param<'r, T: FromParam<'r>>(&'r self, n: usize) -> Result { let param = self.get_param_str(n).ok_or(Error::NoKey)?; T::from_param(param).map_err(|_| Error::BadParse) } /// Get the `n`th path parameter, if it exists. #[doc(hidden)] pub fn get_param_str(&self, n: usize) -> Option<&str> { let params = self.params.borrow(); if n >= params.len() { debug!("{} is >= param count {}", n, params.len()); None } else { Some(¶ms[n]) } } /// Returns a borrow to the cookies sent with this request. Note that /// `Cookie` implements internal mutability, so this method allows you to /// get _and_ set cookies in the given Request. #[inline(always)] pub fn cookies<'r>(&'r self) -> &'r Cookies { &self.cookies } /// Retrieves and parses into `T` all of the path segments in the request /// URI beginning and including the 0-indexed `i`. `T` must implement /// [FromSegments](trait.FromSegments.html), which is used to parse the /// segments. /// /// For example, if the request URI is `"/hello/there/i/am/here"`, then /// `request.get_segments::(1)` will attempt to parse the segments /// `"there/i/am/here"` as type `T`. pub fn get_segments<'r, T: FromSegments<'r>>(&'r self, i: usize) -> Result { let segments = self.get_raw_segments(i).ok_or(Error::NoKey)?; T::from_segments(segments).map_err(|_| Error::BadParse) } /// Get the segments beginning at the `i`th, if they exists. #[doc(hidden)] pub fn get_raw_segments(&self, i: usize) -> Option { if i >= self.uri().segment_count() { debug!("{} is >= segment count {}", i, self.uri().segment_count()); None } else { // TODO: Really want to do self.uri.segments().skip(i).into_inner(), // but the std lib doesn't implement it for Skip. let mut segments = self.uri.as_uri().segments(); for _ in segments.by_ref().take(i) { /* do nothing */ } Some(segments) } } // FIXME: Implement a testing framework for Rocket. #[doc(hidden)] pub fn mock(method: Method, uri: &str) -> Request { Request { params: RefCell::new(vec![]), method: method, cookies: Cookies::new(&[]), uri: URIBuf::from(uri), headers: HyperHeaders::new(), } } ///
/// /// Unstable /// (#17): /// The underlying HTTP library/types are likely to change before v1.0. /// ///
/// /// Returns the headers in this request. #[inline(always)] pub fn headers(&self) -> &HyperHeaders { // FIXME: Get rid of Hyper. &self.headers } ///
/// /// Unstable /// (#17): /// The underlying HTTP library/types are likely to change before v1.0. /// ///
/// /// Returns the Content-Type from the request. Althought you can retrieve /// the content-type from the headers directly, this method is considered to /// be more stable. If no Content-Type was specified in the request, a /// Content-Type of [any](struct.ContentType.html#method.any) is returned. #[inline(always)] pub fn content_type(&self) -> ContentType { let hyp_ct = self.headers().get::(); hyp_ct.map_or(ContentType::Any, |ct| ContentType::from(&ct.0)) } ///
/// /// Unstable /// (#17): /// The underlying HTTP library/types are likely to change before v1.0. /// ///
/// /// Returns the first content-type accepted by this request. pub fn accepts(&self) -> ContentType { let accept = self.headers().get::(); accept.map_or(ContentType::Any, |accept| { let items = &accept.0; if items.len() < 1 { return ContentType::Any; } else { return ContentType::from(items[0].item.clone()); } }) } /// Retrieves the URI from the request. Rocket only allows absolute URIs, so /// the URI will be absolute. #[inline(always)] pub fn uri(&self) -> URI { self.uri.as_uri() } #[doc(hidden)] #[inline(always)] pub fn set_params(&self, route: &Route) { // We use transmute to cast the lifetime of self.uri.as_uri() to // 'static. This is because that lifetime refers to the String in URIBuf // in this structure, which is (obviously) guaranteed to live as long as // the structure AS LONG AS it is not moved out or changed. AS A RESULT, // the `uri` fields MUST NEVER be changed once it is set. // TODO: Find a way to enforce these. Look at OwningRef for inspiration. use ::std::mem::transmute; *self.params.borrow_mut() = unsafe { transmute(route.get_params(self.uri.as_uri())) }; } #[doc(hidden)] #[inline(always)] pub fn set_headers(&mut self, h_headers: HyperHeaders) { let cookies = match h_headers.get::() { // TODO: Retrieve key from config. Some(cookie) => cookie.to_cookie_jar(&[]), None => Cookies::new(&[]), }; self.headers = h_headers; self.cookies = cookies; } #[doc(hidden)] pub fn new(h_method: HyperMethod, h_headers: HyperHeaders, h_uri: HyperRequestUri) -> Result { let uri = match h_uri { HyperRequestUri::AbsolutePath(s) => URIBuf::from(s), _ => return Err(format!("Bad URI: {}", h_uri)), }; let method = match Method::from_hyp(&h_method) { Some(method) => method, _ => return Err(format!("Bad method: {}", h_method)), }; let cookies = match h_headers.get::() { // TODO: Retrieve key from config. Some(cookie) => cookie.to_cookie_jar(&[]), None => Cookies::new(&[]), }; let request = Request { params: RefCell::new(vec![]), method: method, cookies: cookies, uri: uri, headers: h_headers, }; Ok(request) } } impl fmt::Display for Request { /// Pretty prints a Request. This is primarily used by Rocket's logging /// infrastructure. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.uri))?; if self.method.supports_payload() && !self.content_type().is_any() { write!(f, " {}", Yellow.paint(self.content_type()))?; } Ok(()) } }