Document most of the request module.

This commit is contained in:
Sergio Benitez 2016-10-21 02:56:57 -07:00
parent 79eab0e907
commit 2da43e24f7
10 changed files with 334 additions and 22 deletions

View File

@ -52,7 +52,7 @@
//! extern crate rocket; //! extern crate rocket;
//! ``` //! ```
//! //!
//! See the [guide](https://guide.rocket.rs) for more information on how to //! See the [guide](https://rocket.rs/guide) for more information on how to
//! write Rocket applications. Here's a simple example to get you started: //! write Rocket applications. Here's a simple example to get you started:
//! //!
//! ```rust //! ```rust

View File

@ -9,6 +9,34 @@ use http::hyper::{HyperBodyReader, HyperHttpStream};
use http::hyper::HyperNetworkStream; use http::hyper::HyperNetworkStream;
use http::hyper::HyperHttpReader::*; use http::hyper::HyperHttpReader::*;
/// Type representing the data in the body of an incoming request.
///
/// This type is the only means by which the body of a request can be retrieved.
/// This type is not usually used directly. Instead, types that implement
/// [FromData](trait.FromData.html) are used via code generation by specifying
/// the `data = "<param>"` route parameter as follows:
///
/// ```rust,ignore
/// #[post("/submit", data = "<var>")]
/// fn submit(var: T) -> ... { ... }
/// ```
///
/// Above, `T` can be any type that implements `FromData`. Note that `Data`
/// itself implements `FromData`.
///
/// # Reading Data
///
/// Data may be read from a `Data` object by calling either the
/// [open](#method.open) or [peek](#method.peek) methods.
///
/// The `open` method consumes the `Data` object and returns the raw data
/// stream. The `Data` object is consumed for safety reasons: consuming the
/// object ensures that holding a `Data` object means that all of the data is
/// available for reading.
///
/// The `peek` method returns a slice containing at most 4096 bytes of buffered
/// body data. This enables partially or fully reading from a `Data` object
/// without consuming the `Data` object.
pub struct Data { pub struct Data {
buffer: Vec<u8>, buffer: Vec<u8>,
is_done: bool, is_done: bool,
@ -20,6 +48,12 @@ pub struct Data {
} }
impl Data { impl Data {
/// Returns the raw data stream.
///
/// The stream contains all of the data in the body of the request,
/// including that in the `peek` buffer. The method consumes the `Data`
/// instance. This ensures that a `Data` type _always_ represents _all_ of
/// the data in a request.
pub fn open(mut self) -> impl BufRead { pub fn open(mut self) -> impl BufRead {
// Swap out the buffer and stream for empty ones so we can move. // Swap out the buffer and stream for empty ones so we can move.
let mut buffer = vec![]; let mut buffer = vec![];
@ -68,21 +102,37 @@ impl Data {
Ok(Data::new(vec, pos, cap, stream)) Ok(Data::new(vec, pos, cap, stream))
} }
/// Retrieve the `peek` buffer.
///
/// The peek buffer contains at most 4096 bytes of the body of the request.
/// The actual size of the returned buffer varies by web request. The
/// [peek_complete](#method.peek_complete) can be used to determine if this
/// buffer contains _all_ of the data in the body of the request.
#[inline(always)] #[inline(always)]
pub fn peek(&self) -> &[u8] { pub fn peek(&self) -> &[u8] {
&self.buffer[self.position..self.capacity] &self.buffer[self.position..self.capacity]
} }
/// Returns true if the `peek` buffer contains all of the data in the body
/// of the request.
#[inline(always)] #[inline(always)]
pub fn peek_complete(&self) -> bool { pub fn peek_complete(&self) -> bool {
self.is_done self.is_done
} }
/// A helper method to write the body of the request to any `Write` type.
///
/// This method is identical to `io::copy(&mut data.open(), writer)`.
#[inline(always)] #[inline(always)]
pub fn stream_to<W: Write>(self, writer: &mut W) -> io::Result<u64> { pub fn stream_to<W: Write>(self, writer: &mut W) -> io::Result<u64> {
io::copy(&mut self.open(), writer) io::copy(&mut self.open(), writer)
} }
/// A helper method to write the body of the request to a file at the path
/// determined by `path`.
///
/// This method is identical to
/// `io::copy(&mut self.open(), &mut File::create(path)?)`.
#[inline(always)] #[inline(always)]
pub fn stream_to_file<P: AsRef<Path>>(self, path: P) -> io::Result<u64> { pub fn stream_to_file<P: AsRef<Path>>(self, path: P) -> io::Result<u64> {
io::copy(&mut self.open(), &mut File::create(path)?) io::copy(&mut self.open(), &mut File::create(path)?)

View File

@ -2,6 +2,7 @@ use outcome::Outcome;
use http::StatusCode; use http::StatusCode;
use request::{Request, Data}; use request::{Request, Data};
/// Type alias for the `Outcome` of a `FromData` conversion.
pub type DataOutcome<S, E> = Outcome<S, (StatusCode, E), Data>; pub type DataOutcome<S, E> = Outcome<S, (StatusCode, E), Data>;
impl<S, E> DataOutcome<S, E> { impl<S, E> DataOutcome<S, E> {
@ -30,12 +31,51 @@ impl<S, E> DataOutcome<S, E> {
} }
/// Trait used to derive an object from incoming request data. /// Trait used to derive an object from incoming request data.
///
/// Type that implement this trait can be used as target for the `data =
/// "<param>"` route parmater, as illustrated below:
///
/// ```rust,ignore
/// #[post("/submit", data = "<var>")]
/// fn submit(var: T) -> ... { ... }
/// ```
///
/// In this example, `T` can be any type that implements `FromData.`
///
/// # Outcomes
///
/// The returned [Outcome](/rocket/outcome/index.html) of a `from_data` call
/// determines what will happen with the incoming request.
///
/// * **Success**
///
/// If the `Outcome` is `Success`, then the `Success` value will be used as
/// the value for the data parameter.
///
/// * **Failure**
///
/// If the `Outcome` is `Failure`, the request will fail with the given status
/// code. Note that users can request types of `Result<S, E>` and `Option<S>`
/// to catch `Failure`s.
///
/// * **Failure**
///
/// If the `Outcome` is `Forward`, the request will be forwarded to the next
/// matching request. This requires that no data has been read from the `Data`
/// parameter. Note that users can request an `Option<S>` to catch `Forward`s.
pub trait FromData: Sized { pub trait FromData: Sized {
/// The associated error to be returned when parsing fails.
type Error; type Error;
/// Parses an instance of `Self` from the incoming request body data.
///
/// If the parse is successful, an outcome of `Success` is returned. If the
/// data does not correspond to the type of `Self`, `Forward` is returned.
/// If parsing fails, `Failure` is returned.
fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error>; fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error>;
} }
/// The identity implementation of `FromData`. Always returns `Success`.
impl FromData for Data { impl FromData for Data {
type Error = (); type Error = ();
fn from_data(_: &Request, data: Data) -> DataOutcome<Self, Self::Error> { fn from_data(_: &Request, data: Data) -> DataOutcome<Self, Self::Error> {

View File

@ -1,12 +1,13 @@
use error::Error; use error::Error;
/// Trait to create instance of some type from an HTTP form; used by code /// Trait to create an instance of some type from an HTTP form. The
/// generation for `form` route parameters. /// [Form](struct.Form.html) type requires that its generic parameter implements
/// this trait.
/// ///
/// This trait can be automatically derived via the /// This trait can be automatically derived via the
/// [rocket_codegen](/rocket_codegen) plugin: /// [rocket_codegen](/rocket_codegen) plugin:
/// ///
/// ```rust,ignore /// ```rust
/// #![feature(plugin, custom_derive)] /// #![feature(plugin, custom_derive)]
/// #![plugin(rocket_codegen)] /// #![plugin(rocket_codegen)]
/// ///
@ -19,15 +20,32 @@ use error::Error;
/// } /// }
/// ``` /// ```
/// ///
/// The type can then be parsed from incoming form data via the `data`
/// parameter and `Form` type.
///
/// ```rust
/// # #![feature(plugin, custom_derive)]
/// # #![plugin(rocket_codegen)]
/// # extern crate rocket;
/// # use rocket::request::Form;
/// # #[derive(FromForm)]
/// # struct TodoTask { description: String, completed: bool }
/// #[post("/submit", data = "<task>")]
/// fn submit_task(task: Form<TodoTask>) -> String {
/// format!("New task: {}", task.get().description)
/// }
/// # fn main() { }
/// ```
///
/// When deriving `FromForm`, every field in the structure must implement /// When deriving `FromForm`, every field in the structure must implement
/// [FromFormValue](trait.FromFormValue.html). If you implement `FormForm` /// [FromFormValue](trait.FromFormValue.html). If you implement `FormForm`
/// yourself, use the [FormItems](struct.FormItems.html) iterator to iterate /// yourself, use the [FormItems](struct.FormItems.html) iterator to iterate
/// through the form key/value pairs. /// through the form key/value pairs.
pub trait FromForm<'f>: Sized { pub trait FromForm<'f>: Sized {
/// The associated error which can be returned from parsing. /// The associated error to be returned when parsing fails.
type Error; type Error;
/// Parses an instance of `Self` from a raw HTTP form /// Parses an instance of `Self` from a raw HTTP form string
/// (`application/x-www-form-urlencoded data`) or returns an `Error` if one /// (`application/x-www-form-urlencoded data`) or returns an `Error` if one
/// cannot be parsed. /// cannot be parsed.
fn from_form_string(form_string: &'f str) -> Result<Self, Self::Error>; fn from_form_string(form_string: &'f str) -> Result<Self, Self::Error>;

View File

@ -30,7 +30,122 @@ use std::io::Read;
use http::StatusCode; use http::StatusCode;
use request::{Request, FromData, Data, DataOutcome}; use request::{Request, FromData, Data, DataOutcome};
// This works, and it's safe, but it sucks to have the lifetime appear twice. // TODO: This works and is safe, but the lifetime appears twice.
/// A `FromData` type for parsing `FromForm` types.
///
/// This type implements the `FromData` trait. It provides a generic means to
/// parse arbitrary structure from incoming form data.
///
/// # Usage
///
/// This type can be used with any type that implements the `FromForm` trait.
/// The trait can be automatically derived; see the
/// [FromForm](trait.FromForm.html) documentation for more information about
/// implementing the trait.
///
/// Because `Form` implement `FromData`, it can be used directly as a target of
/// the `data = "<param>"` route parameter. For instance, if some structure of
/// type `T` implements the `FromForm` trait, an incoming form can be
/// automatically parsed into the `T` structure with the following route and
/// handler:
///
/// ```rust,ignore
/// #[post("/form_submit", data = "<param>")]
/// fn submit(form: Form<T>) ... { ... }
/// ```
///
/// To preserve memory safety, if the underlying structure type contains
/// references into form data, the type can only be borrowed via the
/// [get](#method.get) or [get_mut](#method.get_mut) methods. Otherwise, the
/// parsed structure can be retrieved with the [into_inner](#method.into_inner)
/// method.
///
/// ## With References
///
/// The simplest data structure with a reference into form data looks like this:
///
/// ```rust
/// # #![feature(plugin, custom_derive)]
/// # #![plugin(rocket_codegen)]
/// # extern crate rocket;
/// #[derive(FromForm)]
/// struct UserInput<'f> {
/// value: &'f str
/// }
/// ```
///
/// This corresponds to a form with a single field named `value` that should be
/// a string. A handler for this type can be written as:
///
/// ```rust
/// # #![feature(plugin, custom_derive)]
/// # #![plugin(rocket_codegen)]
/// # extern crate rocket;
/// # use rocket::request::Form;
/// # #[derive(FromForm)]
/// # struct UserInput<'f> {
/// # value: &'f str
/// # }
/// #[post("/submit", data = "<user_input>")]
/// fn submit_task<'r>(user_input: Form<'r, UserInput<'r>>) -> String {
/// format!("Your value: {}", user_input.get().value)
/// }
/// # fn main() { }
/// ```
///
/// Note that the ``r` lifetime is used _twice_ in the handler's signature: this
/// is necessary to tie the lifetime of the structure to the lifetime of the
/// request data.
///
/// ## Without References
///
/// The owned analog of the `UserInput` type above is:
///
/// ```rust
/// # #![feature(plugin, custom_derive)]
/// # #![plugin(rocket_codegen)]
/// # extern crate rocket;
/// #[derive(FromForm)]
/// struct OwnedUserInput {
/// value: String
/// }
/// ```
///
/// The handler is written similarly:
///
/// ```rust
/// # #![feature(plugin, custom_derive)]
/// # #![plugin(rocket_codegen)]
/// # extern crate rocket;
/// # use rocket::request::Form;
/// # #[derive(FromForm)]
/// # struct OwnedUserInput {
/// # value: String
/// # }
/// #[post("/submit", data = "<user_input>")]
/// fn submit_task(user_input: Form<OwnedUserInput>) -> String {
/// let input: OwnedUserInput = user_input.into_inner();
/// format!("Your value: {}", input.value)
/// }
/// # fn main() { }
/// ```
///
/// Note that no lifetime annotations are required: Rust is able to infer the
/// lifetime as ``static`. Because the lifetime is ``static`, the `into_inner`
/// method can be used to directly retrieve the parsed value.
///
/// ## Performance and Correctness Considerations
///
/// Whether you should use a `str` or `String` in your `FromForm` type depends
/// on your use case. The primary question to answer is: _Can the input contain
/// characters that must be URL encoded?_ Note that this includes commmon
/// characters such as spaces. If so, then you must use `String`, whose
/// `FromFormValue` implementation deserializes the URL encoded string for you.
/// Because the `str` references will refer directly to the underlying form
/// data, they will be raw and URL encoded.
///
/// If your string values will not contain URL encoded characters, using `str`
/// will result in fewer allocation and is thus spreferred.
pub struct Form<'f, T: FromForm<'f> + 'f> { pub struct Form<'f, T: FromForm<'f> + 'f> {
object: T, object: T,
form_string: String, form_string: String,
@ -38,14 +153,18 @@ pub struct Form<'f, T: FromForm<'f> + 'f> {
} }
impl<'f, T: FromForm<'f> + 'f> Form<'f, T> { impl<'f, T: FromForm<'f> + 'f> Form<'f, T> {
/// Immutably borrow the parsed type.
pub fn get(&'f self) -> &'f T { pub fn get(&'f self) -> &'f T {
&self.object &self.object
} }
/// Mutably borrow the parsed type.
pub fn get_mut(&'f mut self) -> &'f mut T { pub fn get_mut(&'f mut self) -> &'f mut T {
&mut self.object &mut self.object
} }
/// Returns the raw form string that was used to parse the encapsulated
/// object.
pub fn raw_form_string(&self) -> &str { pub fn raw_form_string(&self) -> &str {
&self.form_string &self.form_string
} }
@ -87,6 +206,7 @@ impl<'f, T: FromForm<'f> + 'f> Form<'f, T> {
} }
impl<'f, T: FromForm<'f> + 'static> Form<'f, T> { impl<'f, T: FromForm<'f> + 'static> Form<'f, T> {
/// Consume this object and move out the parsed object.
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.object self.object
} }
@ -98,6 +218,15 @@ impl<'f, T: FromForm<'f> + Debug + 'f> Debug for Form<'f, T> {
} }
} }
/// Parses a `Form` from incoming form data.
///
/// If the content type of the request data is not
/// `application/x-www-form-urlencoded`, `Forward`s the request. If the form
/// data cannot be parsed into a `T` or reading the incoming stream failed,
/// returns a `Failure` with the raw form string (if avaialble).
///
/// All relevant warnings and errors are written to the console in Rocket
/// logging format.
impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug { impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug {
type Error = Option<String>; type Error = Option<String>;

View File

@ -4,6 +4,7 @@ use request::Request;
use outcome::Outcome; use outcome::Outcome;
use http::{StatusCode, ContentType, Method, Cookies}; use http::{StatusCode, ContentType, Method, Cookies};
/// Type alias for the `Outcome` of a `FromRequest` conversion.
pub type RequestOutcome<T, E> = Outcome<T, (StatusCode, E), ()>; pub type RequestOutcome<T, E> = Outcome<T, (StatusCode, E), ()>;
impl<T, E> RequestOutcome<T, E> { impl<T, E> RequestOutcome<T, E> {

View File

@ -1,4 +1,18 @@
//! Types and traits that deal with request parsing and handling. //! Types and traits for request parsing and handling.
//!
//! # Request and Data
//!
//! The [Request](struct.Request.html) and [Data](struct.Data.html) types
//! contain all of the available information for an incoming request. The
//! `Request` types contains all information _except_ the body, which is
//! contained in the `Data` type.
//!
//! # Code Generation Conversion Traits
//!
//! This module contains the core code generation data conversion traits. These
//! traits are used by Rocket's code generation facilities to automatically
//! derive values from incoming data based on the signature of a request
//! handler.
mod request; mod request;
mod param; mod param;

View File

@ -5,8 +5,54 @@ use url;
use http::uri::Segments; use http::uri::Segments;
/// Trait to convert a dynamic path segment string to a concrete value.
///
/// This trait is used by Rocket's code generation facilities to parse dynamic
/// path segment string values into a given type. That is, when a path contains
/// a dynamic segment `<param>` where `param` has some type `T` that
/// implements `FromParam`, `T::from_param` will be called.
///
/// # Forwarding
///
/// If the conversion fails, the incoming request will be forwarded to the next
/// matching route, if any. For instance, consider the following route and
/// handler for the dynamic `"/<id>"` path:
///
/// ```rust
/// #![feature(plugin)]
/// #![plugin(rocket_codegen)]
///
/// extern crate rocket;
///
/// #[get("/<id>")]
/// fn hello(id: usize) -> String {
/// # /*
/// ...
/// # */
/// # "".to_string()
/// }
/// # fn main() { }
/// ```
///
/// If `usize::from_param` returns an `Ok(usize)` variant, the encapsulated
/// value is used as the `id` function parameter. If not, the request is
/// forwarded to the next matching route. Since there are no additional matching
/// routes, this example will result in a 404 error for requests with invalid
/// `id` values.
///
/// # `str` vs. `String`
///
/// Paths are URL encoded. As a result, the `str` `FromParam` implementation
/// returns the raw, URL encoded version of the path segment string. On the
/// other hand, `String` decodes the path parameter, but requires an allocation
/// to do so. This tradeoff is similiar to that of form values, and you should
/// use whichever makes sense for your application.
pub trait FromParam<'a>: Sized { pub trait FromParam<'a>: Sized {
/// The associated error to be returned when parsing fails.
type Error; type Error;
/// Parses an instance of `Self` from a dynamic path parameter string or
/// returns an `Error` if one cannot be parsed.
fn from_param(param: &'a str) -> Result<Self, Self::Error>; fn from_param(param: &'a str) -> Result<Self, Self::Error>;
} }
@ -40,8 +86,21 @@ impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64,
bool, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, bool, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6,
SocketAddr); SocketAddr);
/// Trait to convert _many_ dynamic path segment strings to a concrete value.
///
/// This is the `..` analog to [FromParam](trait.FromParam.html), and its
/// functionality is identical to it with one exception: this trait applies to
/// segment parameters of the form `<param..>`, where `param` is of some type
/// `T` that implements `FromSegments`. `T::from_segments` is called to convert
/// the matched segments (via the
/// [Segments](/rocket/http/uri/struct.Segments.html) iterator) into the
/// implementing type.
pub trait FromSegments<'a>: Sized { pub trait FromSegments<'a>: Sized {
/// The associated error to be returned when parsing fails.
type Error; type Error;
/// Parses an instance of `Self` from many dynamic path parameter strings or
/// returns an `Error` if one cannot be parsed.
fn from_segments(segments: Segments<'a>) -> Result<Self, Self::Error>; fn from_segments(segments: Segments<'a>) -> Result<Self, Self::Error>;
} }

View File

@ -12,29 +12,20 @@ use http::uri::{URI, URIBuf};
use http::hyper::{header, HyperCookie, HyperHeaders, HyperMethod, HyperRequestUri}; use http::hyper::{header, HyperCookie, HyperHeaders, HyperMethod, HyperRequestUri};
use http::{Method, ContentType, Cookies}; use http::{Method, ContentType, Cookies};
/// The type for all incoming web requests. /// The type of an incoming web request.
/// ///
/// This should be used sparingly in Rocket applications. In particular, it /// This should be used sparingly in Rocket applications. In particular, it
/// should likely only be used when writing /// should likely only be used when writing
/// [FromRequest](trait.FromRequest.html) implementations. It contains all of /// [FromRequest](trait.FromRequest.html) implementations. It contains all of
/// the information for a given web request. This includes the HTTP method, URI, /// the information for a given web request except for the body data. This
/// cookies, headers, and more. /// includes the HTTP method, URI, cookies, headers, and more.
pub struct Request { pub struct Request {
/// The HTTP method associated with the request. /// The HTTP method associated with the request.
pub method: Method, pub method: Method,
/// <div class="stability" style="margin-left: 0;"> uri: URIBuf, // FIXME: Should be URI (without hyper).
/// <em class="stab unstable">
/// Unstable
/// (<a href="https://github.com/SergioBenitez/Rocket/issues/17">#17</a>):
/// The underlying HTTP library/types are likely to change before v1.0.
/// </em>
/// </div>
///
/// The data in the request.
uri: URIBuf, // FIXME: Should be URI (without Hyper).
params: RefCell<Vec<&'static str>>, params: RefCell<Vec<&'static str>>,
cookies: Cookies, cookies: Cookies,
headers: HyperHeaders, // This sucks. headers: HyperHeaders, // Don't use hyper's headers.
} }
impl Request { impl Request {
@ -140,6 +131,14 @@ impl Request {
hyp_ct.map_or(ContentType::any(), |ct| ContentType::from(&ct.0)) hyp_ct.map_or(ContentType::any(), |ct| ContentType::from(&ct.0))
} }
/// <div class="stability" style="margin-left: 0;">
/// <em class="stab unstable">
/// Unstable
/// (<a href="https://github.com/SergioBenitez/Rocket/issues/17">#17</a>):
/// The underlying HTTP library/types are likely to change before v1.0.
/// </em>
/// </div>
///
/// Returns the first content-type accepted by this request. /// Returns the first content-type accepted by this request.
pub fn accepts(&self) -> ContentType { pub fn accepts(&self) -> ContentType {
let accept = self.headers().get::<header::Accept>(); let accept = self.headers().get::<header::Accept>();

View File

@ -19,6 +19,8 @@ use http::{Method, StatusCode};
use http::hyper::{HyperRequest, FreshHyperResponse}; use http::hyper::{HyperRequest, FreshHyperResponse};
use http::hyper::{HyperServer, HyperHandler, HyperSetCookie, header}; use http::hyper::{HyperServer, HyperHandler, HyperSetCookie, header};
/// The Rocket type used to mount routes and catchers and launch the
/// application.
pub struct Rocket { pub struct Rocket {
address: String, address: String,
port: usize, port: usize,