mirror of https://github.com/rwf2/Rocket.git
Update request-local state documentation.
This commit is contained in:
parent
d1cfdbaa8e
commit
41f0614b14
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue