Make 'Fairing::on_response' and 'Responder::respond_to' async.

This is required to be able to do anything useful with the body in the
outgoing response. Request fairings do not appear to need to be async
as everything on Data that returns a future moves self and on_request only
gets &Data, but the same change in this commit should work for on_request
if desired.
This commit is contained in:
Jeb Rosen 2019-07-24 08:21:52 -07:00 committed by Sergio Benitez
parent 5d439bafc0
commit 7c34a3a93e
22 changed files with 257 additions and 190 deletions

View File

@ -75,7 +75,7 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
// Emit this to force a type signature check.
let #catcher: #fn_sig = #user_catcher_fn_name;
let ___responder = #catcher(#inputs);
::rocket::response::Responder::respond_to(___responder, #req)?
::rocket::response::Responder::respond_to(___responder, #req).await?
});
// Generate the catcher, keeping the user's input around.

View File

@ -381,7 +381,7 @@ fn generate_respond_expr(route: &Route) -> TokenStream2 {
quote_spanned! { ret_span =>
#responder_stmt
#handler::Outcome::from(#req, ___responder)
#handler::Outcome::from(#req, ___responder).await
}
}

View File

@ -32,8 +32,8 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
.function(|_, inner| quote! {
fn respond_to(
self,
__req: &::rocket::Request
) -> ::rocket::response::Result<'__r> {
__req: &'__r ::rocket::Request
) -> ::rocket::response::ResultFuture<'__r> {
#inner
}
})
@ -51,7 +51,7 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
quote_spanned! { f.span().into() =>
let mut __res = <#ty as ::rocket::response::Responder>::respond_to(
#accessor, __req
)?;
).await?;
}
}).expect("have at least one field");
@ -71,11 +71,13 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
});
Ok(quote! {
#responder
#(#headers)*
#content_type
#status
#_Ok(__res)
Box::pin(async move {
#responder
#(#headers)*
#content_type
#status
Ok(__res)
})
})
})
.to_tokens()

View File

@ -157,7 +157,7 @@ macro_rules! default_catchers {
(async move {
status::Custom(Status::from_code($code).unwrap(),
content::Html(error_page_template!($code, $name, $description))
).respond_to(req)
).respond_to(req).await
}).boxed()
}

View File

@ -1,3 +1,5 @@
use std::future::Future;
use std::pin::Pin;
use std::sync::Mutex;
use crate::{Rocket, Request, Response, Data};
@ -49,7 +51,7 @@ enum AdHocKind {
Request(Box<dyn Fn(&mut Request<'_>, &Data) + Send + Sync + 'static>),
/// An ad-hoc **response** fairing. Called when a response is ready to be
/// sent to a client.
Response(Box<dyn Fn(&Request<'_>, &mut Response<'_>) + Send + Sync + 'static>),
Response(Box<dyn for<'a, 'r> Fn(&'a Request<'r>, &'a mut Response<'r>) -> Pin<Box<dyn Future<Output=()> + Send + 'a>> + Send + Sync + 'static>),
}
impl AdHoc {
@ -124,7 +126,7 @@ impl AdHoc {
/// });
/// ```
pub fn on_response<F>(name: &'static str, f: F) -> AdHoc
where F: Fn(&Request<'_>, &mut Response<'_>) + Send + Sync + 'static
where F: for<'a, 'r> Fn(&'a Request<'r>, &'a mut Response<'r>) -> Pin<Box<dyn Future<Output=()> + Send + 'a>> + Send + Sync + 'static
{
AdHoc { name, kind: AdHocKind::Response(Box::new(f)) }
}
@ -166,9 +168,11 @@ impl Fairing for AdHoc {
}
}
fn on_response(&self, request: &Request<'_>, response: &mut Response<'_>) {
fn on_response<'a, 'r>(&'a self, request: &'a Request<'r>, response: &'a mut Response<'r>) -> Pin<Box<dyn Future<Output=()> + Send + 'a>> {
if let AdHocKind::Response(ref callback) = self.kind {
callback(request, response)
} else {
Box::pin(async { })
}
}
}

View File

@ -1,3 +1,6 @@
use std::pin::Pin;
use std::future::Future;
use crate::{Rocket, Request, Response, Data};
use crate::fairing::{Fairing, Kind};
use crate::logger::PaintExt;
@ -66,10 +69,12 @@ impl Fairings {
}
#[inline(always)]
pub fn handle_response(&self, request: &Request<'_>, response: &mut Response<'_>) {
for &i in &self.response {
self.all_fairings[i].on_response(request, response);
}
pub fn handle_response<'a, 'r>(&'a self, request: &'a Request<'r>, response: &'a mut Response<'r>) -> Pin<Box<dyn Future<Output=()> + Send + 'a>> {
Box::pin(async move {
for &i in &self.response {
self.all_fairings[i].on_response(request, response).await;
}
})
}
pub fn failures(&self) -> Option<&[&'static str]> {

View File

@ -47,6 +47,9 @@
//! of other `Fairings` are not jeopardized. For instance, unless it is made
//! abundantly clear, a fairing should not rewrite every request.
use std::pin::Pin;
use std::future::Future;
use crate::{Rocket, Request, Response, Data};
mod fairings;
@ -408,7 +411,9 @@ pub trait Fairing: Send + Sync + 'static {
///
/// The default implementation of this method does nothing.
#[allow(unused_variables)]
fn on_response(&self, request: &Request<'_>, response: &mut Response<'_>) {}
fn on_response<'a, 'r>(&'a self, request: &'a Request<'r>, response: &'a mut Response<'r>) -> Pin<Box<dyn Future<Output=()> + Send + 'a>> {
Box::pin(async { })
}
}
impl<T: Fairing> Fairing for std::sync::Arc<T> {
@ -433,7 +438,7 @@ impl<T: Fairing> Fairing for std::sync::Arc<T> {
}
#[inline]
fn on_response(&self, request: &Request<'_>, response: &mut Response<'_>) {
fn on_response<'a, 'r>(&'a self, request: &'a Request<'r>, response: &'a mut Response<'r>) -> Pin<Box<dyn Future<Output=()> + Send + 'a>> {
(self as &T).on_response(request, response)
}
}

View File

@ -206,11 +206,13 @@ impl<'r> Outcome<'r> {
/// }
/// ```
#[inline]
pub fn from<T: Responder<'r>>(req: &Request<'_>, responder: T) -> Outcome<'r> {
match responder.respond_to(req) {
Ok(response) => outcome::Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status)
}
pub fn from<T: Responder<'r> + Send + 'r>(req: &'r Request<'_>, responder: T) -> HandlerFuture<'r> {
Box::pin(async move {
match responder.respond_to(req).await {
Ok(response) => outcome::Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status)
}
})
}
/// Return the `Outcome` of response to `req` from `responder`.
@ -223,21 +225,23 @@ impl<'r> Outcome<'r> {
///
/// ```rust
/// use rocket::{Request, Data};
/// use rocket::handler::Outcome;
/// use rocket::handler::{Outcome, HandlerFuture};
///
/// fn str_responder(req: &Request, _: Data) -> Outcome<'static> {
/// fn str_responder<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> {
/// Outcome::from(req, "Hello, world!")
/// }
/// ```
#[inline]
pub fn try_from<T, E>(req: &Request<'_>, result: Result<T, E>) -> Outcome<'r>
where T: Responder<'r>, E: std::fmt::Debug
pub fn try_from<T, E>(req: &'r Request<'_>, result: Result<T, E>) -> HandlerFuture<'r>
where T: Responder<'r> + Send + 'r, E: std::fmt::Debug + Send + 'r
{
let responder = result.map_err(crate::response::Debug);
match responder.respond_to(req) {
Ok(response) => outcome::Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status)
}
Box::pin(async move {
let responder = result.map_err(crate::response::Debug);
match responder.respond_to(req).await {
Ok(response) => outcome::Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status)
}
})
}
/// Return the `Outcome` of response to `req` from `responder`.
@ -257,13 +261,15 @@ impl<'r> Outcome<'r> {
/// }
/// ```
#[inline]
pub fn from_or_forward<T>(req: &Request<'_>, data: Data, responder: T) -> Outcome<'r>
where T: Responder<'r>
pub fn from_or_forward<T: 'r>(req: &'r Request<'_>, data: Data, responder: T) -> HandlerFuture<'r>
where T: Responder<'r> + Send
{
match responder.respond_to(req) {
Ok(response) => outcome::Outcome::Success(response),
Err(_) => outcome::Outcome::Forward(data)
}
Box::pin(async move {
match responder.respond_to(req).await {
Ok(response) => outcome::Outcome::Success(response),
Err(_) => outcome::Outcome::Forward(data)
}
})
}
/// Return an `Outcome` of `Failure` with the status code `code`. This is

View File

@ -23,7 +23,7 @@
//! ```
use crate::request::Request;
use crate::response::{Response, Responder};
use crate::response::{Response, Responder, ResultFuture};
use crate::http::{Status, ContentType};
/// Sets the Content-Type of a `Responder` to a chosen value.
@ -46,13 +46,15 @@ pub struct Content<R>(pub ContentType, pub R);
/// Overrides the Content-Type of the response to the wrapped `ContentType` then
/// delegates the remainder of the response to the wrapped responder.
impl<'r, R: Responder<'r>> Responder<'r> for Content<R> {
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Content<R> {
#[inline(always)]
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> {
Response::build()
.merge(self.1.respond_to(req)?)
.header(self.0)
.ok()
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
Response::build()
.merge(self.1.respond_to(req).await?)
.header(self.0)
.ok()
})
}
}
@ -71,8 +73,8 @@ macro_rules! ctrs {
/// Sets the Content-Type of the response then delegates the
/// remainder of the response to the wrapped responder.
impl<'r, R: Responder<'r>> Responder<'r> for $name<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> {
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for $name<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Content(ContentType::$ct, self.0).respond_to(req)
}
}

View File

@ -61,10 +61,12 @@ impl<E> From<E> for Debug<E> {
}
}
impl<'r, E: std::fmt::Debug> Responder<'r> for Debug<E> {
fn respond_to(self, _: &Request<'_>) -> response::Result<'r> {
warn_!("Debug: {:?}", Paint::default(self.0));
warn_!("Debug always responds with {}.", Status::InternalServerError);
Response::build().status(Status::InternalServerError).ok()
impl<'r, E: std::fmt::Debug + Send + 'r> Responder<'r> for Debug<E> {
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
warn_!("Debug: {:?}", Paint::default(self.0));
warn_!("Debug always responds with {}.", Status::InternalServerError);
Response::build().status(Status::InternalServerError).ok()
})
}
}

View File

@ -3,7 +3,7 @@ use std::convert::AsRef;
use time::Duration;
use crate::outcome::IntoOutcome;
use crate::response::{Response, Responder};
use crate::response::{Response, Responder, ResultFuture};
use crate::request::{self, Request, FromRequest};
use crate::http::{Status, Cookie};
use std::sync::atomic::{AtomicBool, Ordering};
@ -198,8 +198,8 @@ impl<'r, R: Responder<'r>> Flash<R> {
/// response. In other words, simply sets a cookie and delegates the rest of the
/// response handling to the wrapped responder. As a result, the `Outcome` of
/// the response is the `Outcome` of the wrapped `Responder`.
impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> {
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Flash<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
trace_!("Flash: setting message: {}:{}", self.name, self.message);
req.cookies().add(self.cookie());
self.inner.respond_to(req)

View File

@ -48,3 +48,5 @@ pub use self::debug::Debug;
/// Type alias for the `Result` of a `Responder::respond` call.
pub type Result<'r> = std::result::Result<self::Response<'r>, crate::http::Status>;
/// Type alias for the `Result` of a `Responder::respond` call.
pub type ResultFuture<'r> = std::pin::Pin<Box<dyn std::future::Future<Output=Result<'r>> + Send + 'r>>;

View File

@ -78,16 +78,18 @@ impl NamedFile {
/// recognized. See [`ContentType::from_extension()`] for more information. If
/// you would like to stream a file with a different Content-Type than that
/// implied by its extension, use a [`File`] directly.
impl Responder<'_> for NamedFile {
fn respond_to(self, req: &Request<'_>) -> response::Result<'static> {
let mut response = self.1.respond_to(req)?;
if let Some(ext) = self.0.extension() {
if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) {
response.set_header(ct);
impl<'r> Responder<'r> for NamedFile {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
let mut response = self.1.respond_to(req).await?;
if let Some(ext) = self.0.extension() {
if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) {
response.set_header(ct);
}
}
}
Ok(response)
Ok(response)
})
}
}

View File

@ -1,7 +1,7 @@
use std::convert::TryInto;
use crate::request::Request;
use crate::response::{Response, Responder};
use crate::response::{Response, Responder, ResultFuture};
use crate::http::uri::Uri;
use crate::http::Status;
@ -147,16 +147,18 @@ impl Redirect {
/// the `Location` header field. The body of the response is empty. If the URI
/// value used to create the `Responder` is an invalid URI, an error of
/// `Status::InternalServerError` is returned.
impl Responder<'_> for Redirect {
fn respond_to(self, _: &Request<'_>) -> Result<Response<'static>, Status> {
if let Some(uri) = self.1 {
Response::build()
.status(self.0)
.raw_header("Location", uri.to_string())
.ok()
} else {
error!("Invalid URI used for redirect.");
Err(Status::InternalServerError)
}
impl<'r> Responder<'r> for Redirect {
fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async {
if let Some(uri) = self.1 {
Response::build()
.status(self.0)
.raw_header("Location", uri.to_string())
.ok()
} else {
error!("Invalid URI used for redirect.");
Err(Status::InternalServerError)
}
})
}
}

View File

@ -193,91 +193,110 @@ pub trait Responder<'r> {
/// returned, the error catcher for the given status is retrieved and called
/// to generate a final error response, which is then written out to the
/// client.
fn respond_to(self, request: &Request<'_>) -> response::Result<'r>;
fn respond_to(self, request: &'r Request<'_>) -> response::ResultFuture<'r>;
}
/// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`.
impl<'r> Responder<'r> for &'r str {
fn respond_to(self, _: &Request<'_>) -> response::Result<'r> {
Response::build()
.header(ContentType::Plain)
.sized_body(Cursor::new(self))
.ok()
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
Response::build()
.header(ContentType::Plain)
.sized_body(Cursor::new(self))
.ok()
})
}
}
/// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`.
impl Responder<'_> for String {
fn respond_to(self, _: &Request<'_>) -> response::Result<'static> {
Response::build()
.header(ContentType::Plain)
.sized_body(Cursor::new(self))
.ok()
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'static> {
Box::pin(async move {
Response::build()
.header(ContentType::Plain)
.sized_body(Cursor::new(self))
.ok()
})
}
}
/// Returns a response with Content-Type `application/octet-stream` and a
/// fixed-size body containing the data in `self`. Always returns `Ok`.
impl<'r> Responder<'r> for &'r [u8] {
fn respond_to(self, _: &Request<'_>) -> response::Result<'r> {
Response::build()
.header(ContentType::Binary)
.sized_body(Cursor::new(self))
.ok()
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
Response::build()
.header(ContentType::Binary)
.sized_body(Cursor::new(self))
.ok()
})
}
}
/// Returns a response with Content-Type `application/octet-stream` and a
/// fixed-size body containing the data in `self`. Always returns `Ok`.
impl Responder<'_> for Vec<u8> {
fn respond_to(self, _: &Request<'_>) -> response::Result<'static> {
Response::build()
.header(ContentType::Binary)
.sized_body(Cursor::new(self))
.ok()
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'static> {
Box::pin(async move {
Response::build()
.header(ContentType::Binary)
.sized_body(Cursor::new(self))
.ok()
})
}
}
/// Returns a response with a sized body for the file. Always returns `Ok`.
impl Responder<'_> for File {
fn respond_to(self, _: &Request<'_>) -> response::Result<'static> {
let metadata = self.metadata();
let stream = BufReader::new(tokio::fs::File::from_std(self)).compat();
match metadata {
Ok(md) => Response::build().raw_body(Body::Sized(stream, md.len())).ok(),
Err(_) => Response::build().streamed_body(stream).ok()
}
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'static> {
Box::pin(async move {
let metadata = self.metadata();
let stream = BufReader::new(tokio::fs::File::from_std(self)).compat();
match metadata {
Ok(md) => Response::build().raw_body(Body::Sized(stream, md.len())).ok(),
Err(_) => Response::build().streamed_body(stream).ok()
}
})
}
}
/// Returns an empty, default `Response`. Always returns `Ok`.
impl Responder<'_> for () {
fn respond_to(self, _: &Request<'_>) -> response::Result<'static> {
Ok(Response::new())
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'static> {
Box::pin(async move {
Ok(Response::new())
})
}
}
/// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints
/// a warning message and returns an `Err` of `Status::NotFound`.
impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
fn respond_to(self, req: &Request<'_>) -> response::Result<'r> {
self.map_or_else(|| {
warn_!("Response was `None`.");
Err(Status::NotFound)
}, |r| r.respond_to(req))
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Option<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
match self {
Some(r) => r.respond_to(req).await,
None => {
warn_!("Response was `None`.");
Err(Status::NotFound)
},
}
})
}
}
/// Responds with the wrapped `Responder` in `self`, whether it is `Ok` or
/// `Err`.
impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result<R, E> {
fn respond_to(self, req: &Request<'_>) -> response::Result<'r> {
match self {
Ok(responder) => responder.respond_to(req),
Err(responder) => responder.respond_to(req),
}
impl<'r, R: Responder<'r> + Send + 'r, E: Responder<'r> + Send + 'r> Responder<'r> for Result<R, E> {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
match self {
Ok(responder) => responder.respond_to(req).await,
Err(responder) => responder.respond_to(req).await,
}
})
}
}
@ -295,21 +314,23 @@ impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result<R, E> {
/// `100` responds with any empty body and the given status code, and all other
/// status code emit an error message and forward to the `500` (internal server
/// error) catcher.
impl Responder<'_> for Status {
fn respond_to(self, _: &Request<'_>) -> response::Result<'static> {
match self.class() {
StatusClass::ClientError | StatusClass::ServerError => Err(self),
StatusClass::Success if self.code < 206 => {
Response::build().status(self).ok()
impl<'r> Responder<'r> for Status {
fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
match self.class() {
StatusClass::ClientError | StatusClass::ServerError => Err(self),
StatusClass::Success if self.code < 206 => {
Response::build().status(self).ok()
}
StatusClass::Informational if self.code == 100 => {
Response::build().status(self).ok()
}
_ => {
error_!("Invalid status used as responder: {}.", self);
warn_!("Fowarding to 500 (Internal Server Error) catcher.");
Err(Status::InternalServerError)
}
}
StatusClass::Informational if self.code == 100 => {
Response::build().status(self).ok()
}
_ => {
error_!("Invalid status used as responder: {}.", self);
warn_!("Fowarding to 500 (Internal Server Error) catcher.");
Err(Status::InternalServerError)
}
}
})
}
}

View File

@ -5,7 +5,7 @@ use std::pin::Pin;
use futures::future::{Future, FutureExt};
use futures::io::{AsyncRead, AsyncReadExt};
use crate::response::Responder;
use crate::response::{Responder, ResultFuture};
use crate::http::{Header, HeaderMap, Status, ContentType, Cookie};
use crate::ext::AsyncReadExt as _;
@ -1216,7 +1216,9 @@ use crate::request::Request;
impl<'r> Responder<'r> for Response<'r> {
/// This is the identity implementation. It simply returns `Ok(self)`.
fn respond_to(self, _: &Request<'_>) -> Result<Response<'r>, Status> {
Ok(self)
fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async {
Ok(self)
})
}
}

View File

@ -12,7 +12,7 @@ use std::collections::hash_map::DefaultHasher;
use std::borrow::Cow;
use crate::request::Request;
use crate::response::{Responder, Response};
use crate::response::{Responder, Response, ResultFuture};
use crate::http::Status;
/// Sets the status of the response to 201 (Created).
@ -154,20 +154,22 @@ impl<'r, R> Created<R> {
/// the response with the `Responder`, the `ETag` header is set conditionally if
/// a hashable `Responder` is provided via [`Created::tagged_body()`]. The `ETag`
/// header is set to a hash value of the responder.
impl<'r, R: Responder<'r>> Responder<'r> for Created<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> {
let mut response = Response::build();
if let Some(responder) = self.1 {
response.merge(responder.respond_to(req)?);
}
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Created<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
let mut response = Response::build();
if let Some(responder) = self.1 {
response.merge(responder.respond_to(req).await?);
}
if let Some(hash) = self.2 {
response.raw_header("ETag", format!(r#""{}""#, hash));
}
if let Some(hash) = self.2 {
response.raw_header("ETag", format!(r#""{}""#, hash));
}
response.status(Status::Created)
.raw_header("Location", self.0)
.ok()
response.status(Status::Created)
.raw_header("Location", self.0)
.ok()
})
}
}
@ -200,14 +202,16 @@ pub struct Accepted<R>(pub Option<R>);
/// Sets the status code of the response to 202 Accepted. If the responder is
/// `Some`, it is used to finalize the response.
impl<'r, R: Responder<'r>> Responder<'r> for Accepted<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond_to(req)?);
}
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Accepted<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond_to(req).await?);
}
build.status(Status::Accepted).ok()
build.status(Status::Accepted).ok()
})
}
}
@ -265,14 +269,16 @@ pub struct BadRequest<R>(pub Option<R>);
/// Sets the status code of the response to 400 Bad Request. If the responder is
/// `Some`, it is used to finalize the response.
impl<'r, R: Responder<'r>> Responder<'r> for BadRequest<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond_to(req)?);
}
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for BadRequest<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond_to(req).await?);
}
build.status(Status::BadRequest).ok()
build.status(Status::BadRequest).ok()
})
}
}
@ -372,11 +378,13 @@ impl<'r, R: Responder<'r>> Responder<'r> for Forbidden<R> {
pub struct NotFound<R>(pub R);
/// Sets the status code of the response to 404 Not Found.
impl<'r, R: Responder<'r>> Responder<'r> for NotFound<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> {
Response::build_from(self.0.respond_to(req)?)
.status(Status::NotFound)
.ok()
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for NotFound<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
Response::build_from(self.0.respond_to(req).await?)
.status(Status::NotFound)
.ok()
})
}
}
@ -437,11 +445,13 @@ pub struct Custom<R>(pub Status, pub R);
/// Sets the status code of the response and then delegates the remainder of the
/// response to the wrapped responder.
impl<'r, R: Responder<'r>> Responder<'r> for Custom<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> {
Response::build_from(self.1.respond_to(req)?)
.status(self.0)
.ok()
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Custom<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
Response::build_from(self.1.respond_to(req).await?)
.status(self.0)
.ok()
})
}
}

View File

@ -3,7 +3,7 @@ use std::fmt::{self, Debug};
use futures::io::AsyncRead;
use crate::request::Request;
use crate::response::{Response, Responder, DEFAULT_CHUNK_SIZE};
use crate::response::{Response, Responder, ResultFuture, DEFAULT_CHUNK_SIZE};
use crate::http::Status;
/// Streams a response to a client from an arbitrary `AsyncRead`er type.
@ -70,7 +70,9 @@ impl<T: AsyncRead> From<T> for Stream<T> {
/// response is abandoned, and the response ends abruptly. An error is printed
/// to the console with an indication of what went wrong.
impl<'r, T: AsyncRead + Send + 'r> Responder<'r> for Stream<T> {
fn respond_to(self, _: &Request<'_>) -> Result<Response<'r>, Status> {
Response::build().chunked_body(self.0, self.1).ok()
fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async {
Response::build().chunked_body(self.0, self.1).ok()
})
}
}

View File

@ -267,7 +267,7 @@ impl Rocket {
}
// Run the response fairings.
self.fairings.handle_response(request, &mut response);
self.fairings.handle_response(request, &mut response).await;
// Strip the body if this is a `HEAD` request.
if was_head_request {

View File

@ -15,7 +15,7 @@ type Selector = Method;
// A handler to use when one is needed temporarily.
pub(crate) fn dummy_handler<'r>(r: &'r Request<'_>, _: crate::Data) -> std::pin::Pin<Box<dyn Future<Output = crate::handler::Outcome<'r>> + Send + 'r>> {
futures::future::ready(crate::Outcome::from(r, ())).boxed()
crate::Outcome::from(r, ())
}
#[derive(Default)]

View File

@ -34,16 +34,16 @@ mod fairing_before_head_strip {
assert_eq!(req.method(), Method::Head);
}))
.attach(AdHoc::on_response("Check HEAD 2", |req, res| {
assert_eq!(req.method(), Method::Head);
// TODO.async: Needs async on_response fairings
// assert_eq!(res.body_string().await, Some(RESPONSE_STRING.into()));
Box::pin(async move {
assert_eq!(req.method(), Method::Head);
assert_eq!(res.body_string().await, Some(RESPONSE_STRING.into()));
})
}));
let client = Client::new(rocket).unwrap();
let mut response = client.head("/").dispatch();
assert_eq!(response.status(), Status::Ok);
// TODO.async: See above
// assert!(response.body().is_none());
assert!(response.body().is_none());
}
#[test]
@ -63,15 +63,15 @@ mod fairing_before_head_strip {
assert_eq!(c.0.fetch_add(1, Ordering::SeqCst), 0);
}))
.attach(AdHoc::on_response("Check GET", |req, res| {
assert_eq!(req.method(), Method::Get);
// TODO.async: Needs async on_response fairings
// assert_eq!(res.body_string().await, Some(RESPONSE_STRING.into()));
Box::pin(async move {
assert_eq!(req.method(), Method::Get);
assert_eq!(res.body_string().await, Some(RESPONSE_STRING.into()));
})
}));
let client = Client::new(rocket).unwrap();
let mut response = client.head("/").dispatch();
assert_eq!(response.status(), Status::Ok);
// TODO.async: See above
// assert!(response.body().is_none());
assert!(response.body().is_none());
}
}

View File

@ -14,7 +14,7 @@ pub struct CustomResponder<'r, R> {
}
impl<'r, R: Responder<'r>> Responder<'r> for CustomResponder<'r, R> {
fn respond_to(self, _: &rocket::Request) -> response::Result<'r> {
fn respond_to(self, _: &rocket::Request) -> response::ResultFuture<'r> {
unimplemented!()
}
}