Rework 'local' module. Add 'LocalResponse' methods.

This completes the 'local' blocking client implementation.
This commit is contained in:
Sergio Benitez 2020-07-02 21:20:28 -07:00
parent 050a2c6461
commit 6482fa2fba
15 changed files with 939 additions and 741 deletions

View File

@ -1,21 +1,26 @@
use std::sync::RwLock;
use std::borrow::Cow;
use crate::local::asynchronous::LocalRequest;
use crate::local::asynchronous::{LocalRequest, LocalResponse};
use crate::rocket::{Rocket, Cargo};
use crate::http::{Method, private::CookieJar};
use crate::error::LaunchError;
struct_client! { [
/// An `async` client to construct and dispatch local requests.
///
/// ### Synchronization
/// For details, see [the top-level documentation](../index.html#client).
/// For the `blocking` version, see
/// [`blocking::Client`](crate::local::blocking::Client).
///
/// While `Client` implements `Sync`, using it in a multithreaded environment
/// while tracking cookies can result in surprising, non-deterministic behavior.
/// This is because while cookie modifications are serialized, the exact
/// ordering depends on when requests are dispatched. Specifically, when cookie
/// tracking is enabled, all request dispatches are serialized, which in-turn
/// serializes modifications to the internally tracked cookies.
/// ## Multithreaded Syncronization Pitfalls
///
/// Unlike its [`blocking`](crate::local::blocking) variant, this `async` `Client`
/// implements `Sync`. However, using it in a multithreaded environment while
/// tracking cookies can result in surprising, non-deterministic behavior. This
/// is because while cookie modifications are serialized, the exact ordering
/// depends on when requests are dispatched. Specifically, when cookie tracking
/// is enabled, all request dispatches are serialized, which in-turn serializes
/// modifications to the internally tracked cookies.
///
/// If possible, refrain from sharing a single instance of `Client` across
/// multiple threads. Instead, prefer to create a unique instance of `Client`
@ -26,7 +31,7 @@ struct_client! { [
/// ## Example
///
/// The following snippet creates a `Client` from a `Rocket` instance and
/// dispatches a local request to `POST /` with a body of `Hello, world!`.
/// dispatches a local `POST /` request with a body of `Hello, world!`.
///
/// ```rust
/// use rocket::local::asynchronous::Client;
@ -36,14 +41,13 @@ struct_client! { [
/// let client = Client::new(rocket).await.expect("valid rocket");
/// let response = client.post("/")
/// .body("Hello, world!")
/// .dispatch().await;
/// .dispatch()
/// .await;
/// # });
/// ```
]
pub struct Client {
cargo: Cargo,
pub(crate) cookies: Option<RwLock<CookieJar>>,
}
pub(in super) cookies: Option<RwLock<CookieJar>>,
}
impl Client {
@ -61,13 +65,17 @@ impl Client {
Ok(Client { cargo: rocket.into_cargo().await, cookies })
}
// WARNING: This is unstable! Do not use this method outside of Rocket!
#[doc(hidden)]
/// WARNING: This is unstable! Do not use this method outside of Rocket!
pub fn _test<T, F: FnOnce(Self) -> T + Send>(f: F) -> T {
pub fn _test<T, F>(f: F) -> T
where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send
{
crate::async_test(async {
let rocket = crate::ignite();
let client = Client::new(rocket).await.expect("valid rocket");
f(client)
let request = client.get("/");
let response = request.clone().dispatch().await;
f(&client, request, response)
})
}
@ -82,18 +90,16 @@ impl Client {
{
LocalRequest::new(self, method, uri.into())
}
}
impl_client!("use rocket::local::asynchronous::Client;" @async await Client);
// Generates the public API methods, which call the private methods above.
pub_client_impl!("use rocket::local::asynchronous::Client;" @async await);
}
#[cfg(test)]
mod test {
use super::Client;
fn assert_sync_send<T: Sync + Send>() {}
#[test]
fn test_local_client_impl_send_sync() {
assert_sync_send::<Client>();
fn assert_sync_send<T: Sync + Send>() {}
assert_sync_send::<super::Client>();
}
}

View File

@ -1,8 +1,10 @@
//! Structures for asynchronous local dispatching of requests, primarily for
//! testing.
//! Asynchronous local dispatching of requests.
//!
//! This module contains the `asynchronous` variant of the `local` API: it can
//! be used with `#[rocket::async_test]` or another asynchronous test harness.
//! For the blocking variant, see [`blocking`](super::blocking).
//!
//! See the [top-level documentation](super) for more usage details.
mod client;
mod request;

View File

@ -5,11 +5,14 @@ use crate::http::{Status, Method, uri::Origin, ext::IntoOwned};
use super::{Client, LocalResponse};
struct_request! { [
/// An `async` local request as returned by [`Client`](super::Client).
///
/// For details, see [the top-level documentation](../index.html#localrequest).
///
/// ## Example
///
/// The following snippet uses the available builder methods to construct a
/// `POST` request to `/` with a JSON body:
/// The following snippet uses the available builder methods to construct and
/// dispatch a `POST` request to `/` with a JSON body:
///
/// ```rust
/// use rocket::local::asynchronous::{Client, LocalRequest};
@ -22,16 +25,16 @@ struct_request! { [
/// .remote("127.0.0.1:8000".parse().unwrap())
/// .cookie(Cookie::new("name", "value"))
/// .body(r#"{ "value": 42 }"#);
///
/// let response = req.dispatch().await;
/// # });
/// ```
]
pub struct LocalRequest<'c> {
client: &'c Client,
request: Request<'c>,
data: Vec<u8>,
uri: Cow<'c, str>,
}
}
impl<'c> LocalRequest<'c> {
pub(crate) fn new(
@ -65,33 +68,26 @@ impl<'c> LocalRequest<'c> {
&mut self.data
}
// This method should _never_ be publicly exposed!
#[inline(always)]
fn long_lived_request<'a>(&mut self) -> &'a mut Request<'c> {
// FIXME: Whatever. I'll kill this.
unsafe { &mut *(&mut self.request as *mut _) }
}
// Performs the actual dispatch.
// TODO.async: @jebrosen suspects there might be actual UB in here after all,
// and now we just went and mixed threads into it
async fn _dispatch(mut self) -> LocalResponse<'c> {
// First, validate the URI, returning an error response (generated from
// an error catcher) immediately if it's invalid.
let rocket = self.client.rocket();
if let Ok(uri) = Origin::parse(&self.uri) {
self.request.set_uri(uri.into_owned());
} else {
error!("Malformed request URI: {}", self.uri);
let res = self.client.rocket()
.handle_error(Status::BadRequest, self.long_lived_request());
return LocalResponse { _request: self.request, inner: res.await };
return LocalResponse::new(self.request, move |req| {
rocket.handle_error(Status::BadRequest, req)
}).await
}
// Actually dispatch the request.
let response = self.client.rocket()
.dispatch(self.long_lived_request(), Data::local(self.data))
.await;
let data = Data::local(self.data);
let token = rocket.preprocess_request(&mut self.request, &data).await;
let response = LocalResponse::new(self.request, move |request| {
rocket.dispatch(token, request, data)
}).await;
// If the client is tracking cookies, updates the internal cookie jar
// with the changes reflected by `response`.
@ -110,8 +106,11 @@ impl<'c> LocalRequest<'c> {
}
}
LocalResponse { _request: self.request, inner: response }
response
}
pub_request_impl!("# use rocket::local::asynchronous::Client;
use rocket::local::asynchronous::LocalRequest;" async await);
}
impl<'c> Clone for LocalRequest<'c> {
@ -125,4 +124,8 @@ impl<'c> Clone for LocalRequest<'c> {
}
}
impl_request!("use rocket::local::asynchronous::Client;" @async await LocalRequest);
impl std::fmt::Debug for LocalRequest<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._request().fmt(f)
}
}

View File

@ -1,24 +1,133 @@
use std::io;
use std::future::Future;
use std::{pin::Pin, task::{Context, Poll}};
use tokio::io::AsyncRead;
use crate::{Request, Response};
struct_response! {
/// An `async` response from a dispatched [`LocalRequest`](super::LocalRequest).
///
/// This `LocalResponse` implements [`tokio::io::AsyncRead`]. As such, if
/// [`into_string()`](LocalResponse::into_string()) and
/// [`into_bytes()`](LocalResponse::into_bytes()) do not suffice, the response's
/// body can be read directly:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// use std::io;
///
/// use rocket::local::asynchronous::Client;
/// use rocket::tokio::io::AsyncReadExt;
/// use rocket::http::Status;
///
/// #[get("/")]
/// fn hello_world() -> &'static str {
/// "Hello, world!"
/// }
///
/// # /*
/// #[launch]
/// # */
/// fn rocket() -> rocket::Rocket {
/// rocket::ignite().mount("/", routes![hello_world])
/// }
///
/// # async fn read_body_manually() -> io::Result<()> {
/// // Dispatch a `GET /` request.
/// let client = Client::new(rocket()).await.expect("valid rocket");
/// let mut response = client.get("/").dispatch().await;
///
/// // Check metadata validity.
/// assert_eq!(response.status(), Status::Ok);
/// assert_eq!(response.body().unwrap().known_size(), Some(13));
///
/// // Read 10 bytes of the body. Note: in reality, we'd use `into_string()`.
/// let mut buffer = [0; 10];
/// response.read(&mut buffer).await?;
/// assert_eq!(buffer, "Hello, wor".as_bytes());
/// # Ok(())
/// # }
/// # rocket::async_test(read_body_manually()).expect("read okay");
/// ```
///
/// For more, see [the top-level documentation](../index.html#localresponse).
pub struct LocalResponse<'c> {
pub(in super) _request: Request<'c>,
pub(in super) inner: Response<'c>,
}
_request: Box<Request<'c>>,
response: Response<'c>,
}
impl<'c> LocalResponse<'c> {
fn _response(&self) -> &Response<'c> {
&self.inner
}
pub(crate) async fn _into_string(mut self) -> Option<String> {
self.inner.body_string().await
}
pub(crate) async fn _into_bytes(mut self) -> Option<Vec<u8>> {
self.inner.body_bytes().await
pub(crate) fn new<F, O>(req: Request<'c>, f: F) -> impl Future<Output = LocalResponse<'c>>
where F: FnOnce(&'c Request<'c>) -> O + Send,
O: Future<Output = Response<'c>> + Send
{
// `LocalResponse` is a self-referential structure. In particular,
// `inner` can refer to `_request` and its contents. As such, we must
// 1) Ensure `Request` has a stable address.
//
// This is done by `Box`ing the `Request`, using only the stable
// address thereafter.
//
// 2) Ensure no refs to `Request` or its contents leak with a lifetime
// extending beyond that of `&self`.
//
// We have no methods that return an `&Request`. However, we must
// also ensure that `Response` doesn't leak any such references. To
// do so, we don't expose the `Response` directly in any way;
// otherwise, methods like `.headers()` could, in conjuction with
// particular crafted `Responder`s, potentially be used to obtain a
// reference to contents of `Request`. All methods, instead, return
// references bounded by `self`. This is easily verified by nothing
// that 1) `LocalResponse` fields are private, and 2) all `impl`s
// of `LocalResponse` aside from this method abstract the lifetime
// away as `'_`, ensuring it is not used for any output value.
let boxed_req = Box::new(req);
let request: &'c Request<'c> = unsafe { &*(&*boxed_req as *const _) };
async move {
LocalResponse {
_request: boxed_req,
response: f(request).await
}
}
}
}
impl_response!("use rocket::local::asynchronous::Client;" @async await LocalResponse);
impl LocalResponse<'_> {
pub(crate) fn _response(&self) -> &Response<'_> {
&self.response
}
pub(crate) async fn _into_string(mut self) -> Option<String> {
self.response.body_string().await
}
pub(crate) async fn _into_bytes(mut self) -> Option<Vec<u8>> {
self.response.body_bytes().await
}
// Generates the public API methods, which call the private methods above.
pub_response_impl!("# use rocket::local::asynchronous::Client;
use rocket::local::asynchronous::LocalResponse;" async await);
}
impl AsyncRead for LocalResponse<'_> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8]
) -> Poll<io::Result<usize>> {
let body = match self.response.body_mut() {
Some(body) => body,
_ => return Poll::Ready(Ok(0))
};
Pin::new(body.as_reader()).poll_read(cx, buf)
}
}
impl std::fmt::Debug for LocalResponse<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._response().fmt(f)
}
}

View File

@ -1,16 +1,20 @@
use std::borrow::Cow;
use std::cell::RefCell;
use crate::error::LaunchError;
use crate::http::Method;
use crate::local::{asynchronous, blocking::LocalRequest};
use crate::local::{asynchronous, blocking::{LocalRequest, LocalResponse}};
use crate::rocket::{Rocket, Cargo};
struct_client! { [
/// A `blocking` client to construct and dispatch local requests.
///
/// For details, see [the top-level documentation](../index.html#client). For
/// the `async` version, see [`asynchronous::Client`].
///
/// ## Example
///
/// The following snippet creates a `Client` from a `Rocket` instance and
/// dispatches a local request to `POST /` with a body of `Hello, world!`.
/// dispatches a local `POST /` request with a body of `Hello, world!`.
///
/// ```rust
/// use rocket::local::blocking::Client;
@ -21,12 +25,10 @@ struct_client! { [
/// .body("Hello, world!")
/// .dispatch();
/// ```
]
pub struct Client {
pub(crate) inner: asynchronous::Client,
runtime: RefCell<tokio::runtime::Runtime>,
}
}
impl Client {
fn _new(rocket: Rocket, tracked: bool) -> Result<Client, LaunchError> {
@ -41,12 +43,16 @@ impl Client {
Ok(Self { inner, runtime: RefCell::new(runtime) })
}
// WARNING: This is unstable! Do not use this method outside of Rocket!
#[doc(hidden)]
/// WARNING: This is unstable! Do not use this method outside of Rocket!
pub fn _test<T, F: FnOnce(Self) -> T + Send>(f: F) -> T {
pub fn _test<T, F>(f: F) -> T
where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send
{
let rocket = crate::ignite();
let client = Client::new(rocket).expect("valid rocket");
f(client)
let request = client.get("/");
let response = request.clone().dispatch();
f(&client, request, response)
}
#[inline(always)]
@ -71,23 +77,13 @@ impl Client {
{
LocalRequest::new(self, method, uri.into())
}
}
impl_client!("use rocket::local::blocking::Client;" Client);
// Generates the public API methods, which call the private methods above.
pub_client_impl!("use rocket::local::blocking::Client;");
}
#[cfg(doctest)]
mod doctest {
/// ```no_run
/// // Just to ensure we get the path/form right in the following tests.
/// use rocket::local::blocking::Client;
///
/// fn test<T>() {};
/// test::<Client>();
///
/// fn is_send<T: Send>() {};
/// is_send::<Client>();
/// ```
///
/// ```compile_fail
/// use rocket::local::blocking::Client;
///
@ -95,5 +91,5 @@ mod doctest {
/// not_sync::<Client>();
/// ```
#[allow(dead_code)]
fn test_not_sync_or_send() {}
fn test_not_sync() {}
}

View File

@ -1,9 +1,11 @@
//! Structures for blocking local dispatching of requests, primarily for
//! testing.
//! Blocking local dispatching of requests.
//!
//! This module contains the `blocking` variant of the `local` API: it can be
//! used in Rust's synchronous `#[test]` harness. This is accomplished by
//! starting and running an interal asynchronous Runtime as needed.
//! starting and running an interal asynchronous Runtime as needed. For the
//! asynchronous variant, see [`asynchronous`](super::asynchronous).
//!
//! See the [top-level documentation](super) for more usage details.
mod client;
mod request;

View File

@ -4,11 +4,14 @@ use crate::{Request, http::Method, local::asynchronous};
use super::{Client, LocalResponse};
struct_request! { [
/// A `blocking` local request as returned by [`Client`](super::Client).
///
/// For details, see [the top-level documentation](../index.html#localrequest).
///
/// ## Example
///
/// The following snippet uses the available builder methods to construct a
/// `POST` request to `/` with a JSON body:
/// The following snippet uses the available builder methods to construct and
/// dispatch a `POST` request to `/` with a JSON body:
///
/// ```rust
/// use rocket::local::blocking::{Client, LocalRequest};
@ -20,14 +23,14 @@ struct_request! { [
/// .remote("127.0.0.1:8000".parse().unwrap())
/// .cookie(Cookie::new("name", "value"))
/// .body(r#"{ "value": 42 }"#);
///
/// let response = req.dispatch();
/// ```
]
#[derive(Clone)]
pub struct LocalRequest<'c> {
inner: asynchronous::LocalRequest<'c>,
client: &'c Client,
}
}
impl<'c> LocalRequest<'c> {
#[inline]
@ -58,6 +61,13 @@ impl<'c> LocalRequest<'c> {
let inner = self.client.block_on(self.inner.dispatch());
LocalResponse { inner, client: self.client }
}
pub_request_impl!("# use rocket::local::blocking::Client;
use rocket::local::blocking::LocalRequest;");
}
impl_request!("use rocket::local::blocking::Client;" LocalRequest);
impl std::fmt::Debug for LocalRequest<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._request().fmt(f)
}
}

View File

@ -1,17 +1,63 @@
use std::io;
use tokio::io::AsyncReadExt;
use crate::{Response, local::asynchronous};
use super::Client;
struct_response! {
/// A `blocking` response from a dispatched [`LocalRequest`](super::LocalRequest).
///
/// This `LocalResponse` implements [`io::Read`]. As such, if
/// [`into_string()`](LocalResponse::into_string()) and
/// [`into_bytes()`](LocalResponse::into_bytes()) do not suffice, the response's
/// body can be read directly:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// use std::io::{self, Read};
///
/// use rocket::local::blocking::Client;
/// use rocket::http::Status;
///
/// #[get("/")]
/// fn hello_world() -> &'static str {
/// "Hello, world!"
/// }
///
/// # /*
/// #[launch]
/// # */
/// fn rocket() -> rocket::Rocket {
/// rocket::ignite().mount("/", routes![hello_world])
/// }
///
/// # fn read_body_manually() -> io::Result<()> {
/// // Dispatch a `GET /` request.
/// let client = Client::new(rocket()).expect("valid rocket");
/// let mut response = client.get("/").dispatch();
///
/// // Check metadata validity.
/// assert_eq!(response.status(), Status::Ok);
/// assert_eq!(response.body().unwrap().known_size(), Some(13));
///
/// // Read 10 bytes of the body. Note: in reality, we'd use `into_string()`.
/// let mut buffer = [0; 10];
/// response.read(&mut buffer)?;
/// assert_eq!(buffer, "Hello, wor".as_bytes());
/// # Ok(())
/// # }
/// # read_body_manually().expect("read okay");
/// ```
///
/// For more, see [the top-level documentation](../index.html#localresponse).
pub struct LocalResponse<'c> {
pub(in super) inner: asynchronous::LocalResponse<'c>,
pub(in super) client: &'c Client,
}
}
impl<'c> LocalResponse<'c> {
fn _response(&self) -> &Response<'c> {
&*self.inner
impl LocalResponse<'_> {
fn _response(&self) -> &Response<'_> {
&self.inner._response()
}
fn _into_string(self) -> Option<String> {
@ -21,6 +67,20 @@ impl<'c> LocalResponse<'c> {
fn _into_bytes(self) -> Option<Vec<u8>> {
self.client.block_on(self.inner._into_bytes())
}
// Generates the public API methods, which call the private methods above.
pub_response_impl!("# use rocket::local::blocking::Client;
use rocket::local::blocking::LocalResponse;");
}
impl_response!("use rocket::local::blocking::Client;" LocalResponse);
impl io::Read for LocalResponse<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.client.block_on(self.inner.read(buf))
}
}
impl std::fmt::Debug for LocalResponse<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._response().fmt(f)
}
}

View File

@ -1,41 +1,3 @@
macro_rules! struct_client {
([$(#[$attr:meta])*] $item:item) =>
{
/// A structure to construct requests for local dispatching.
///
/// # Usage
///
/// A `Client` is constructed via the [`new()`] or [`untracked()`] methods from
/// an already constructed `Rocket` instance. Once a value of `Client` has been
/// constructed, the [`LocalRequest`] constructor methods ([`get()`], [`put()`],
/// [`post()`], and so on) can be used to create a `LocalRequest` for
/// dispatching.
///
/// See the [top-level documentation](crate::local) for more usage information.
///
/// ## Cookie Tracking
///
/// A `Client` constructed using [`new()`] propagates cookie changes made by
/// responses to previously dispatched requests. In other words, if a previously
/// dispatched request resulted in a response that adds a cookie, any future
/// requests will contain that cookie. Similarly, cookies removed by a response
/// won't be propagated further.
///
/// This is typically the desired mode of operation for a `Client` as it removes
/// the burden of manually tracking cookies. Under some circumstances, however,
/// disabling this tracking may be desired. In these cases, use the
/// [`untracked()`](Client::untracked()) constructor to create a `Client` that
/// _will not_ track cookies.
$(#[$attr])*
///
/// [`new()`]: #method.new
/// [`untracked()`]: #method.untracked
/// [`get()`]: #method.get
/// [`put()`]: #method.put
/// [`post()`]: #method.post
$item
}}
macro_rules! req_method {
($import:literal, $NAME:literal, $f:ident, $method:expr) => (
req_method!(@
@ -62,8 +24,8 @@ macro_rules! req_method {
/// ```rust,no_run
#[doc = $import]
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// # Client::_test(|client, _, _| {
/// let client: &Client = client;
#[doc = $use_it]
/// # });
/// ```
@ -76,137 +38,142 @@ macro_rules! req_method {
)
}
macro_rules! impl_client {
($import:literal $(@$prefix:tt $suffix:tt)? $name:ident) =>
macro_rules! pub_client_impl {
($import:literal $(@$prefix:tt $suffix:tt)?) =>
{
impl $name {
/// Construct a new `Client` from an instance of `Rocket` with cookie
/// tracking.
///
/// # Cookie Tracking
///
/// By default, a `Client` propagates cookie changes made by responses
/// to previously dispatched requests. In other words, if a previously
/// dispatched request resulted in a response that adds a cookie, any
/// future requests will contain the new cookies. Similarly, cookies
/// removed by a response won't be propagated further.
///
/// This is typically the desired mode of operation for a `Client` as it
/// removes the burden of manually tracking cookies. Under some
/// circumstances, however, disabling this tracking may be desired. The
/// [`untracked()`](Client::untracked()) method creates a `Client` that
/// _will not_ track cookies.
///
/// # Errors
///
/// If launching the `Rocket` instance would fail, excepting network errors,
/// the `LaunchError` is returned.
///
/// ```rust,no_run
#[doc = $import]
///
/// let rocket = rocket::ignite();
/// let client = Client::new(rocket);
/// ```
#[inline(always)]
pub $($prefix)? fn new(rocket: Rocket) -> Result<Self, LaunchError> {
Self::_new(rocket, true) $(.$suffix)?
}
/// Construct a new `Client` from an instance of `Rocket` with cookie
/// tracking.
///
/// # Cookie Tracking
///
/// By default, a `Client` propagates cookie changes made by responses
/// to previously dispatched requests. In other words, if a previously
/// dispatched request resulted in a response that adds a cookie, any
/// future requests will contain the new cookies. Similarly, cookies
/// removed by a response won't be propagated further.
///
/// This is typically the desired mode of operation for a `Client` as it
/// removes the burden of manually tracking cookies. Under some
/// circumstances, however, disabling this tracking may be desired. The
/// [`untracked()`](Client::untracked()) method creates a `Client` that
/// _will not_ track cookies.
///
/// # Errors
///
/// If launching the `Rocket` instance would fail, excepting network errors,
/// the `LaunchError` is returned.
///
/// ```rust,no_run
#[doc = $import]
///
/// let rocket = rocket::ignite();
/// let client = Client::new(rocket);
/// ```
#[inline(always)]
pub $($prefix)? fn new(rocket: Rocket) -> Result<Self, LaunchError> {
Self::_new(rocket, true) $(.$suffix)?
}
/// Construct a new `Client` from an instance of `Rocket` _without_
/// cookie tracking.
///
/// # Cookie Tracking
///
/// Unlike the [`new()`](Client::new()) constructor, a `Client` returned
/// from this method _does not_ automatically propagate cookie changes.
///
/// # Errors
///
/// If launching the `Rocket` instance would fail, excepting network
/// errors, the `LaunchError` is returned.
///
/// ```rust,no_run
#[doc = $import]
///
/// let rocket = rocket::ignite();
/// let client = Client::untracked(rocket);
/// ```
pub $($prefix)? fn untracked(rocket: Rocket) -> Result<Self, LaunchError> {
Self::_new(rocket, true) $(.$suffix)?
}
/// Construct a new `Client` from an instance of `Rocket` _without_
/// cookie tracking.
///
/// # Cookie Tracking
///
/// Unlike the [`new()`](Client::new()) constructor, a `Client` returned
/// from this method _does not_ automatically propagate cookie changes.
///
/// # Errors
///
/// If launching the `Rocket` instance would fail, excepting network
/// errors, the `LaunchError` is returned.
///
/// ```rust,no_run
#[doc = $import]
///
/// let rocket = rocket::ignite();
/// let client = Client::untracked(rocket);
/// ```
pub $($prefix)? fn untracked(rocket: Rocket) -> Result<Self, LaunchError> {
Self::_new(rocket, true) $(.$suffix)?
}
/// Returns a reference to the `Rocket` this client is creating requests
/// for.
///
/// # Example
///
/// ```rust,no_run
#[doc = $import]
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let rocket = client.rocket();
/// # });
/// ```
#[inline(always)]
pub fn rocket(&self) -> &Rocket {
&*self._cargo()
}
/// Returns a reference to the `Rocket` this client is creating requests
/// for.
///
/// # Example
///
/// ```rust,no_run
#[doc = $import]
///
/// # Client::_test(|client, _, _| {
/// let client: &Client = client;
/// let rocket = client.rocket();
/// # });
/// ```
#[inline(always)]
pub fn rocket(&self) -> &Rocket {
&*self._cargo()
}
/// Returns a reference to the `Cargo` of the `Rocket` this client is
/// creating requests for.
///
/// # Example
///
/// ```rust
#[doc = $import]
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let cargo = client.cargo();
/// # });
/// ```
#[inline(always)]
pub fn cargo(&self) -> &Cargo {
self._cargo()
}
/// Returns a reference to the `Cargo` of the `Rocket` this client is
/// creating requests for.
///
/// # Example
///
/// ```rust
#[doc = $import]
///
/// # Client::_test(|client, _, _| {
/// let client: &Client = client;
/// let cargo = client.cargo();
/// # });
/// ```
#[inline(always)]
pub fn cargo(&self) -> &Cargo {
self._cargo()
}
req_method!($import, "GET", get, Method::Get);
req_method!($import, "PUT", put, Method::Put);
req_method!($import, "POST", post, Method::Post);
req_method!($import, "DELETE", delete, Method::Delete);
req_method!($import, "OPTIONS", options, Method::Options);
req_method!($import, "HEAD", head, Method::Head);
req_method!($import, "PATCH", patch, Method::Patch);
req_method!($import, "GET", get, Method::Get);
req_method!($import, "PUT", put, Method::Put);
req_method!($import, "POST", post, Method::Post);
req_method!($import, "DELETE", delete, Method::Delete);
req_method!($import, "OPTIONS", options, Method::Options);
req_method!($import, "HEAD", head, Method::Head);
req_method!($import, "PATCH", patch, Method::Patch);
/// Create a local `GET` request to the URI `uri`.
///
/// When dispatched, the request will be served by the instance of
/// Rocket within `self`. The request is not dispatched automatically.
/// To actually dispatch the request, call [`LocalRequest::dispatch()`]
/// on the returned request.
///
/// # Example
///
/// ```rust,no_run
#[doc = $import]
/// use rocket::http::Method;
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// client.req(Method::Get, "/hello");
/// # });
/// ```
#[inline(always)]
pub fn req<'c, 'u: 'c, U>(
&'c self,
method: Method,
uri: U
) -> LocalRequest<'c>
where U: Into<Cow<'u, str>>
{
self._req(method, uri)
}
/// Create a local `GET` request to the URI `uri`.
///
/// When dispatched, the request will be served by the instance of
/// Rocket within `self`. The request is not dispatched automatically.
/// To actually dispatch the request, call [`LocalRequest::dispatch()`]
/// on the returned request.
///
/// # Example
///
/// ```rust,no_run
#[doc = $import]
/// use rocket::http::Method;
///
/// # Client::_test(|client, _, _| {
/// let client: &Client = client;
/// client.req(Method::Get, "/hello");
/// # });
/// ```
#[inline(always)]
pub fn req<'c, 'u: 'c, U>(
&'c self,
method: Method,
uri: U
) -> LocalRequest<'c>
where U: Into<Cow<'u, str>>
{
self._req(method, uri)
}
#[cfg(test)]
#[allow(dead_code)]
fn _ensure_impls_exist() {
fn is_send<T: Send>() {}
is_send::<Self>();
}
}}

View File

@ -5,81 +5,33 @@
//! and integration test Rocket applications by crafting requests, dispatching
//! them, and verifying the response.
//!
//! # Usage
//! # Async. vs. Blocking
//!
//! This module contains two variants of the local API: [`asynchronous`] and
//! [`blocking`]. The primary difference between the two is in usage: the
//! `asynchronous` API requires an asynchronous test entry point such as
//! `#[rocket::async_test]`, while the `blocking` API can be used with
//! `#[test]`. Additionally, several methods in the `asynchronous` API are
//! `async` and must therefore be `await`ed.
//! This module contains two variants, in its two submodules, of the same local
//! API: [`asynchronous`], and [`blocking`]. As their names imply, the
//! `asynchronous` API is `async`, returning a `Future` for operations that
//! would otherwise block, while `blocking` blocks for the same operations.
//!
//! Both APIs include a [`Client`] structure that is used to create
//! [`LocalRequest`] structures that can be dispatched against a given
//! [`Rocket`](crate::Rocket) instance. Usage is straightforward:
//! Unless your request handling requires concurrency to make progress, or
//! you're making use of a `Client` in an environment that necessitates or would
//! benefit from concurrency, you should use the `blocking` set of APIs due
//! their ease-of-use. If your request handling _does_ require concurrency to
//! make progress, for instance by having one handler `await` a response
//! generated from a request to another handler, use the `asynchronous` set of
//! APIs.
//!
//! 1. Construct a `Rocket` instance that represents the application.
//!
//! ```rust
//! let rocket = rocket::ignite();
//! # let _ = rocket;
//! ```
//!
//! 2. Construct a `Client` using the `Rocket` instance.
//!
//! ```rust
//! # use rocket::local::asynchronous::Client;
//! # let rocket = rocket::ignite();
//! # rocket::async_test(async {
//! let client = Client::new(rocket).await.expect("valid rocket instance");
//! # let _ = client;
//! # });
//! ```
//!
//! 3. Construct requests using the `Client` instance.
//!
//! ```rust
//! # use rocket::local::asynchronous::Client;
//! # let rocket = rocket::ignite();
//! # rocket::async_test(async {
//! # let client = Client::new(rocket).await.unwrap();
//! let req = client.get("/");
//! # let _ = req;
//! # });
//! ```
//!
//! 3. Dispatch the request to retrieve the response.
//!
//! ```rust
//! # use rocket::local::asynchronous::Client;
//! # let rocket = rocket::ignite();
//! # rocket::async_test(async {
//! # let client = Client::new(rocket).await.unwrap();
//! # let req = client.get("/");
//! let response = req.dispatch().await;
//! # let _ = response;
//! # });
//! ```
//!
//! All together and in idiomatic fashion, this might look like:
//!
//! ```rust
//! use rocket::local::asynchronous::Client;
//!
//! # rocket::async_test(async {
//! let client = Client::new(rocket::ignite()).await.expect("valid rocket");
//! let response = client.post("/")
//! .body("Hello, world!")
//! .dispatch().await;
//! # let _ = response;
//! # });
//! ```
//! Both APIs include a `Client` structure that is used to create `LocalRequest`
//! structures that can be dispatched against a given [`Rocket`](crate::Rocket)
//! instance to yield a `LocalResponse` structure. The APIs are identical except
//! in that the `asynchronous` APIs return `Future`s for otherwise blocking
//! operations.
//!
//! # Unit/Integration Testing
//!
//! This module can be used to test a Rocket application by constructing
//! requests via `Client` and validating the resulting response. As an example,
//! consider the following complete "Hello, world!" application, with testing.
//! This module is primarily intended to be used to test a Rocket application by
//! constructing requests via `Client`, dispatching them, and validating the
//! resulting response. As a complete example, consider the following "Hello,
//! world!" application, with testing.
//!
//! ```rust
//! #![feature(proc_macro_hygiene)]
@ -91,27 +43,143 @@
//! "Hello, world!"
//! }
//!
//! # fn main() { }
//! # /*
//! #[launch]
//! # */
//! fn rocket() -> rocket::Rocket {
//! rocket::ignite().mount("/", routes![hello])
//! }
//!
//! #[cfg(test)]
//! mod test {
//! use super::{rocket, hello};
//! use rocket::local::asynchronous::Client;
//!
//! #[rocket::async_test]
//! fn test_hello_world() {
//! // Using the preferred `blocking` API.
//! #[test]
//! fn test_hello_world_blocking() {
//! // Construct a client to use for dispatching requests.
//! let rocket = rocket::ignite().mount("/", routes![hello]);
//! let client = Client::new(rocket).expect("valid rocket instance");
//! use rocket::local::blocking::Client;
//! let client = Client::new(super::rocket())
//! .expect("valid `Rocket`");
//!
//! // Dispatch a request to 'GET /' and validate the response.
//! let mut response = client.get("/").dispatch().await;
//! assert_eq!(response.into_string().await, Some("Hello, world!".into()));
//! let response = client.get("/").dispatch();
//! assert_eq!(response.into_string().unwrap(), "Hello, world!");
//! }
//!
//! // Using the `asynchronous` API.
//! #[rocket::async_test]
//! async fn test_hello_world_async() {
//! // Construct a client to use for dispatching requests.
//! use rocket::local::asynchronous::Client;
//! let client = Client::new(super::rocket()).await
//! .expect("valid `Rocket`");
//!
//! // Dispatch a request to 'GET /' and validate the response.
//! let response = client.get("/").dispatch().await;
//! assert_eq!(response.into_string().await.unwrap(), "Hello, world!");
//! }
//! }
//! ```
//!
//! For more details on testing, see the [testing guide].
//!
//! [testing guide]: https://rocket.rs/v0.5/guide/testing/
//! [`Client`]: crate::local::asynchronous::Client
//! [`LocalRequest`]: crate::local::asynchronous::LocalRequest
//!
//! # `Client`
//!
//! A `Client`, either [`blocking::Client`] or [`asynchronous::Client`],
//! referred to as simply [`Client`] and [`async` `Client`], respectively,
//! constructs requests for local dispatching.
//!
//! **Usage**
//!
//! A `Client` is constructed via the [`new()`] ([`async` `new()`]) or
//! [`untracked()`] ([`async` `untracked()`]) methods from an already
//! constructed `Rocket` instance. Once a value of `Client` has been
//! constructed, [`get()`], [`put()`], [`post()`], and so on ([`async` `get()`],
//! [`async` `put()`], [`async` `post()`]) can be called to create a
//! [`LocalRequest`] ([`async` `LocalRequest`]) for dispatching.
//!
//! **Cookie Tracking**
//!
//! A `Client` constructed using `new()` propagates cookie changes made by
//! responses to previously dispatched requests. In other words, if a previously
//! dispatched request resulted in a response that adds a cookie, any future
//! requests will contain that cookie. Similarly, cookies removed by a response
//! won't be propagated further.
//!
//! This is typically the desired mode of operation for a `Client` as it removes
//! the burden of manually tracking cookies. Under some circumstances, however,
//! disabling this tracking may be desired. In these cases, use the
//! `untracked()` constructor to create a `Client` that _will not_ track
//! cookies.
//!
//! [`Client`]: blocking::Client
//! [`async` `Client`]: asynchronous::Client
//! [`LocalRequest`]: blocking::LocalRequest
//! [`async` `LocalRequest`]: asynchronous::LocalRequest
//!
//! [`new()`]: blocking::Client::new()
//! [`untracked()`]: blocking::Client::untracked()
//! [`async` `new()`]: asynchronous::Client::new()
//! [`async` `untracked()`]: asynchronous::Client::untracked()
//!
//! [`get()`]: blocking::Client::get()
//! [`put()`]: blocking::Client::put()
//! [`post()`]: blocking::Client::post()
//! [`async` `get()`]: asynchronous::Client::get()
//! [`async` `put()`]: asynchronous::Client::put()
//! [`async` `post()`]: asynchronous::Client::post()
//!
//! **Example**
//!
//! For a usage example, see [`Client`] or [`async` `Client`].
//!
//! # `LocalRequest`
//!
//! A [`LocalRequest`] ([`async` `LocalRequest`]) is constructed via a `Client`.
//! Once obtained, headers, cookies, including private cookies, the remote IP
//! address, and the request body can all be set via methods on the
//! `LocalRequest` structure.
//!
//! **Dispatching**
//!
//! A `LocalRequest` is dispatched by calling [`dispatch()`] ([`async`
//! `dispatch()`]). The `LocalRequest` is consumed and a [`LocalResponse`]
//! ([`async` `LocalResponse`]) is returned.
//!
//! Note that `LocalRequest` implements [`Clone`]. As such, if the same request
//! needs to be dispatched multiple times, the request can first be cloned and
//! then dispatched: `request.clone().dispatch()`.
//!
//! **Example**
//!
//! For a usage example, see [`LocalRequest`] or [`async` `LocalRequest`].
//!
//! [`LocalResponse`]: blocking::LocalResponse
//! [`async` `LocalResponse`]: asynchronous::LocalResponse
//! [`dispatch()`]: blocking::LocalRequest::dispatch()
//! [`async` `dispatch()`]: asynchronous::LocalRequest::dispatch()
//!
//! # `LocalResponse`
//!
//! The result of `dispatch()`ing a `LocalRequest` is a [`LocalResponse`]
//! ([`async` `LocalResponse`]). A `LocalResponse` can be queried for response
//! metadata, including the HTTP status, headers, and cookies, via its getter
//! methods. Additionally, the body of the response can be read into a string
//! ([`into_string()`] or [`async` `into_string()`]) or a vector
//! ([`into_bytes()`] or [`async` `into_bytes()`]).
//!
//! The response body must also be read directly using standard I/O mechanisms:
//! the `blocking` `LocalResponse` implements `Read` while the `async`
//! `LocalResponse` implements `AsyncRead`.
//!
//! For a usage example, see [`LocalResponse`] or [`async` `LocalResponse`].
//!
//! [`into_string()`]: blocking::LocalResponse::into_string()
//! [`async` `into_string()`]: asynchronous::LocalResponse::into_string()
//! [`into_bytes()`]: blocking::LocalResponse::into_bytes()
//! [`async` `into_bytes()`]: asynchronous::LocalResponse::into_bytes()
#[macro_use] mod client;
#[macro_use] mod request;

View File

@ -1,286 +1,246 @@
macro_rules! struct_request {
([$(#[$attr:meta])*] $item:item) =>
macro_rules! pub_request_impl {
($import:literal $($prefix:tt $suffix:tt)?) =>
{
/// A structure representing a local request as created by [`Client`].
/// Retrieves the inner `Request` as seen by Rocket.
///
/// # Usage
/// # Example
///
/// A `LocalRequest` value is constructed via method constructors on [`Client`].
/// Headers can be added via the [`header`] builder method and the
/// [`add_header`] method. Cookies can be added via the [`cookie`] builder
/// method. The remote IP address can be set via the [`remote`] builder method.
/// The body of the request can be set via the [`body`] builder method or
/// [`set_body`] method.
/// ```rust
#[doc = $import]
///
$(#[$attr])*
///
/// # Dispatching
///
/// A `LocalRequest` is dispatched by calling [`dispatch`].
/// The `LocalRequest` is consumed and a response is returned.
///
/// Note that `LocalRequest` implements `Clone`. As such, if the
/// same request needs to be dispatched multiple times, the request can first be
/// cloned and then dispatched: `request.clone().dispatch()`.
///
/// [`Client`]: super::Client
/// [`header`]: #method.header
/// [`add_header`]: #method.add_header
/// [`cookie`]: #method.cookie
/// [`remote`]: #method.remote
/// [`body`]: #method.body
/// [`set_body`]: #method.set_body
/// [`dispatch`]: #method.dispatch
$item
}}
/// # Client::_test(|_, request, _| {
/// let request: LocalRequest = request;
/// let inner: &rocket::Request = request.inner();
/// # });
/// ```
#[inline(always)]
pub fn inner(&self) -> &Request<'c> {
self._request()
}
macro_rules! impl_request {
($import:literal $(@$prefix:tt $suffix:tt)? $name:ident) =>
{
impl<'c> $name<'c> {
/// Retrieves the inner `Request` as seen by Rocket.
///
/// # Example
///
/// ```rust
#[doc = $import]
/// use rocket::Request;
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let req = client.get("/");
/// let inner: &Request = req.inner();
/// # });
/// ```
#[inline(always)]
pub fn inner(&self) -> &Request<'c> {
self._request()
}
/// Add a header to this request.
///
/// Any type that implements `Into<Header>` can be used here. Among
/// others, this includes [`ContentType`] and [`Accept`].
///
/// [`ContentType`]: crate::http::ContentType
/// [`Accept`]: crate::http::Accept
///
/// # Examples
///
/// Add the Content-Type header:
///
/// ```rust
#[doc = $import]
/// use rocket::http::Header;
/// use rocket::http::ContentType;
///
/// # Client::_test(|_, request, _| {
/// let request: LocalRequest = request;
/// let req = request
/// .header(ContentType::JSON)
/// .header(Header::new("X-Custom", "custom-value"));
/// # });
/// ```
#[inline]
pub fn header<H>(mut self, header: H) -> Self
where H: Into<crate::http::Header<'static>>
{
self._request_mut().add_header(header.into());
self
}
/// Add a header to this request.
///
/// Any type that implements `Into<Header>` can be used here. Among
/// others, this includes [`ContentType`] and [`Accept`].
///
/// [`ContentType`]: crate::http::ContentType
/// [`Accept`]: crate::http::Accept
///
/// # Examples
///
/// Add the Content-Type header:
///
/// ```rust
#[doc = $import]
/// use rocket::http::ContentType;
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let req = client.get("/").header(ContentType::JSON);
/// # });
/// ```
#[inline]
pub fn header<H>(mut self, header: H) -> Self
where H: Into<crate::http::Header<'static>>
{
self._request_mut().add_header(header.into());
self
}
/// Adds a header to this request without consuming `self`.
///
/// # Examples
///
/// Add the Content-Type header:
///
/// ```rust
#[doc = $import]
/// use rocket::http::ContentType;
///
/// # Client::_test(|_, mut request, _| {
/// let mut request: LocalRequest = request;
/// request.add_header(ContentType::JSON);
/// # });
/// ```
#[inline]
pub fn add_header<H>(&mut self, header: H)
where H: Into<crate::http::Header<'static>>
{
self._request_mut().add_header(header.into());
}
/// Adds a header to this request without consuming `self`.
///
/// # Examples
///
/// Add the Content-Type header:
///
/// ```rust
#[doc = $import]
/// use rocket::http::ContentType;
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let mut req = client.get("/");
/// req.add_header(ContentType::JSON);
/// # });
/// ```
#[inline]
pub fn add_header<H>(&mut self, header: H)
where H: Into<crate::http::Header<'static>>
{
self._request_mut().add_header(header.into());
}
/// Set the remote address of this request.
///
/// # Examples
///
/// Set the remote address to "8.8.8.8:80":
///
/// ```rust
#[doc = $import]
///
/// # Client::_test(|_, request, _| {
/// let request: LocalRequest = request;
/// let address = "8.8.8.8:80".parse().unwrap();
/// let req = request.remote(address);
/// # });
/// ```
#[inline]
pub fn remote(mut self, address: std::net::SocketAddr) -> Self {
self._request_mut().set_remote(address);
self
}
/// Set the remote address of this request.
///
/// # Examples
///
/// Set the remote address to "8.8.8.8:80":
///
/// ```rust
#[doc = $import]
///
/// # Client::_test(|client| {
/// let address = "8.8.8.8:80".parse().unwrap();
///
/// let client: Client = client;
/// let req = client.get("/").remote(address);
/// # });
/// ```
#[inline]
pub fn remote(mut self, address: std::net::SocketAddr) -> Self {
self._request_mut().set_remote(address);
self
}
/// Add a cookie to this request.
///
/// # Examples
///
/// Add `user_id` cookie:
///
/// ```rust
#[doc = $import]
/// use rocket::http::Cookie;
///
/// # Client::_test(|_, request, _| {
/// let request: LocalRequest = request;
/// let req = request
/// .cookie(Cookie::new("username", "sb"))
/// .cookie(Cookie::new("user_id", "12"));
/// # });
/// ```
#[inline]
pub fn cookie(self, cookie: crate::http::Cookie<'_>) -> Self {
self._request().cookies().add_original(cookie.into_owned());
self
}
/// Add a cookie to this request.
///
/// # Examples
///
/// Add `user_id` cookie:
///
/// ```rust
#[doc = $import]
/// use rocket::http::Cookie;
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let req = client.get("/")
/// .cookie(Cookie::new("username", "sb"))
/// .cookie(Cookie::new("user_id", "12"));
/// # });
/// ```
#[inline]
pub fn cookie(self, cookie: crate::http::Cookie<'_>) -> Self {
/// Add all of the cookies in `cookies` to this request.
///
/// # Examples
///
/// Add `user_id` cookie:
///
/// ```rust
#[doc = $import]
/// use rocket::http::Cookie;
///
/// # Client::_test(|_, request, _| {
/// let request: LocalRequest = request;
/// let cookies = vec![Cookie::new("a", "b"), Cookie::new("c", "d")];
/// let req = request.cookies(cookies);
/// # });
/// ```
#[inline]
pub fn cookies(self, cookies: Vec<crate::http::Cookie<'_>>) -> Self {
for cookie in cookies {
self._request().cookies().add_original(cookie.into_owned());
self
}
/// Add all of the cookies in `cookies` to this request.
///
/// # Examples
///
/// Add `user_id` cookie:
///
/// ```rust
#[doc = $import]
/// use rocket::http::Cookie;
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let cookies = vec![Cookie::new("a", "b"), Cookie::new("c", "d")];
/// let req = client.get("/").cookies(cookies);
/// # });
/// ```
#[inline]
pub fn cookies(self, cookies: Vec<crate::http::Cookie<'_>>) -> Self {
for cookie in cookies {
self._request().cookies().add_original(cookie.into_owned());
}
self
}
/// Add a [private cookie] to this request.
///
/// This method is only available when the `private-cookies` feature is
/// enabled.
///
/// [private cookie]: crate::http::Cookies::add_private()
///
/// # Examples
///
/// Add `user_id` as a private cookie:
///
/// ```rust
#[doc = $import]
/// use rocket::http::Cookie;
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let req = client.get("/").private_cookie(Cookie::new("user_id", "sb"));
/// # });
/// ```
#[inline]
#[cfg(feature = "private-cookies")]
pub fn private_cookie(self, cookie: crate::http::Cookie<'static>) -> Self {
self._request().cookies().add_original_private(cookie);
self
}
/// Set the body (data) of the request.
///
/// # Examples
///
/// Set the body to be a JSON structure; also sets the Content-Type.
///
/// ```rust
#[doc = $import]
/// use rocket::http::ContentType;
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let req = client.post("/")
/// .header(ContentType::JSON)
/// .body(r#"{ "key": "value", "array": [1, 2, 3], }"#);
/// # });
/// ```
#[inline]
pub fn body<S: AsRef<[u8]>>(mut self, body: S) -> Self {
// TODO: For CGI, we want to be able to set the body to be stdin
// without actually reading everything into a vector. Can we allow
// that here while keeping the simplicity? Looks like it would
// require us to reintroduce a NetStream::Local(Box<Read>) or
// something like that.
*self._body_mut() = body.as_ref().into();
self
}
/// Set the body (data) of the request without consuming `self`.
///
/// # Examples
///
/// Set the body to be a JSON structure; also sets the Content-Type.
///
/// ```rust
#[doc = $import]
/// use rocket::http::ContentType;
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let mut req = client.post("/").header(ContentType::JSON);
/// req.set_body(r#"{ "key": "value", "array": [1, 2, 3], }"#);
/// # });
/// ```
#[inline]
pub fn set_body<S: AsRef<[u8]>>(&mut self, body: S) {
*self._body_mut() = body.as_ref().into();
}
/// Dispatches the request, returning the response.
///
/// This method consumes `self` and is the preferred mechanism for
/// dispatching.
///
/// # Example
///
/// ```rust
/// use rocket::local::asynchronous::Client;
///
/// # rocket::async_test(async {
/// let client = Client::new(rocket::ignite()).await.unwrap();
/// let response = client.get("/").dispatch();
/// # });
/// ```
#[inline(always)]
pub $($prefix)? fn dispatch(self) -> LocalResponse<'c> {
self._dispatch()$(.$suffix)?
}
self
}
impl std::fmt::Debug for $name<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._request().fmt(f)
}
/// Add a [private cookie] to this request.
///
/// This method is only available when the `private-cookies` feature is
/// enabled.
///
/// [private cookie]: crate::http::Cookies::add_private()
///
/// # Examples
///
/// Add `user_id` as a private cookie:
///
/// ```rust
#[doc = $import]
/// use rocket::http::Cookie;
///
/// # Client::_test(|_, request, _| {
/// let request: LocalRequest = request;
/// let req = request.private_cookie(Cookie::new("user_id", "sb"));
/// # });
/// ```
#[inline]
#[cfg(feature = "private-cookies")]
pub fn private_cookie(self, cookie: crate::http::Cookie<'static>) -> Self {
self._request().cookies().add_original_private(cookie);
self
}
// TODO: Add test to check that `LocalRequest` is `Clone`.
/// Set the body (data) of the request.
///
/// # Examples
///
/// Set the body to be a JSON structure; also sets the Content-Type.
///
/// ```rust
#[doc = $import]
/// use rocket::http::ContentType;
///
/// # Client::_test(|_, request, _| {
/// let request: LocalRequest = request;
/// let req = request
/// .header(ContentType::JSON)
/// .body(r#"{ "key": "value", "array": [1, 2, 3], }"#);
/// # });
/// ```
#[inline]
pub fn body<S: AsRef<[u8]>>(mut self, body: S) -> Self {
// TODO: For CGI, we want to be able to set the body to be stdin
// without actually reading everything into a vector. Can we allow
// that here while keeping the simplicity? Looks like it would
// require us to reintroduce a NetStream::Local(Box<Read>) or
// something like that.
*self._body_mut() = body.as_ref().into();
self
}
/// Set the body (data) of the request without consuming `self`.
///
/// # Examples
///
/// Set the body to be a JSON structure; also sets the Content-Type.
///
/// ```rust
#[doc = $import]
/// use rocket::http::ContentType;
///
/// # Client::_test(|_, request, _| {
/// let request: LocalRequest = request;
/// let mut request = request.header(ContentType::JSON);
/// request.set_body(r#"{ "key": "value", "array": [1, 2, 3], }"#);
/// # });
/// ```
#[inline]
pub fn set_body<S: AsRef<[u8]>>(&mut self, body: S) {
*self._body_mut() = body.as_ref().into();
}
/// Dispatches the request, returning the response.
///
/// This method consumes `self` and is the preferred mechanism for
/// dispatching.
///
/// # Example
///
/// ```rust
#[doc = $import]
///
/// # Client::_test(|_, request, _| {
/// let request: LocalRequest = request;
/// let response = request.dispatch();
/// # });
/// ```
#[inline(always)]
pub $($prefix)? fn dispatch(self) -> LocalResponse<'c> {
self._dispatch()$(.$suffix)?
}
#[cfg(test)]
#[allow(dead_code)]
fn _ensure_impls_exist() {
fn is_clone_debug<T: Clone + std::fmt::Debug>() {}
is_clone_debug::<Self>();
}
}}

View File

@ -1,74 +1,89 @@
macro_rules! struct_response {
($item:item) =>
{
/// A structure representing a response from dispatching a local request.
///
/// This structure is a thin wrapper around [`Response`]. It implements no
/// methods of its own; all functionality is exposed via the [`Deref`]
/// implementation with a target of `Response`. In other words, when
/// invoking methods, a `LocalResponse` can be treated exactly as if it were
/// a (read-only) `Response`.
///
/// [`Deref`]: std::ops::Deref
$item
}}
macro_rules! impl_response {
($import:literal $(@$prefix:tt $suffix:tt)? $name:ident) =>
{
impl<'c> $name<'c> {
/// Consumes `self` reads its body into a string. If `self` doesn't have
/// a body, reading fails, or string conversion (for non-UTF-8 bodies)
/// fails, returns `None`.
macro_rules! getter_method {
($doc_prelude:literal, $desc:literal, $f:ident -> $r:ty) => (
getter_method!(@$doc_prelude, $f, $desc, $r,
concat!("let ", stringify!($f), " = response.", stringify!($f), "();"));
);
(@$doc_prelude:literal, $f:ident, $desc:expr, $r:ty, $use_it:expr) => (
/// Returns the
#[doc = $desc]
/// of `self`.
///
/// # Example
///
/// ```rust,ignore
#[doc = $import]
/// ```rust
#[doc = $doc_prelude]
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let response = client.get("/").body("Hello!").dispatch();
/// assert_eq!(response.into_string().unwrap(), "Hello!");
/// # })
/// # Client::_test(|_, _, response| {
/// let response: LocalResponse = response;
#[doc = $use_it]
/// # });
/// ```
#[inline(always)]
pub $($prefix)? fn into_string(self) -> Option<String> {
self._into_string() $(.$suffix)?
pub fn $f(&self) -> $r {
self._response().$f()
}
)
}
/// Consumes `self` and reads its body into a `Vec` of `u8` bytes. If
/// `self` doesn't have a body or reading fails returns `None`. Note
/// that `self`'s `body` is consumed after a call to this method.
///
/// # Example
///
/// ```rust,ignore
#[doc = $import]
///
/// # Client::_test(|client| {
/// let client: Client = client;
/// let response = client.get("/").body("Hello!").dispatch();
/// assert_eq!(response.into_bytes().unwrap(), "Hello!".as_bytes());
/// # })
/// ```
#[inline(always)]
pub $($prefix)? fn into_bytes(self) -> Option<Vec<u8>> {
self._into_bytes() $(.$suffix)?
}
macro_rules! pub_response_impl {
($doc_prelude:literal $($prefix:tt $suffix:tt)?) =>
{
getter_method!($doc_prelude, "HTTP status",
status -> crate::http::Status);
getter_method!($doc_prelude, "Content-Type, if a valid one is set,",
content_type -> Option<crate::http::ContentType>);
getter_method!($doc_prelude, "HTTP headers",
headers -> &crate::http::HeaderMap<'_>);
getter_method!($doc_prelude, "HTTP cookies as set in the `Set-Cookie` header",
cookies -> Vec<crate::http::Cookie<'_>>);
getter_method!($doc_prelude, "response body, if there is one,",
body -> Option<&crate::response::ResponseBody<'_>>);
/// Consumes `self` and reads the entirety of its body into a string. If
/// `self` doesn't have a body, reading fails, or string conversion (for
/// non-UTF-8 bodies) fails, returns `None`.
///
/// # Example
///
/// ```rust
#[doc = $doc_prelude]
///
/// # Client::_test(|_, _, response| {
/// let response: LocalResponse = response;
/// let string = response.into_string();
/// # });
/// ```
#[inline(always)]
pub $($prefix)? fn into_string(self) -> Option<String> {
self._into_string() $(.$suffix)?
}
impl std::fmt::Debug for LocalResponse<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._response().fmt(f)
}
/// Consumes `self` and reads the entirety of its body into a `Vec` of `u8`
/// bytes. If `self` doesn't have a body or reading fails, returns `None`.
///
/// # Example
///
/// ```rust
#[doc = $doc_prelude]
///
/// # Client::_test(|_, _, response| {
/// let response: LocalResponse = response;
/// let bytes = response.into_bytes();
/// # });
/// ```
#[inline(always)]
pub $($prefix)? fn into_bytes(self) -> Option<Vec<u8>> {
self._into_bytes() $(.$suffix)?
}
impl<'c> std::ops::Deref for LocalResponse<'c> {
type Target = Response<'c>;
fn deref(&self) -> &Response<'c> {
self._response()
}
#[cfg(test)]
#[allow(dead_code)]
fn _ensure_impls_exist() {
fn is_debug<T: std::fmt::Debug>() {}
is_debug::<Self>();
}
}}

View File

@ -100,8 +100,8 @@ impl<'r> Request<'r> {
uri: Origin<'s>
) -> Request<'r> {
let mut request = Request {
uri,
method: Atomic::new(method),
uri: uri,
headers: HeaderMap::new(),
remote: None,
state: RequestState {

View File

@ -61,6 +61,9 @@ enum PreLaunchOp {
#[repr(transparent)]
pub struct Cargo(Rocket);
// A token returned to force the execution of one method before another.
pub(crate) struct Token;
impl Rocket {
#[inline]
fn _mount(&mut self, base: Origin<'static>, routes: Vec<Route>) {
@ -133,29 +136,24 @@ impl Rocket {
// requiring only a single `await` at the call site. After completion,
// `self.pending` will be empty and `self.manifest` will reflect all pending
// changes.
//
// Note that this returns a boxed future, because `_attach()` calls this
// function again creating a cycle.
fn actualize_manifest(&mut self) -> BoxFuture<'_, ()> {
Box::pin(async move {
// Note: attach fairings may add more ops to the `manifest`! We
// process them as a stack to maintain proper ordering.
let mut manifest = mem::replace(&mut self.manifest, vec![]);
while !manifest.is_empty() {
trace_!("[MANIEST PROGRESS]: {:?}", manifest);
match manifest.remove(0) {
PreLaunchOp::Manage(_, callback) => callback(&mut self.managed_state),
PreLaunchOp::Mount(base, routes) => self._mount(base, routes),
PreLaunchOp::Register(catchers) => self._register(catchers),
PreLaunchOp::Attach(fairing) => {
let rocket = mem::replace(self, Rocket::dummy());
*self = rocket._attach(fairing).await;
self.manifest.append(&mut manifest);
manifest = mem::replace(&mut self.manifest, vec![]);
}
async fn actualize_manifest(&mut self) {
// Note: attach fairings may add more ops to the `manifest`! We
// process them as a stack to maintain proper ordering.
let mut manifest = mem::replace(&mut self.manifest, vec![]);
while !manifest.is_empty() {
trace_!("[MANIEST PROGRESS]: {:?}", manifest);
match manifest.remove(0) {
PreLaunchOp::Manage(_, callback) => callback(&mut self.managed_state),
PreLaunchOp::Mount(base, routes) => self._mount(base, routes),
PreLaunchOp::Register(catchers) => self._register(catchers),
PreLaunchOp::Attach(fairing) => {
let rocket = mem::replace(self, Rocket::dummy());
*self = rocket._attach(fairing).await;
self.manifest.append(&mut manifest);
manifest = mem::replace(&mut self.manifest, vec![]);
}
}
})
}
}
pub(crate) async fn into_cargo(mut self) -> Cargo {
@ -176,11 +174,11 @@ impl Rocket {
// converts Hyper types into Rocket types, then calls the `dispatch` function,
// which knows nothing about Hyper. Because responding depends on the
// `HyperResponse` type, this function does the actual response processing.
fn hyper_service_fn(
async fn hyper_service_fn(
rocket: Arc<Rocket>,
h_addr: std::net::SocketAddr,
hyp_req: hyper::Request<hyper::Body>,
) -> impl Future<Output = Result<hyper::Response<hyper::Body>, io::Error>> {
) -> Result<hyper::Response<hyper::Body>, io::Error> {
// This future must return a hyper::Response, but that's not easy
// because the response body might borrow from the request. Instead,
// we do the body writing in another future that will send us
@ -211,13 +209,12 @@ fn hyper_service_fn(
let data = Data::from_hyp(h_body).await;
// Dispatch the request to get a response, then write that response out.
let r = rocket.dispatch(&mut req, data).await;
let token = rocket.preprocess_request(&mut req, &data).await;
let r = rocket.dispatch(token, &mut req, data).await;
rocket.issue_response(r, tx).await;
});
async move {
rx.await.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}
rx.await.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}
impl Rocket {
@ -295,9 +292,14 @@ impl Rocket {
/// Preprocess the request for Rocket things. Currently, this means:
///
/// * Rewriting the method in the request if _method form field exists.
/// * Run the request fairings.
///
/// Keep this in-sync with derive_form when preprocessing form fields.
fn preprocess_request(&self, req: &mut Request<'_>, data: &Data) {
pub(crate) async fn preprocess_request(
&self,
req: &mut Request<'_>,
data: &Data
) -> Token {
// Check if this is a form and if the form contains the special _method
// field which we use to reinterpret the request's method.
let data_len = data.peek().len();
@ -312,10 +314,15 @@ impl Rocket {
.next();
if let Some(Ok(method)) = method {
req.set_method(method);
req._set_method(method);
}
}
}
// Run request fairings.
self.fairings.handle_request(req, data).await;
Token
}
/// Route the request and process the outcome to eventually get a response.
@ -384,8 +391,9 @@ impl Rocket {
// Dispatch the request to the handler.
let outcome = route.handler.handle(request, data).await;
// Check if the request processing completed (Some) or if the request needs
// to be forwarded. If it does, continue the loop (None) to try again.
// Check if the request processing completed (Some) or if the
// request needs to be forwarded. If it does, continue the loop
// (None) to try again.
info_!("{} {}", Paint::default("Outcome:").bold(), outcome);
match outcome {
o@Outcome::Success(_) | o@Outcome::Failure(_) => return o,
@ -399,42 +407,35 @@ impl Rocket {
}
#[inline]
pub(crate) fn dispatch<'s, 'r: 's>(
pub(crate) async fn dispatch<'s, 'r: 's>(
&'s self,
request: &'r mut Request<'s>,
_token: Token,
request: &'r Request<'s>,
data: Data
) -> impl Future<Output = Response<'r>> + 's {
async move {
info!("{}:", request);
) -> Response<'r> {
info!("{}:", request);
// Do a bit of preprocessing before routing.
self.preprocess_request(request, &data);
// Remember if the request is `HEAD` for later body stripping.
let was_head_request = request.method() == Method::Head;
// Run the request fairings.
self.fairings.handle_request(request, &data).await;
// Route the request and run the user's handlers.
let mut response = self.route_and_process(request, data).await;
// Remember if the request is `HEAD` for later body stripping.
let was_head_request = request.method() == Method::Head;
// Route the request and run the user's handlers.
let mut response = self.route_and_process(request, data).await;
// Add a default 'Server' header if it isn't already there.
// TODO: If removing Hyper, write out `Date` header too.
if !response.headers().contains("Server") {
response.set_header(Header::new("Server", "Rocket"));
}
// Run the response fairings.
self.fairings.handle_response(request, &mut response).await;
// Strip the body if this is a `HEAD` request.
if was_head_request {
response.strip_body();
}
response
// Add a default 'Server' header if it isn't already there.
// TODO: If removing Hyper, write out `Date` header too.
if !response.headers().contains("Server") {
response.set_header(Header::new("Server", "Rocket"));
}
// Run the response fairings.
self.fairings.handle_response(request, &mut response).await;
// Strip the body if this is a `HEAD` request.
if was_head_request {
response.strip_body();
}
response
}
// Finds the error catcher for the status `status` and executes it for the

View File

@ -1,9 +1,8 @@
use super::rocket;
use rocket::Response;
use rocket::local::asynchronous::Client;
use rocket::local::asynchronous::{Client, LocalResponse};
use rocket::http::{Status, Cookie, ContentType};
fn user_id_cookie(response: &Response<'_>) -> Option<Cookie<'static>> {
fn user_id_cookie(response: &LocalResponse<'_>) -> Option<Cookie<'static>> {
let cookie = response.headers()
.get("Set-Cookie")
.filter(|v| v.starts_with("user_id"))