Rocket

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 41f0614b1403d329ca234d54128bdf216f9942d7
parent d1cfdbaa8e575c4f0b8a0509b06356cd0ffb1987
Author: Sergio Benitez <sb@sergio.bz>
Date:   Tue,  7 Aug 2018 22:29:38 -0700

Update request-local state documentation.

Diffstat:
Mcore/lib/src/fairing/mod.rs | 53++++++++++++++++++++++++++++++++---------------------
Mcore/lib/src/request/from_request.rs | 29++++++++++++++++-------------
Msite/guide/state.md | 93++++++++++++++++++++++++++++++++++++++++++-------------------------------------
3 files changed, 97 insertions(+), 78 deletions(-)

diff --git a/core/lib/src/fairing/mod.rs b/core/lib/src/fairing/mod.rs @@ -261,12 +261,17 @@ pub use self::info_kind::{Info, Kind}; /// } /// ``` /// -/// ## Request-Local Cache +/// ## Request-Local State /// -/// Fairings can use the *request-local cache* to persist data between the -/// request and the response, or to pass data to a request guard. +/// Fairings can use [request-local state] to persist or carry data between +/// requests and responses, or to pass data to a request guard. /// -/// ``` +/// As an example, the following fairing uses request-local state to time +/// requests, setting an `X-Response-Time` header on all responses with the +/// elapsed time. It also exposes the start time of a request via a `StartTime` +/// request guard. +/// +/// ```rust /// # use std::time::{Duration, SystemTime}; /// # use rocket::Outcome; /// # use rocket::{Request, Data, Response}; @@ -274,9 +279,12 @@ pub use self::info_kind::{Info, Kind}; /// # use rocket::http::Status; /// # use rocket::request::{self, FromRequest}; /// # -/// struct RequestTimer; +/// /// Fairing for timing requests. +/// pub struct RequestTimer; +/// +/// /// Value stored in request-local state. /// #[derive(Copy, Clone)] -/// struct StartTime(pub Option<SystemTime>); +/// struct TimerStart(Option<SystemTime>); /// /// impl Fairing for RequestTimer { /// fn info(&self) -> Info { @@ -286,40 +294,43 @@ pub use self::info_kind::{Info, Kind}; /// } /// } /// -/// /// Stores the start time of the request +/// /// Stores the start time of the request in request-local state. /// fn on_request(&self, request: &mut Request, _: &Data) { -/// // Store a StartTime instead of directly storing a SystemTime, +/// // Store a `TimerStart` instead of directly storing a `SystemTime` /// // to ensure that this usage doesn't conflict with anything else -/// // that might store a SystemTime in request-local cache. -/// request.local_cache(|| StartTime(Some(SystemTime::now()))); +/// // that might store a `SystemTime` in request-local cache. +/// request.local_cache(|| TimerStart(Some(SystemTime::now()))); /// } /// /// /// Adds a header to the response indicating how long the server took to -/// /// process the request +/// /// process the request. /// fn on_response(&self, request: &Request, response: &mut Response) { -/// let start_time = request.local_cache(|| StartTime(None)); +/// let start_time = request.local_cache(|| TimerStart(None)); /// if let Some(Ok(duration)) = start_time.0.map(|st| st.elapsed()) { -/// response.set_raw_header("X-Response-Time", format!("{} ms", -/// duration.as_secs() * 1000 + duration.subsec_millis() as u64)); +/// let ms = duration.as_secs() * 1000 + duration.subsec_millis() as u64; +/// response.set_raw_header("X-Response-Time", format!("{} ms", ms)); /// } /// } /// } /// -/// // Allows a route to access the time the request was initiated. -/// // This guard will fail if the RequestTimer fairing was not attached, -/// // and will never return a StartTime(None). +/// /// Request guard used to retrieve the start time of a request. +/// #[derive(Copy, Clone)] +/// pub struct StartTime(pub SystemTime); +/// +/// // Allows a route to access the time a request was initiated. /// impl<'a, 'r> FromRequest<'a, 'r> for StartTime { /// type Error = (); /// /// fn from_request(request: &'a Request<'r>) -> request::Outcome<StartTime, ()> { -/// let start_time = request.local_cache(|| StartTime(None)); -/// match *start_time { -/// st@StartTime(Some(_)) => Outcome::Success(st), -/// StartTime(None) => Outcome::Failure((Status::InternalServerError, ())), +/// match *request.local_cache(|| TimerStart(None)) { +/// TimerStart(Some(time)) => Outcome::Success(StartTime(time)), +/// TimerStart(None) => Outcome::Failure((Status::InternalServerError, ())), /// } /// } /// } /// ``` +/// +/// [request-local state]: https://rocket.rs/guide/state/#request-local-state pub trait Fairing: Send + Sync + 'static { /// Returns an [`Info`](/rocket/fairing/struct.Info.html) structure diff --git a/core/lib/src/request/from_request.rs b/core/lib/src/request/from_request.rs @@ -208,14 +208,15 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> { /// # fn main() { } /// ``` /// -/// # Request-Local Cache +/// # Request-Local State /// -/// Request guards that perform expensive operations, such as querying a -/// database or an external service, should use the *request-local cache* to -/// store the result if they might be invoked multiple times during the routing +/// Request guards that perform expensive operations, such as those that query a +/// database or an external service, should use the [request-local state] cache +/// to store results if they might be invoked multiple times during the routing /// of a single request. /// -/// For example, consider a pair of `User` and `Admin` guards: +/// For example, consider a pair of `User` and `Admin` guards and a pair of +/// routes (`admin_dashboard` and `user_dashboard`): /// /// ```rust /// # #![feature(plugin, decl_macro)] @@ -275,10 +276,10 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> { /// fn user_dashboard(user: User) { } /// ``` /// -/// When a non-admin user is logged in, the database will be queried twice: Once +/// When a non-admin user is logged in, the database will be queried twice: once /// via the `Admin` guard invoking the `User` guard, and a second time via the -/// `User` guard directly. For cases such as these, the request-local cache -/// should be used: +/// `User` guard directly. For cases like these, request-local state should be +/// used, as illustrated below: /// /// ```rust /// # #![feature(plugin, decl_macro)] @@ -307,8 +308,8 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> { /// type Error = (); /// /// fn from_request(request: &'a Request<'r>) -> request::Outcome<&'a User, ()> { -/// // The closure will run only once per request, and future -/// // invocations will reuse the result of the first calculation +/// // This closure will execute at most once per request, regardless of +/// // the number of times the `User` guard is executed. /// let user_result = request.local_cache(|| { /// let db = request.guard::<Database>().succeeded()?; /// request.cookies() @@ -316,6 +317,7 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> { /// .and_then(|cookie| cookie.value().parse().ok()) /// .and_then(|id| db.get_user(id).ok()) /// }); +/// /// user_result.as_ref().or_forward(()) /// } /// } @@ -335,9 +337,10 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> { /// } /// ``` /// -/// Notice that these request guards provide access to *borrowed* data -/// (`&'a User` and `Admin<'a>`). The data is now owned by the request's cache, -/// so it must either be borrowed or cloned by the guards. +/// Notice that these request guards provide access to *borrowed* data (`&'a +/// User` and `Admin<'a>`) as the data is now owned by the request's cache. +/// +/// [request-local state]: https://rocket.rs/guide/state/#request-local-state pub trait FromRequest<'a, 'r>: Sized { /// The associated error to be returned if derivation fails. diff --git a/site/guide/state.md b/site/guide/state.md @@ -85,50 +85,6 @@ fn from_request(req: &'a Request<'r>) -> request::Outcome<T, ()> { [`Request::guard()`]: https://api.rocket.rs/rocket/struct.Request.html#method.guard -### Request-Local State - -While managed state is *global* and available application-wide, request-local -state is *local* to a given request, carried along with the request, and dropped -once the request is completed. Request-local state can be used whenever a -`Request` is available, such as in a fairing, a request guard, or a responder. - -Request-local state is *cached*: if data of a given type has already been -stored, it will be reused. This is especially useful for request guards that -might be invoked multiple times during the routing and processing of a single -request, such as those dealing with authentication. - -```rust -/// A global counter for arbitrary request IDs -static request_id_counter: AtomicUsize = AtomicUsize::new(0); -/// A type that represents request IDs -struct RequestId(pub usize); - -/// Returns the current request's RequestId, assigning one -/// if the current request does not have one already. -impl<'a, 'r> FromRequest<'a, 'r> for RequestId { - fn from_request(request: &'a Request<'r>) -> request::Outcome { - // The closure passed to local_cache will be executed at most once per - // request, the first time the RequestId guard is used. If it is - // requested again, local_cache will return the same value. - Outcome::Success(request.local_cache(|| { - RequestId(request_id_counter.fetch_add(1, Ordering::Relaxed)) - })) - } -} -``` - -Another use case for request-local state is request validation. A fairing can -read request headers, query a database or other external service, and store the -result in request-local state. The result of the validation is available to each -individual route and also to any custom `Responder`s, for example from an -authentication library. - -Refer to the documentation for the [`FromRequest`] and [`Fairing`] traits for -more examples of this functionality. - -[`FromRequest`]: https://api.rocket.rs/rocket/request/trait.FromRequest.html -[`Fairing`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html - ### Unmanaged State If you request a `State<T>` for a `T` that is not `managed`, Rocket won't call @@ -191,6 +147,55 @@ learn more about the [`manage` method](https://api.rocket.rs/rocket/struct.Rocket.html#method.manage) and [`State` type](https://api.rocket.rs/rocket/struct.State.html) in the API docs. +### Request-Local State + +While managed state is *global* and available application-wide, request-local +state is *local* to a given request, carried along with the request, and dropped +once the request is completed. Request-local state can be used whenever a +`Request` is available, such as in a fairing, a request guard, or a responder. + +Request-local state is *cached*: if data of a given type has already been +stored, it will be reused. This is especially useful for request guards that +might be invoked multiple times during routing and processing of a single +request, such as those that deal with authentication. + +As an example, consider the following request guard implementation for +`RequestId` that uses request-local state to generate and expose a unique +integer ID per request: + +```rust +/// A global atomic counter for generating IDs. +static request_id_counter: AtomicUsize = AtomicUsize::new(0); + +/// A type that represents a request's ID. +struct RequestId(pub usize); + +/// Returns the current request's ID, assigning one only as necessary. +impl<'a, 'r> FromRequest<'a, 'r> for RequestId { + fn from_request(request: &'a Request<'r>) -> request::Outcome { + // The closure passed to `local_cache` will be executed at most once per + // request: the first time the `RequestId` guard is used. If it is + // requested again, `local_cache` will return the same value. + Outcome::Success(request.local_cache(|| { + RequestId(request_id_counter.fetch_add(1, Ordering::Relaxed)) + })) + } +} +``` + +Note that, without request-local state, it would not be possible to: + + 1. Associate a piece of data, here an ID, directly with a request. + 2. Ensure that a value is generated at most once per request. + +For more examples, see the [`FromRequest`] documentation, which uses +request-local state to cache expensive authentication and authorization +computations, and the [`Fairing`] documentation, which uses request-local state +to implement request timing. + +[`FromRequest`]: https://api.rocket.rs/rocket/request/trait.FromRequest.htmll#request-local-state +[`Fairing`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#request-local-state + ## Databases While Rocket doesn't have built-in support for databases yet, you can combine a