mirror of https://github.com/rwf2/Rocket.git
Rework 'local' module. Add 'LocalResponse' methods.
This completes the 'local' blocking client implementation.
This commit is contained in:
parent
050a2c6461
commit
6482fa2fba
|
@ -1,21 +1,26 @@
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::local::asynchronous::LocalRequest;
|
use crate::local::asynchronous::{LocalRequest, LocalResponse};
|
||||||
use crate::rocket::{Rocket, Cargo};
|
use crate::rocket::{Rocket, Cargo};
|
||||||
use crate::http::{Method, private::CookieJar};
|
use crate::http::{Method, private::CookieJar};
|
||||||
use crate::error::LaunchError;
|
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
|
/// ## Multithreaded Syncronization Pitfalls
|
||||||
/// while tracking cookies can result in surprising, non-deterministic behavior.
|
///
|
||||||
/// This is because while cookie modifications are serialized, the exact
|
/// Unlike its [`blocking`](crate::local::blocking) variant, this `async` `Client`
|
||||||
/// ordering depends on when requests are dispatched. Specifically, when cookie
|
/// implements `Sync`. However, using it in a multithreaded environment while
|
||||||
/// tracking is enabled, all request dispatches are serialized, which in-turn
|
/// tracking cookies can result in surprising, non-deterministic behavior. This
|
||||||
/// serializes modifications to the internally tracked cookies.
|
/// 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
|
/// If possible, refrain from sharing a single instance of `Client` across
|
||||||
/// multiple threads. Instead, prefer to create a unique instance of `Client`
|
/// multiple threads. Instead, prefer to create a unique instance of `Client`
|
||||||
|
@ -26,7 +31,7 @@ struct_client! { [
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// The following snippet creates a `Client` from a `Rocket` instance and
|
/// 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
|
/// ```rust
|
||||||
/// use rocket::local::asynchronous::Client;
|
/// use rocket::local::asynchronous::Client;
|
||||||
|
@ -36,14 +41,13 @@ struct_client! { [
|
||||||
/// let client = Client::new(rocket).await.expect("valid rocket");
|
/// let client = Client::new(rocket).await.expect("valid rocket");
|
||||||
/// let response = client.post("/")
|
/// let response = client.post("/")
|
||||||
/// .body("Hello, world!")
|
/// .body("Hello, world!")
|
||||||
/// .dispatch().await;
|
/// .dispatch()
|
||||||
|
/// .await;
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
]
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
cargo: Cargo,
|
cargo: Cargo,
|
||||||
pub(crate) cookies: Option<RwLock<CookieJar>>,
|
pub(in super) cookies: Option<RwLock<CookieJar>>,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
|
@ -61,13 +65,17 @@ impl Client {
|
||||||
Ok(Client { cargo: rocket.into_cargo().await, cookies })
|
Ok(Client { cargo: rocket.into_cargo().await, cookies })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WARNING: This is unstable! Do not use this method outside of Rocket!
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// WARNING: This is unstable! Do not use this method outside of Rocket!
|
pub fn _test<T, F>(f: F) -> T
|
||||||
pub fn _test<T, F: FnOnce(Self) -> T + Send>(f: F) -> T {
|
where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send
|
||||||
|
{
|
||||||
crate::async_test(async {
|
crate::async_test(async {
|
||||||
let rocket = crate::ignite();
|
let rocket = crate::ignite();
|
||||||
let client = Client::new(rocket).await.expect("valid rocket");
|
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())
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::Client;
|
|
||||||
|
|
||||||
fn assert_sync_send<T: Sync + Send>() {}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_local_client_impl_send_sync() {
|
fn test_local_client_impl_send_sync() {
|
||||||
assert_sync_send::<Client>();
|
fn assert_sync_send<T: Sync + Send>() {}
|
||||||
|
assert_sync_send::<super::Client>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
//! Structures for asynchronous local dispatching of requests, primarily for
|
//! Asynchronous local dispatching of requests.
|
||||||
//! testing.
|
|
||||||
//!
|
//!
|
||||||
//! This module contains the `asynchronous` variant of the `local` API: it can
|
//! This module contains the `asynchronous` variant of the `local` API: it can
|
||||||
//! be used with `#[rocket::async_test]` or another asynchronous test harness.
|
//! 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 client;
|
||||||
mod request;
|
mod request;
|
||||||
|
|
|
@ -5,11 +5,14 @@ use crate::http::{Status, Method, uri::Origin, ext::IntoOwned};
|
||||||
|
|
||||||
use super::{Client, LocalResponse};
|
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
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// The following snippet uses the available builder methods to construct a
|
/// The following snippet uses the available builder methods to construct and
|
||||||
/// `POST` request to `/` with a JSON body:
|
/// dispatch a `POST` request to `/` with a JSON body:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::local::asynchronous::{Client, LocalRequest};
|
/// use rocket::local::asynchronous::{Client, LocalRequest};
|
||||||
|
@ -22,16 +25,16 @@ struct_request! { [
|
||||||
/// .remote("127.0.0.1:8000".parse().unwrap())
|
/// .remote("127.0.0.1:8000".parse().unwrap())
|
||||||
/// .cookie(Cookie::new("name", "value"))
|
/// .cookie(Cookie::new("name", "value"))
|
||||||
/// .body(r#"{ "value": 42 }"#);
|
/// .body(r#"{ "value": 42 }"#);
|
||||||
|
///
|
||||||
|
/// let response = req.dispatch().await;
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
]
|
|
||||||
pub struct LocalRequest<'c> {
|
pub struct LocalRequest<'c> {
|
||||||
client: &'c Client,
|
client: &'c Client,
|
||||||
request: Request<'c>,
|
request: Request<'c>,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
uri: Cow<'c, str>,
|
uri: Cow<'c, str>,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'c> LocalRequest<'c> {
|
impl<'c> LocalRequest<'c> {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
|
@ -65,33 +68,26 @@ impl<'c> LocalRequest<'c> {
|
||||||
&mut self.data
|
&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.
|
// 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> {
|
async fn _dispatch(mut self) -> LocalResponse<'c> {
|
||||||
// First, validate the URI, returning an error response (generated from
|
// First, validate the URI, returning an error response (generated from
|
||||||
// an error catcher) immediately if it's invalid.
|
// an error catcher) immediately if it's invalid.
|
||||||
|
let rocket = self.client.rocket();
|
||||||
if let Ok(uri) = Origin::parse(&self.uri) {
|
if let Ok(uri) = Origin::parse(&self.uri) {
|
||||||
self.request.set_uri(uri.into_owned());
|
self.request.set_uri(uri.into_owned());
|
||||||
} else {
|
} else {
|
||||||
error!("Malformed request URI: {}", self.uri);
|
error!("Malformed request URI: {}", self.uri);
|
||||||
let res = self.client.rocket()
|
return LocalResponse::new(self.request, move |req| {
|
||||||
.handle_error(Status::BadRequest, self.long_lived_request());
|
rocket.handle_error(Status::BadRequest, req)
|
||||||
|
}).await
|
||||||
return LocalResponse { _request: self.request, inner: res.await };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actually dispatch the request.
|
// Actually dispatch the request.
|
||||||
let response = self.client.rocket()
|
let data = Data::local(self.data);
|
||||||
.dispatch(self.long_lived_request(), Data::local(self.data))
|
let token = rocket.preprocess_request(&mut self.request, &data).await;
|
||||||
.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
|
// If the client is tracking cookies, updates the internal cookie jar
|
||||||
// with the changes reflected by `response`.
|
// 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> {
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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};
|
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 struct LocalResponse<'c> {
|
||||||
pub(in super) _request: Request<'c>,
|
_request: Box<Request<'c>>,
|
||||||
pub(in super) inner: Response<'c>,
|
response: Response<'c>,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'c> LocalResponse<'c> {
|
impl<'c> LocalResponse<'c> {
|
||||||
fn _response(&self) -> &Response<'c> {
|
pub(crate) fn new<F, O>(req: Request<'c>, f: F) -> impl Future<Output = LocalResponse<'c>>
|
||||||
&self.inner
|
where F: FnOnce(&'c Request<'c>) -> O + Send,
|
||||||
}
|
O: Future<Output = Response<'c>> + Send
|
||||||
|
{
|
||||||
pub(crate) async fn _into_string(mut self) -> Option<String> {
|
// `LocalResponse` is a self-referential structure. In particular,
|
||||||
self.inner.body_string().await
|
// `inner` can refer to `_request` and its contents. As such, we must
|
||||||
}
|
// 1) Ensure `Request` has a stable address.
|
||||||
|
//
|
||||||
pub(crate) async fn _into_bytes(mut self) -> Option<Vec<u8>> {
|
// This is done by `Box`ing the `Request`, using only the stable
|
||||||
self.inner.body_bytes().await
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use crate::error::LaunchError;
|
use crate::error::LaunchError;
|
||||||
use crate::http::Method;
|
use crate::http::Method;
|
||||||
use crate::local::{asynchronous, blocking::LocalRequest};
|
use crate::local::{asynchronous, blocking::{LocalRequest, LocalResponse}};
|
||||||
use crate::rocket::{Rocket, Cargo};
|
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
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// The following snippet creates a `Client` from a `Rocket` instance and
|
/// 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
|
/// ```rust
|
||||||
/// use rocket::local::blocking::Client;
|
/// use rocket::local::blocking::Client;
|
||||||
|
@ -21,12 +25,10 @@ struct_client! { [
|
||||||
/// .body("Hello, world!")
|
/// .body("Hello, world!")
|
||||||
/// .dispatch();
|
/// .dispatch();
|
||||||
/// ```
|
/// ```
|
||||||
]
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
pub(crate) inner: asynchronous::Client,
|
pub(crate) inner: asynchronous::Client,
|
||||||
runtime: RefCell<tokio::runtime::Runtime>,
|
runtime: RefCell<tokio::runtime::Runtime>,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
fn _new(rocket: Rocket, tracked: bool) -> Result<Client, LaunchError> {
|
fn _new(rocket: Rocket, tracked: bool) -> Result<Client, LaunchError> {
|
||||||
|
@ -41,12 +43,16 @@ impl Client {
|
||||||
Ok(Self { inner, runtime: RefCell::new(runtime) })
|
Ok(Self { inner, runtime: RefCell::new(runtime) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WARNING: This is unstable! Do not use this method outside of Rocket!
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// WARNING: This is unstable! Do not use this method outside of Rocket!
|
pub fn _test<T, F>(f: F) -> T
|
||||||
pub fn _test<T, F: FnOnce(Self) -> T + Send>(f: F) -> T {
|
where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send
|
||||||
|
{
|
||||||
let rocket = crate::ignite();
|
let rocket = crate::ignite();
|
||||||
let client = Client::new(rocket).expect("valid rocket");
|
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)]
|
#[inline(always)]
|
||||||
|
@ -71,23 +77,13 @@ impl Client {
|
||||||
{
|
{
|
||||||
LocalRequest::new(self, method, uri.into())
|
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)]
|
#[cfg(doctest)]
|
||||||
mod 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
|
/// ```compile_fail
|
||||||
/// use rocket::local::blocking::Client;
|
/// use rocket::local::blocking::Client;
|
||||||
///
|
///
|
||||||
|
@ -95,5 +91,5 @@ mod doctest {
|
||||||
/// not_sync::<Client>();
|
/// not_sync::<Client>();
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn test_not_sync_or_send() {}
|
fn test_not_sync() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
//! Structures for blocking local dispatching of requests, primarily for
|
//! Blocking local dispatching of requests.
|
||||||
//! testing.
|
|
||||||
//!
|
//!
|
||||||
//! This module contains the `blocking` variant of the `local` API: it can be
|
//! This module contains the `blocking` variant of the `local` API: it can be
|
||||||
//! used in Rust's synchronous `#[test]` harness. This is accomplished by
|
//! 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 client;
|
||||||
mod request;
|
mod request;
|
||||||
|
|
|
@ -4,11 +4,14 @@ use crate::{Request, http::Method, local::asynchronous};
|
||||||
|
|
||||||
use super::{Client, LocalResponse};
|
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
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// The following snippet uses the available builder methods to construct a
|
/// The following snippet uses the available builder methods to construct and
|
||||||
/// `POST` request to `/` with a JSON body:
|
/// dispatch a `POST` request to `/` with a JSON body:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::local::blocking::{Client, LocalRequest};
|
/// use rocket::local::blocking::{Client, LocalRequest};
|
||||||
|
@ -20,14 +23,14 @@ struct_request! { [
|
||||||
/// .remote("127.0.0.1:8000".parse().unwrap())
|
/// .remote("127.0.0.1:8000".parse().unwrap())
|
||||||
/// .cookie(Cookie::new("name", "value"))
|
/// .cookie(Cookie::new("name", "value"))
|
||||||
/// .body(r#"{ "value": 42 }"#);
|
/// .body(r#"{ "value": 42 }"#);
|
||||||
|
///
|
||||||
|
/// let response = req.dispatch();
|
||||||
/// ```
|
/// ```
|
||||||
]
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LocalRequest<'c> {
|
pub struct LocalRequest<'c> {
|
||||||
inner: asynchronous::LocalRequest<'c>,
|
inner: asynchronous::LocalRequest<'c>,
|
||||||
client: &'c Client,
|
client: &'c Client,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'c> LocalRequest<'c> {
|
impl<'c> LocalRequest<'c> {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -58,6 +61,13 @@ impl<'c> LocalRequest<'c> {
|
||||||
let inner = self.client.block_on(self.inner.dispatch());
|
let inner = self.client.block_on(self.inner.dispatch());
|
||||||
LocalResponse { inner, client: self.client }
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +1,63 @@
|
||||||
|
use std::io;
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
use crate::{Response, local::asynchronous};
|
use crate::{Response, local::asynchronous};
|
||||||
|
|
||||||
use super::Client;
|
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 struct LocalResponse<'c> {
|
||||||
pub(in super) inner: asynchronous::LocalResponse<'c>,
|
pub(in super) inner: asynchronous::LocalResponse<'c>,
|
||||||
pub(in super) client: &'c Client,
|
pub(in super) client: &'c Client,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'c> LocalResponse<'c> {
|
impl LocalResponse<'_> {
|
||||||
fn _response(&self) -> &Response<'c> {
|
fn _response(&self) -> &Response<'_> {
|
||||||
&*self.inner
|
&self.inner._response()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _into_string(self) -> Option<String> {
|
fn _into_string(self) -> Option<String> {
|
||||||
|
@ -21,6 +67,20 @@ impl<'c> LocalResponse<'c> {
|
||||||
fn _into_bytes(self) -> Option<Vec<u8>> {
|
fn _into_bytes(self) -> Option<Vec<u8>> {
|
||||||
self.client.block_on(self.inner._into_bytes())
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
macro_rules! req_method {
|
||||||
($import:literal, $NAME:literal, $f:ident, $method:expr) => (
|
($import:literal, $NAME:literal, $f:ident, $method:expr) => (
|
||||||
req_method!(@
|
req_method!(@
|
||||||
|
@ -62,8 +24,8 @@ macro_rules! req_method {
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
#[doc = $import]
|
#[doc = $import]
|
||||||
///
|
///
|
||||||
/// # Client::_test(|client| {
|
/// # Client::_test(|client, _, _| {
|
||||||
/// let client: Client = client;
|
/// let client: &Client = client;
|
||||||
#[doc = $use_it]
|
#[doc = $use_it]
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -76,137 +38,142 @@ macro_rules! req_method {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_client {
|
macro_rules! pub_client_impl {
|
||||||
($import:literal $(@$prefix:tt $suffix:tt)? $name:ident) =>
|
($import:literal $(@$prefix:tt $suffix:tt)?) =>
|
||||||
{
|
{
|
||||||
impl $name {
|
/// Construct a new `Client` from an instance of `Rocket` with cookie
|
||||||
/// Construct a new `Client` from an instance of `Rocket` with cookie
|
/// tracking.
|
||||||
/// tracking.
|
///
|
||||||
///
|
/// # Cookie Tracking
|
||||||
/// # Cookie Tracking
|
///
|
||||||
///
|
/// By default, a `Client` propagates cookie changes made by responses
|
||||||
/// By default, a `Client` propagates cookie changes made by responses
|
/// to previously dispatched requests. In other words, if a previously
|
||||||
/// to previously dispatched requests. In other words, if a previously
|
/// dispatched request resulted in a response that adds a cookie, any
|
||||||
/// dispatched request resulted in a response that adds a cookie, any
|
/// future requests will contain the new cookies. Similarly, cookies
|
||||||
/// future requests will contain the new cookies. Similarly, cookies
|
/// removed by a response won't be propagated further.
|
||||||
/// removed by a response won't be propagated further.
|
///
|
||||||
///
|
/// This is typically the desired mode of operation for a `Client` as it
|
||||||
/// This is typically the desired mode of operation for a `Client` as it
|
/// removes the burden of manually tracking cookies. Under some
|
||||||
/// removes the burden of manually tracking cookies. Under some
|
/// circumstances, however, disabling this tracking may be desired. The
|
||||||
/// circumstances, however, disabling this tracking may be desired. The
|
/// [`untracked()`](Client::untracked()) method creates a `Client` that
|
||||||
/// [`untracked()`](Client::untracked()) method creates a `Client` that
|
/// _will not_ track cookies.
|
||||||
/// _will not_ track cookies.
|
///
|
||||||
///
|
/// # Errors
|
||||||
/// # Errors
|
///
|
||||||
///
|
/// If launching the `Rocket` instance would fail, excepting network errors,
|
||||||
/// If launching the `Rocket` instance would fail, excepting network errors,
|
/// the `LaunchError` is returned.
|
||||||
/// the `LaunchError` is returned.
|
///
|
||||||
///
|
/// ```rust,no_run
|
||||||
/// ```rust,no_run
|
#[doc = $import]
|
||||||
#[doc = $import]
|
///
|
||||||
///
|
/// let rocket = rocket::ignite();
|
||||||
/// let rocket = rocket::ignite();
|
/// let client = Client::new(rocket);
|
||||||
/// let client = Client::new(rocket);
|
/// ```
|
||||||
/// ```
|
#[inline(always)]
|
||||||
#[inline(always)]
|
pub $($prefix)? fn new(rocket: Rocket) -> Result<Self, LaunchError> {
|
||||||
pub $($prefix)? fn new(rocket: Rocket) -> Result<Self, LaunchError> {
|
Self::_new(rocket, true) $(.$suffix)?
|
||||||
Self::_new(rocket, true) $(.$suffix)?
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct a new `Client` from an instance of `Rocket` _without_
|
/// Construct a new `Client` from an instance of `Rocket` _without_
|
||||||
/// cookie tracking.
|
/// cookie tracking.
|
||||||
///
|
///
|
||||||
/// # Cookie Tracking
|
/// # Cookie Tracking
|
||||||
///
|
///
|
||||||
/// Unlike the [`new()`](Client::new()) constructor, a `Client` returned
|
/// Unlike the [`new()`](Client::new()) constructor, a `Client` returned
|
||||||
/// from this method _does not_ automatically propagate cookie changes.
|
/// from this method _does not_ automatically propagate cookie changes.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// If launching the `Rocket` instance would fail, excepting network
|
/// If launching the `Rocket` instance would fail, excepting network
|
||||||
/// errors, the `LaunchError` is returned.
|
/// errors, the `LaunchError` is returned.
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
#[doc = $import]
|
#[doc = $import]
|
||||||
///
|
///
|
||||||
/// let rocket = rocket::ignite();
|
/// let rocket = rocket::ignite();
|
||||||
/// let client = Client::untracked(rocket);
|
/// let client = Client::untracked(rocket);
|
||||||
/// ```
|
/// ```
|
||||||
pub $($prefix)? fn untracked(rocket: Rocket) -> Result<Self, LaunchError> {
|
pub $($prefix)? fn untracked(rocket: Rocket) -> Result<Self, LaunchError> {
|
||||||
Self::_new(rocket, true) $(.$suffix)?
|
Self::_new(rocket, true) $(.$suffix)?
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the `Rocket` this client is creating requests
|
/// Returns a reference to the `Rocket` this client is creating requests
|
||||||
/// for.
|
/// for.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
#[doc = $import]
|
#[doc = $import]
|
||||||
///
|
///
|
||||||
/// # Client::_test(|client| {
|
/// # Client::_test(|client, _, _| {
|
||||||
/// let client: Client = client;
|
/// let client: &Client = client;
|
||||||
/// let rocket = client.rocket();
|
/// let rocket = client.rocket();
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn rocket(&self) -> &Rocket {
|
pub fn rocket(&self) -> &Rocket {
|
||||||
&*self._cargo()
|
&*self._cargo()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the `Cargo` of the `Rocket` this client is
|
/// Returns a reference to the `Cargo` of the `Rocket` this client is
|
||||||
/// creating requests for.
|
/// creating requests for.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
#[doc = $import]
|
#[doc = $import]
|
||||||
///
|
///
|
||||||
/// # Client::_test(|client| {
|
/// # Client::_test(|client, _, _| {
|
||||||
/// let client: Client = client;
|
/// let client: &Client = client;
|
||||||
/// let cargo = client.cargo();
|
/// let cargo = client.cargo();
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn cargo(&self) -> &Cargo {
|
pub fn cargo(&self) -> &Cargo {
|
||||||
self._cargo()
|
self._cargo()
|
||||||
}
|
}
|
||||||
|
|
||||||
req_method!($import, "GET", get, Method::Get);
|
req_method!($import, "GET", get, Method::Get);
|
||||||
req_method!($import, "PUT", put, Method::Put);
|
req_method!($import, "PUT", put, Method::Put);
|
||||||
req_method!($import, "POST", post, Method::Post);
|
req_method!($import, "POST", post, Method::Post);
|
||||||
req_method!($import, "DELETE", delete, Method::Delete);
|
req_method!($import, "DELETE", delete, Method::Delete);
|
||||||
req_method!($import, "OPTIONS", options, Method::Options);
|
req_method!($import, "OPTIONS", options, Method::Options);
|
||||||
req_method!($import, "HEAD", head, Method::Head);
|
req_method!($import, "HEAD", head, Method::Head);
|
||||||
req_method!($import, "PATCH", patch, Method::Patch);
|
req_method!($import, "PATCH", patch, Method::Patch);
|
||||||
|
|
||||||
/// Create a local `GET` request to the URI `uri`.
|
/// Create a local `GET` request to the URI `uri`.
|
||||||
///
|
///
|
||||||
/// When dispatched, the request will be served by the instance of
|
/// When dispatched, the request will be served by the instance of
|
||||||
/// Rocket within `self`. The request is not dispatched automatically.
|
/// Rocket within `self`. The request is not dispatched automatically.
|
||||||
/// To actually dispatch the request, call [`LocalRequest::dispatch()`]
|
/// To actually dispatch the request, call [`LocalRequest::dispatch()`]
|
||||||
/// on the returned request.
|
/// on the returned request.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
#[doc = $import]
|
#[doc = $import]
|
||||||
/// use rocket::http::Method;
|
/// use rocket::http::Method;
|
||||||
///
|
///
|
||||||
/// # Client::_test(|client| {
|
/// # Client::_test(|client, _, _| {
|
||||||
/// let client: Client = client;
|
/// let client: &Client = client;
|
||||||
/// client.req(Method::Get, "/hello");
|
/// client.req(Method::Get, "/hello");
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn req<'c, 'u: 'c, U>(
|
pub fn req<'c, 'u: 'c, U>(
|
||||||
&'c self,
|
&'c self,
|
||||||
method: Method,
|
method: Method,
|
||||||
uri: U
|
uri: U
|
||||||
) -> LocalRequest<'c>
|
) -> LocalRequest<'c>
|
||||||
where U: Into<Cow<'u, str>>
|
where U: Into<Cow<'u, str>>
|
||||||
{
|
{
|
||||||
self._req(method, uri)
|
self._req(method, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn _ensure_impls_exist() {
|
||||||
|
fn is_send<T: Send>() {}
|
||||||
|
is_send::<Self>();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -5,81 +5,33 @@
|
||||||
//! and integration test Rocket applications by crafting requests, dispatching
|
//! and integration test Rocket applications by crafting requests, dispatching
|
||||||
//! them, and verifying the response.
|
//! them, and verifying the response.
|
||||||
//!
|
//!
|
||||||
//! # Usage
|
//! # Async. vs. Blocking
|
||||||
//!
|
//!
|
||||||
//! This module contains two variants of the local API: [`asynchronous`] and
|
//! This module contains two variants, in its two submodules, of the same local
|
||||||
//! [`blocking`]. The primary difference between the two is in usage: the
|
//! API: [`asynchronous`], and [`blocking`]. As their names imply, the
|
||||||
//! `asynchronous` API requires an asynchronous test entry point such as
|
//! `asynchronous` API is `async`, returning a `Future` for operations that
|
||||||
//! `#[rocket::async_test]`, while the `blocking` API can be used with
|
//! would otherwise block, while `blocking` blocks for the same operations.
|
||||||
//! `#[test]`. Additionally, several methods in the `asynchronous` API are
|
|
||||||
//! `async` and must therefore be `await`ed.
|
|
||||||
//!
|
//!
|
||||||
//! Both APIs include a [`Client`] structure that is used to create
|
//! Unless your request handling requires concurrency to make progress, or
|
||||||
//! [`LocalRequest`] structures that can be dispatched against a given
|
//! you're making use of a `Client` in an environment that necessitates or would
|
||||||
//! [`Rocket`](crate::Rocket) instance. Usage is straightforward:
|
//! 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.
|
//! Both APIs include a `Client` structure that is used to create `LocalRequest`
|
||||||
//!
|
//! structures that can be dispatched against a given [`Rocket`](crate::Rocket)
|
||||||
//! ```rust
|
//! instance to yield a `LocalResponse` structure. The APIs are identical except
|
||||||
//! let rocket = rocket::ignite();
|
//! in that the `asynchronous` APIs return `Future`s for otherwise blocking
|
||||||
//! # let _ = rocket;
|
//! operations.
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! 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;
|
|
||||||
//! # });
|
|
||||||
//! ```
|
|
||||||
//!
|
//!
|
||||||
//! # Unit/Integration Testing
|
//! # Unit/Integration Testing
|
||||||
//!
|
//!
|
||||||
//! This module can be used to test a Rocket application by constructing
|
//! This module is primarily intended to be used to test a Rocket application by
|
||||||
//! requests via `Client` and validating the resulting response. As an example,
|
//! constructing requests via `Client`, dispatching them, and validating the
|
||||||
//! consider the following complete "Hello, world!" application, with testing.
|
//! resulting response. As a complete example, consider the following "Hello,
|
||||||
|
//! world!" application, with testing.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! #![feature(proc_macro_hygiene)]
|
//! #![feature(proc_macro_hygiene)]
|
||||||
|
@ -91,27 +43,143 @@
|
||||||
//! "Hello, world!"
|
//! "Hello, world!"
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! # fn main() { }
|
//! # /*
|
||||||
|
//! #[launch]
|
||||||
|
//! # */
|
||||||
|
//! fn rocket() -> rocket::Rocket {
|
||||||
|
//! rocket::ignite().mount("/", routes![hello])
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
//! #[cfg(test)]
|
//! #[cfg(test)]
|
||||||
//! mod test {
|
//! mod test {
|
||||||
//! use super::{rocket, hello};
|
//! // Using the preferred `blocking` API.
|
||||||
//! use rocket::local::asynchronous::Client;
|
//! #[test]
|
||||||
//!
|
//! fn test_hello_world_blocking() {
|
||||||
//! #[rocket::async_test]
|
|
||||||
//! fn test_hello_world() {
|
|
||||||
//! // Construct a client to use for dispatching requests.
|
//! // Construct a client to use for dispatching requests.
|
||||||
//! let rocket = rocket::ignite().mount("/", routes![hello]);
|
//! use rocket::local::blocking::Client;
|
||||||
//! let client = Client::new(rocket).expect("valid rocket instance");
|
//! let client = Client::new(super::rocket())
|
||||||
|
//! .expect("valid `Rocket`");
|
||||||
//!
|
//!
|
||||||
//! // Dispatch a request to 'GET /' and validate the response.
|
//! // Dispatch a request to 'GET /' and validate the response.
|
||||||
//! let mut response = client.get("/").dispatch().await;
|
//! let response = client.get("/").dispatch();
|
||||||
//! assert_eq!(response.into_string().await, Some("Hello, world!".into()));
|
//! 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
|
//! [`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 client;
|
||||||
#[macro_use] mod request;
|
#[macro_use] mod request;
|
||||||
|
|
|
@ -1,286 +1,246 @@
|
||||||
macro_rules! struct_request {
|
macro_rules! pub_request_impl {
|
||||||
([$(#[$attr:meta])*] $item:item) =>
|
($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`].
|
/// ```rust
|
||||||
/// Headers can be added via the [`header`] builder method and the
|
#[doc = $import]
|
||||||
/// [`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.
|
|
||||||
///
|
///
|
||||||
$(#[$attr])*
|
/// # Client::_test(|_, request, _| {
|
||||||
///
|
/// let request: LocalRequest = request;
|
||||||
/// # Dispatching
|
/// let inner: &rocket::Request = request.inner();
|
||||||
///
|
/// # });
|
||||||
/// A `LocalRequest` is dispatched by calling [`dispatch`].
|
/// ```
|
||||||
/// The `LocalRequest` is consumed and a response is returned.
|
#[inline(always)]
|
||||||
///
|
pub fn inner(&self) -> &Request<'c> {
|
||||||
/// Note that `LocalRequest` implements `Clone`. As such, if the
|
self._request()
|
||||||
/// 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
|
|
||||||
}}
|
|
||||||
|
|
||||||
macro_rules! impl_request {
|
/// Add a header to this request.
|
||||||
($import:literal $(@$prefix:tt $suffix:tt)? $name:ident) =>
|
///
|
||||||
{
|
/// Any type that implements `Into<Header>` can be used here. Among
|
||||||
impl<'c> $name<'c> {
|
/// others, this includes [`ContentType`] and [`Accept`].
|
||||||
/// Retrieves the inner `Request` as seen by Rocket.
|
///
|
||||||
///
|
/// [`ContentType`]: crate::http::ContentType
|
||||||
/// # Example
|
/// [`Accept`]: crate::http::Accept
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// # Examples
|
||||||
#[doc = $import]
|
///
|
||||||
/// use rocket::Request;
|
/// Add the Content-Type header:
|
||||||
///
|
///
|
||||||
/// # Client::_test(|client| {
|
/// ```rust
|
||||||
/// let client: Client = client;
|
#[doc = $import]
|
||||||
/// let req = client.get("/");
|
/// use rocket::http::Header;
|
||||||
/// let inner: &Request = req.inner();
|
/// use rocket::http::ContentType;
|
||||||
/// # });
|
///
|
||||||
/// ```
|
/// # Client::_test(|_, request, _| {
|
||||||
#[inline(always)]
|
/// let request: LocalRequest = request;
|
||||||
pub fn inner(&self) -> &Request<'c> {
|
/// let req = request
|
||||||
self._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.
|
/// Adds a header to this request without consuming `self`.
|
||||||
///
|
///
|
||||||
/// Any type that implements `Into<Header>` can be used here. Among
|
/// # Examples
|
||||||
/// others, this includes [`ContentType`] and [`Accept`].
|
///
|
||||||
///
|
/// Add the Content-Type header:
|
||||||
/// [`ContentType`]: crate::http::ContentType
|
///
|
||||||
/// [`Accept`]: crate::http::Accept
|
/// ```rust
|
||||||
///
|
#[doc = $import]
|
||||||
/// # Examples
|
/// use rocket::http::ContentType;
|
||||||
///
|
///
|
||||||
/// Add the Content-Type header:
|
/// # Client::_test(|_, mut request, _| {
|
||||||
///
|
/// let mut request: LocalRequest = request;
|
||||||
/// ```rust
|
/// request.add_header(ContentType::JSON);
|
||||||
#[doc = $import]
|
/// # });
|
||||||
/// use rocket::http::ContentType;
|
/// ```
|
||||||
///
|
#[inline]
|
||||||
/// # Client::_test(|client| {
|
pub fn add_header<H>(&mut self, header: H)
|
||||||
/// let client: Client = client;
|
where H: Into<crate::http::Header<'static>>
|
||||||
/// let req = client.get("/").header(ContentType::JSON);
|
{
|
||||||
/// # });
|
self._request_mut().add_header(header.into());
|
||||||
/// ```
|
}
|
||||||
#[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`.
|
/// Set the remote address of this request.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// Add the Content-Type header:
|
/// Set the remote address to "8.8.8.8:80":
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
#[doc = $import]
|
#[doc = $import]
|
||||||
/// use rocket::http::ContentType;
|
///
|
||||||
///
|
/// # Client::_test(|_, request, _| {
|
||||||
/// # Client::_test(|client| {
|
/// let request: LocalRequest = request;
|
||||||
/// let client: Client = client;
|
/// let address = "8.8.8.8:80".parse().unwrap();
|
||||||
/// let mut req = client.get("/");
|
/// let req = request.remote(address);
|
||||||
/// req.add_header(ContentType::JSON);
|
/// # });
|
||||||
/// # });
|
/// ```
|
||||||
/// ```
|
#[inline]
|
||||||
#[inline]
|
pub fn remote(mut self, address: std::net::SocketAddr) -> Self {
|
||||||
pub fn add_header<H>(&mut self, header: H)
|
self._request_mut().set_remote(address);
|
||||||
where H: Into<crate::http::Header<'static>>
|
self
|
||||||
{
|
}
|
||||||
self._request_mut().add_header(header.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the remote address of this request.
|
/// Add a cookie to this request.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// Set the remote address to "8.8.8.8:80":
|
/// Add `user_id` cookie:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
#[doc = $import]
|
#[doc = $import]
|
||||||
///
|
/// use rocket::http::Cookie;
|
||||||
/// # Client::_test(|client| {
|
///
|
||||||
/// let address = "8.8.8.8:80".parse().unwrap();
|
/// # Client::_test(|_, request, _| {
|
||||||
///
|
/// let request: LocalRequest = request;
|
||||||
/// let client: Client = client;
|
/// let req = request
|
||||||
/// let req = client.get("/").remote(address);
|
/// .cookie(Cookie::new("username", "sb"))
|
||||||
/// # });
|
/// .cookie(Cookie::new("user_id", "12"));
|
||||||
/// ```
|
/// # });
|
||||||
#[inline]
|
/// ```
|
||||||
pub fn remote(mut self, address: std::net::SocketAddr) -> Self {
|
#[inline]
|
||||||
self._request_mut().set_remote(address);
|
pub fn cookie(self, cookie: crate::http::Cookie<'_>) -> Self {
|
||||||
self
|
self._request().cookies().add_original(cookie.into_owned());
|
||||||
}
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a cookie to this request.
|
/// Add all of the cookies in `cookies` to this request.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// Add `user_id` cookie:
|
/// Add `user_id` cookie:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
#[doc = $import]
|
#[doc = $import]
|
||||||
/// use rocket::http::Cookie;
|
/// use rocket::http::Cookie;
|
||||||
///
|
///
|
||||||
/// # Client::_test(|client| {
|
/// # Client::_test(|_, request, _| {
|
||||||
/// let client: Client = client;
|
/// let request: LocalRequest = request;
|
||||||
/// let req = client.get("/")
|
/// let cookies = vec![Cookie::new("a", "b"), Cookie::new("c", "d")];
|
||||||
/// .cookie(Cookie::new("username", "sb"))
|
/// let req = request.cookies(cookies);
|
||||||
/// .cookie(Cookie::new("user_id", "12"));
|
/// # });
|
||||||
/// # });
|
/// ```
|
||||||
/// ```
|
#[inline]
|
||||||
#[inline]
|
pub fn cookies(self, cookies: Vec<crate::http::Cookie<'_>>) -> Self {
|
||||||
pub fn cookie(self, cookie: crate::http::Cookie<'_>) -> Self {
|
for cookie in cookies {
|
||||||
self._request().cookies().add_original(cookie.into_owned());
|
self._request().cookies().add_original(cookie.into_owned());
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add all of the cookies in `cookies` to this request.
|
self
|
||||||
///
|
|
||||||
/// # 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)?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for $name<'_> {
|
/// Add a [private cookie] to this request.
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
///
|
||||||
self._request().fmt(f)
|
/// 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>();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,74 +1,89 @@
|
||||||
macro_rules! struct_response {
|
macro_rules! getter_method {
|
||||||
($item:item) =>
|
($doc_prelude:literal, $desc:literal, $f:ident -> $r:ty) => (
|
||||||
{
|
getter_method!(@$doc_prelude, $f, $desc, $r,
|
||||||
/// A structure representing a response from dispatching a local request.
|
concat!("let ", stringify!($f), " = response.", stringify!($f), "();"));
|
||||||
///
|
);
|
||||||
/// This structure is a thin wrapper around [`Response`]. It implements no
|
(@$doc_prelude:literal, $f:ident, $desc:expr, $r:ty, $use_it:expr) => (
|
||||||
/// methods of its own; all functionality is exposed via the [`Deref`]
|
/// Returns the
|
||||||
/// implementation with a target of `Response`. In other words, when
|
#[doc = $desc]
|
||||||
/// invoking methods, a `LocalResponse` can be treated exactly as if it were
|
/// of `self`.
|
||||||
/// 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`.
|
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust
|
||||||
#[doc = $import]
|
#[doc = $doc_prelude]
|
||||||
///
|
///
|
||||||
/// # Client::_test(|client| {
|
/// # Client::_test(|_, _, response| {
|
||||||
/// let client: Client = client;
|
/// let response: LocalResponse = response;
|
||||||
/// let response = client.get("/").body("Hello!").dispatch();
|
#[doc = $use_it]
|
||||||
/// assert_eq!(response.into_string().unwrap(), "Hello!");
|
/// # });
|
||||||
/// # })
|
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub $($prefix)? fn into_string(self) -> Option<String> {
|
pub fn $f(&self) -> $r {
|
||||||
self._into_string() $(.$suffix)?
|
self._response().$f()
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Consumes `self` and reads its body into a `Vec` of `u8` bytes. If
|
macro_rules! pub_response_impl {
|
||||||
/// `self` doesn't have a body or reading fails returns `None`. Note
|
($doc_prelude:literal $($prefix:tt $suffix:tt)?) =>
|
||||||
/// that `self`'s `body` is consumed after a call to this method.
|
{
|
||||||
///
|
getter_method!($doc_prelude, "HTTP status",
|
||||||
/// # Example
|
status -> crate::http::Status);
|
||||||
///
|
|
||||||
/// ```rust,ignore
|
getter_method!($doc_prelude, "Content-Type, if a valid one is set,",
|
||||||
#[doc = $import]
|
content_type -> Option<crate::http::ContentType>);
|
||||||
///
|
|
||||||
/// # Client::_test(|client| {
|
getter_method!($doc_prelude, "HTTP headers",
|
||||||
/// let client: Client = client;
|
headers -> &crate::http::HeaderMap<'_>);
|
||||||
/// let response = client.get("/").body("Hello!").dispatch();
|
|
||||||
/// assert_eq!(response.into_bytes().unwrap(), "Hello!".as_bytes());
|
getter_method!($doc_prelude, "HTTP cookies as set in the `Set-Cookie` header",
|
||||||
/// # })
|
cookies -> Vec<crate::http::Cookie<'_>>);
|
||||||
/// ```
|
|
||||||
#[inline(always)]
|
getter_method!($doc_prelude, "response body, if there is one,",
|
||||||
pub $($prefix)? fn into_bytes(self) -> Option<Vec<u8>> {
|
body -> Option<&crate::response::ResponseBody<'_>>);
|
||||||
self._into_bytes() $(.$suffix)?
|
|
||||||
}
|
/// 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<'_> {
|
/// Consumes `self` and reads the entirety of its body into a `Vec` of `u8`
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
/// bytes. If `self` doesn't have a body or reading fails, returns `None`.
|
||||||
self._response().fmt(f)
|
///
|
||||||
}
|
/// # 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> {
|
#[cfg(test)]
|
||||||
type Target = Response<'c>;
|
#[allow(dead_code)]
|
||||||
|
fn _ensure_impls_exist() {
|
||||||
fn deref(&self) -> &Response<'c> {
|
fn is_debug<T: std::fmt::Debug>() {}
|
||||||
self._response()
|
is_debug::<Self>();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -100,8 +100,8 @@ impl<'r> Request<'r> {
|
||||||
uri: Origin<'s>
|
uri: Origin<'s>
|
||||||
) -> Request<'r> {
|
) -> Request<'r> {
|
||||||
let mut request = Request {
|
let mut request = Request {
|
||||||
|
uri,
|
||||||
method: Atomic::new(method),
|
method: Atomic::new(method),
|
||||||
uri: uri,
|
|
||||||
headers: HeaderMap::new(),
|
headers: HeaderMap::new(),
|
||||||
remote: None,
|
remote: None,
|
||||||
state: RequestState {
|
state: RequestState {
|
||||||
|
|
|
@ -61,6 +61,9 @@ enum PreLaunchOp {
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Cargo(Rocket);
|
pub struct Cargo(Rocket);
|
||||||
|
|
||||||
|
// A token returned to force the execution of one method before another.
|
||||||
|
pub(crate) struct Token;
|
||||||
|
|
||||||
impl Rocket {
|
impl Rocket {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn _mount(&mut self, base: Origin<'static>, routes: Vec<Route>) {
|
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,
|
// requiring only a single `await` at the call site. After completion,
|
||||||
// `self.pending` will be empty and `self.manifest` will reflect all pending
|
// `self.pending` will be empty and `self.manifest` will reflect all pending
|
||||||
// changes.
|
// changes.
|
||||||
//
|
async fn actualize_manifest(&mut self) {
|
||||||
// Note that this returns a boxed future, because `_attach()` calls this
|
// Note: attach fairings may add more ops to the `manifest`! We
|
||||||
// function again creating a cycle.
|
// process them as a stack to maintain proper ordering.
|
||||||
fn actualize_manifest(&mut self) -> BoxFuture<'_, ()> {
|
let mut manifest = mem::replace(&mut self.manifest, vec![]);
|
||||||
Box::pin(async move {
|
while !manifest.is_empty() {
|
||||||
// Note: attach fairings may add more ops to the `manifest`! We
|
trace_!("[MANIEST PROGRESS]: {:?}", manifest);
|
||||||
// process them as a stack to maintain proper ordering.
|
match manifest.remove(0) {
|
||||||
let mut manifest = mem::replace(&mut self.manifest, vec![]);
|
PreLaunchOp::Manage(_, callback) => callback(&mut self.managed_state),
|
||||||
while !manifest.is_empty() {
|
PreLaunchOp::Mount(base, routes) => self._mount(base, routes),
|
||||||
trace_!("[MANIEST PROGRESS]: {:?}", manifest);
|
PreLaunchOp::Register(catchers) => self._register(catchers),
|
||||||
match manifest.remove(0) {
|
PreLaunchOp::Attach(fairing) => {
|
||||||
PreLaunchOp::Manage(_, callback) => callback(&mut self.managed_state),
|
let rocket = mem::replace(self, Rocket::dummy());
|
||||||
PreLaunchOp::Mount(base, routes) => self._mount(base, routes),
|
*self = rocket._attach(fairing).await;
|
||||||
PreLaunchOp::Register(catchers) => self._register(catchers),
|
self.manifest.append(&mut manifest);
|
||||||
PreLaunchOp::Attach(fairing) => {
|
manifest = mem::replace(&mut self.manifest, vec![]);
|
||||||
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 {
|
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,
|
// converts Hyper types into Rocket types, then calls the `dispatch` function,
|
||||||
// which knows nothing about Hyper. Because responding depends on the
|
// which knows nothing about Hyper. Because responding depends on the
|
||||||
// `HyperResponse` type, this function does the actual response processing.
|
// `HyperResponse` type, this function does the actual response processing.
|
||||||
fn hyper_service_fn(
|
async fn hyper_service_fn(
|
||||||
rocket: Arc<Rocket>,
|
rocket: Arc<Rocket>,
|
||||||
h_addr: std::net::SocketAddr,
|
h_addr: std::net::SocketAddr,
|
||||||
hyp_req: hyper::Request<hyper::Body>,
|
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
|
// This future must return a hyper::Response, but that's not easy
|
||||||
// because the response body might borrow from the request. Instead,
|
// because the response body might borrow from the request. Instead,
|
||||||
// we do the body writing in another future that will send us
|
// 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;
|
let data = Data::from_hyp(h_body).await;
|
||||||
|
|
||||||
// Dispatch the request to get a response, then write that response out.
|
// 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;
|
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 {
|
impl Rocket {
|
||||||
|
@ -295,9 +292,14 @@ impl Rocket {
|
||||||
/// Preprocess the request for Rocket things. Currently, this means:
|
/// Preprocess the request for Rocket things. Currently, this means:
|
||||||
///
|
///
|
||||||
/// * Rewriting the method in the request if _method form field exists.
|
/// * 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.
|
/// 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
|
// Check if this is a form and if the form contains the special _method
|
||||||
// field which we use to reinterpret the request's method.
|
// field which we use to reinterpret the request's method.
|
||||||
let data_len = data.peek().len();
|
let data_len = data.peek().len();
|
||||||
|
@ -312,10 +314,15 @@ impl Rocket {
|
||||||
.next();
|
.next();
|
||||||
|
|
||||||
if let Some(Ok(method)) = method {
|
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.
|
/// Route the request and process the outcome to eventually get a response.
|
||||||
|
@ -384,8 +391,9 @@ impl Rocket {
|
||||||
// Dispatch the request to the handler.
|
// Dispatch the request to the handler.
|
||||||
let outcome = route.handler.handle(request, data).await;
|
let outcome = route.handler.handle(request, data).await;
|
||||||
|
|
||||||
// Check if the request processing completed (Some) or if the request needs
|
// Check if the request processing completed (Some) or if the
|
||||||
// to be forwarded. If it does, continue the loop (None) to try again.
|
// request needs to be forwarded. If it does, continue the loop
|
||||||
|
// (None) to try again.
|
||||||
info_!("{} {}", Paint::default("Outcome:").bold(), outcome);
|
info_!("{} {}", Paint::default("Outcome:").bold(), outcome);
|
||||||
match outcome {
|
match outcome {
|
||||||
o@Outcome::Success(_) | o@Outcome::Failure(_) => return o,
|
o@Outcome::Success(_) | o@Outcome::Failure(_) => return o,
|
||||||
|
@ -399,42 +407,35 @@ impl Rocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn dispatch<'s, 'r: 's>(
|
pub(crate) async fn dispatch<'s, 'r: 's>(
|
||||||
&'s self,
|
&'s self,
|
||||||
request: &'r mut Request<'s>,
|
_token: Token,
|
||||||
|
request: &'r Request<'s>,
|
||||||
data: Data
|
data: Data
|
||||||
) -> impl Future<Output = Response<'r>> + 's {
|
) -> Response<'r> {
|
||||||
async move {
|
info!("{}:", request);
|
||||||
info!("{}:", request);
|
|
||||||
|
|
||||||
// Do a bit of preprocessing before routing.
|
// Remember if the request is `HEAD` for later body stripping.
|
||||||
self.preprocess_request(request, &data);
|
let was_head_request = request.method() == Method::Head;
|
||||||
|
|
||||||
// Run the request fairings.
|
// Route the request and run the user's handlers.
|
||||||
self.fairings.handle_request(request, &data).await;
|
let mut response = self.route_and_process(request, data).await;
|
||||||
|
|
||||||
// Remember if the request is `HEAD` for later body stripping.
|
// Add a default 'Server' header if it isn't already there.
|
||||||
let was_head_request = request.method() == Method::Head;
|
// TODO: If removing Hyper, write out `Date` header too.
|
||||||
|
if !response.headers().contains("Server") {
|
||||||
// Route the request and run the user's handlers.
|
response.set_header(Header::new("Server", "Rocket"));
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// Finds the error catcher for the status `status` and executes it for the
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use super::rocket;
|
use super::rocket;
|
||||||
use rocket::Response;
|
use rocket::local::asynchronous::{Client, LocalResponse};
|
||||||
use rocket::local::asynchronous::Client;
|
|
||||||
use rocket::http::{Status, Cookie, ContentType};
|
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()
|
let cookie = response.headers()
|
||||||
.get("Set-Cookie")
|
.get("Set-Cookie")
|
||||||
.filter(|v| v.starts_with("user_id"))
|
.filter(|v| v.starts_with("user_id"))
|
||||||
|
|
Loading…
Reference in New Issue