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. // Emit this to force a type signature check.
let #catcher: #fn_sig = #user_catcher_fn_name; let #catcher: #fn_sig = #user_catcher_fn_name;
let ___responder = #catcher(#inputs); 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. // 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 => quote_spanned! { ret_span =>
#responder_stmt #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! { .function(|_, inner| quote! {
fn respond_to( fn respond_to(
self, self,
__req: &::rocket::Request __req: &'__r ::rocket::Request
) -> ::rocket::response::Result<'__r> { ) -> ::rocket::response::ResultFuture<'__r> {
#inner #inner
} }
}) })
@ -51,7 +51,7 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
quote_spanned! { f.span().into() => quote_spanned! { f.span().into() =>
let mut __res = <#ty as ::rocket::response::Responder>::respond_to( let mut __res = <#ty as ::rocket::response::Responder>::respond_to(
#accessor, __req #accessor, __req
)?; ).await?;
} }
}).expect("have at least one field"); }).expect("have at least one field");
@ -71,11 +71,13 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
}); });
Ok(quote! { Ok(quote! {
Box::pin(async move {
#responder #responder
#(#headers)* #(#headers)*
#content_type #content_type
#status #status
#_Ok(__res) Ok(__res)
})
}) })
}) })
.to_tokens() .to_tokens()

View File

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

View File

@ -1,3 +1,5 @@
use std::future::Future;
use std::pin::Pin;
use std::sync::Mutex; use std::sync::Mutex;
use crate::{Rocket, Request, Response, Data}; use crate::{Rocket, Request, Response, Data};
@ -49,7 +51,7 @@ enum AdHocKind {
Request(Box<dyn Fn(&mut Request<'_>, &Data) + Send + Sync + 'static>), Request(Box<dyn Fn(&mut Request<'_>, &Data) + Send + Sync + 'static>),
/// An ad-hoc **response** fairing. Called when a response is ready to be /// An ad-hoc **response** fairing. Called when a response is ready to be
/// sent to a client. /// 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 { impl AdHoc {
@ -124,7 +126,7 @@ impl AdHoc {
/// }); /// });
/// ``` /// ```
pub fn on_response<F>(name: &'static str, f: F) -> 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)) } 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 { if let AdHocKind::Response(ref callback) = self.kind {
callback(request, response) 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::{Rocket, Request, Response, Data};
use crate::fairing::{Fairing, Kind}; use crate::fairing::{Fairing, Kind};
use crate::logger::PaintExt; use crate::logger::PaintExt;
@ -66,10 +69,12 @@ impl Fairings {
} }
#[inline(always)] #[inline(always)]
pub fn handle_response(&self, request: &Request<'_>, response: &mut 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 { for &i in &self.response {
self.all_fairings[i].on_response(request, response); self.all_fairings[i].on_response(request, response).await;
} }
})
} }
pub fn failures(&self) -> Option<&[&'static str]> { 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 //! of other `Fairings` are not jeopardized. For instance, unless it is made
//! abundantly clear, a fairing should not rewrite every request. //! abundantly clear, a fairing should not rewrite every request.
use std::pin::Pin;
use std::future::Future;
use crate::{Rocket, Request, Response, Data}; use crate::{Rocket, Request, Response, Data};
mod fairings; mod fairings;
@ -408,7 +411,9 @@ pub trait Fairing: Send + Sync + 'static {
/// ///
/// The default implementation of this method does nothing. /// The default implementation of this method does nothing.
#[allow(unused_variables)] #[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> { impl<T: Fairing> Fairing for std::sync::Arc<T> {
@ -433,7 +438,7 @@ impl<T: Fairing> Fairing for std::sync::Arc<T> {
} }
#[inline] #[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) (self as &T).on_response(request, response)
} }
} }

View File

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

View File

@ -23,7 +23,7 @@
//! ``` //! ```
use crate::request::Request; use crate::request::Request;
use crate::response::{Response, Responder}; use crate::response::{Response, Responder, ResultFuture};
use crate::http::{Status, ContentType}; use crate::http::{Status, ContentType};
/// Sets the Content-Type of a `Responder` to a chosen value. /// 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 /// Overrides the Content-Type of the response to the wrapped `ContentType` then
/// delegates the remainder of the response to the wrapped responder. /// 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)] #[inline(always)]
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> { fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
Response::build() Response::build()
.merge(self.1.respond_to(req)?) .merge(self.1.respond_to(req).await?)
.header(self.0) .header(self.0)
.ok() .ok()
})
} }
} }
@ -71,8 +73,8 @@ macro_rules! ctrs {
/// Sets the Content-Type of the response then delegates the /// Sets the Content-Type of the response then delegates the
/// remainder of the response to the wrapped responder. /// remainder of the response to the wrapped responder.
impl<'r, R: Responder<'r>> Responder<'r> for $name<R> { impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for $name<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> { fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Content(ContentType::$ct, self.0).respond_to(req) 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> { impl<'r, E: std::fmt::Debug + Send + 'r> Responder<'r> for Debug<E> {
fn respond_to(self, _: &Request<'_>) -> response::Result<'r> { fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
warn_!("Debug: {:?}", Paint::default(self.0)); warn_!("Debug: {:?}", Paint::default(self.0));
warn_!("Debug always responds with {}.", Status::InternalServerError); warn_!("Debug always responds with {}.", Status::InternalServerError);
Response::build().status(Status::InternalServerError).ok() Response::build().status(Status::InternalServerError).ok()
})
} }
} }

View File

@ -3,7 +3,7 @@ use std::convert::AsRef;
use time::Duration; use time::Duration;
use crate::outcome::IntoOutcome; use crate::outcome::IntoOutcome;
use crate::response::{Response, Responder}; use crate::response::{Response, Responder, ResultFuture};
use crate::request::{self, Request, FromRequest}; use crate::request::{self, Request, FromRequest};
use crate::http::{Status, Cookie}; use crate::http::{Status, Cookie};
use std::sync::atomic::{AtomicBool, Ordering}; 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. 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 /// response handling to the wrapped responder. As a result, the `Outcome` of
/// the response is the `Outcome` of the wrapped `Responder`. /// the response is the `Outcome` of the wrapped `Responder`.
impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> { impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Flash<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> { fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
trace_!("Flash: setting message: {}:{}", self.name, self.message); trace_!("Flash: setting message: {}:{}", self.name, self.message);
req.cookies().add(self.cookie()); req.cookies().add(self.cookie());
self.inner.respond_to(req) 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. /// Type alias for the `Result` of a `Responder::respond` call.
pub type Result<'r> = std::result::Result<self::Response<'r>, crate::http::Status>; 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,9 +78,10 @@ impl NamedFile {
/// recognized. See [`ContentType::from_extension()`] for more information. If /// recognized. See [`ContentType::from_extension()`] for more information. If
/// you would like to stream a file with a different Content-Type than that /// you would like to stream a file with a different Content-Type than that
/// implied by its extension, use a [`File`] directly. /// implied by its extension, use a [`File`] directly.
impl Responder<'_> for NamedFile { impl<'r> Responder<'r> for NamedFile {
fn respond_to(self, req: &Request<'_>) -> response::Result<'static> { fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
let mut response = self.1.respond_to(req)?; Box::pin(async move {
let mut response = self.1.respond_to(req).await?;
if let Some(ext) = self.0.extension() { if let Some(ext) = self.0.extension() {
if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) { if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) {
response.set_header(ct); response.set_header(ct);
@ -88,6 +89,7 @@ impl Responder<'_> for NamedFile {
} }
Ok(response) Ok(response)
})
} }
} }

View File

@ -1,7 +1,7 @@
use std::convert::TryInto; use std::convert::TryInto;
use crate::request::Request; use crate::request::Request;
use crate::response::{Response, Responder}; use crate::response::{Response, Responder, ResultFuture};
use crate::http::uri::Uri; use crate::http::uri::Uri;
use crate::http::Status; use crate::http::Status;
@ -147,8 +147,9 @@ impl Redirect {
/// the `Location` header field. The body of the response is empty. If the URI /// 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 /// value used to create the `Responder` is an invalid URI, an error of
/// `Status::InternalServerError` is returned. /// `Status::InternalServerError` is returned.
impl Responder<'_> for Redirect { impl<'r> Responder<'r> for Redirect {
fn respond_to(self, _: &Request<'_>) -> Result<Response<'static>, Status> { fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async {
if let Some(uri) = self.1 { if let Some(uri) = self.1 {
Response::build() Response::build()
.status(self.0) .status(self.0)
@ -158,5 +159,6 @@ impl Responder<'_> for Redirect {
error!("Invalid URI used for redirect."); error!("Invalid URI used for redirect.");
Err(Status::InternalServerError) 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 /// 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 /// to generate a final error response, which is then written out to the
/// client. /// 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 /// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`. /// containing the string `self`. Always returns `Ok`.
impl<'r> Responder<'r> for &'r str { impl<'r> Responder<'r> for &'r str {
fn respond_to(self, _: &Request<'_>) -> response::Result<'r> { fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
Response::build() Response::build()
.header(ContentType::Plain) .header(ContentType::Plain)
.sized_body(Cursor::new(self)) .sized_body(Cursor::new(self))
.ok() .ok()
})
} }
} }
/// Returns a response with Content-Type `text/plain` and a fixed-size body /// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`. /// containing the string `self`. Always returns `Ok`.
impl Responder<'_> for String { impl Responder<'_> for String {
fn respond_to(self, _: &Request<'_>) -> response::Result<'static> { fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'static> {
Box::pin(async move {
Response::build() Response::build()
.header(ContentType::Plain) .header(ContentType::Plain)
.sized_body(Cursor::new(self)) .sized_body(Cursor::new(self))
.ok() .ok()
})
} }
} }
/// Returns a response with Content-Type `application/octet-stream` and a /// Returns a response with Content-Type `application/octet-stream` and a
/// fixed-size body containing the data in `self`. Always returns `Ok`. /// fixed-size body containing the data in `self`. Always returns `Ok`.
impl<'r> Responder<'r> for &'r [u8] { impl<'r> Responder<'r> for &'r [u8] {
fn respond_to(self, _: &Request<'_>) -> response::Result<'r> { fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
Response::build() Response::build()
.header(ContentType::Binary) .header(ContentType::Binary)
.sized_body(Cursor::new(self)) .sized_body(Cursor::new(self))
.ok() .ok()
})
} }
} }
/// Returns a response with Content-Type `application/octet-stream` and a /// Returns a response with Content-Type `application/octet-stream` and a
/// fixed-size body containing the data in `self`. Always returns `Ok`. /// fixed-size body containing the data in `self`. Always returns `Ok`.
impl Responder<'_> for Vec<u8> { impl Responder<'_> for Vec<u8> {
fn respond_to(self, _: &Request<'_>) -> response::Result<'static> { fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'static> {
Box::pin(async move {
Response::build() Response::build()
.header(ContentType::Binary) .header(ContentType::Binary)
.sized_body(Cursor::new(self)) .sized_body(Cursor::new(self))
.ok() .ok()
})
} }
} }
/// Returns a response with a sized body for the file. Always returns `Ok`. /// Returns a response with a sized body for the file. Always returns `Ok`.
impl Responder<'_> for File { impl Responder<'_> for File {
fn respond_to(self, _: &Request<'_>) -> response::Result<'static> { fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'static> {
Box::pin(async move {
let metadata = self.metadata(); let metadata = self.metadata();
let stream = BufReader::new(tokio::fs::File::from_std(self)).compat(); let stream = BufReader::new(tokio::fs::File::from_std(self)).compat();
match metadata { match metadata {
Ok(md) => Response::build().raw_body(Body::Sized(stream, md.len())).ok(), Ok(md) => Response::build().raw_body(Body::Sized(stream, md.len())).ok(),
Err(_) => Response::build().streamed_body(stream).ok() Err(_) => Response::build().streamed_body(stream).ok()
} }
})
} }
} }
/// Returns an empty, default `Response`. Always returns `Ok`. /// Returns an empty, default `Response`. Always returns `Ok`.
impl Responder<'_> for () { impl Responder<'_> for () {
fn respond_to(self, _: &Request<'_>) -> response::Result<'static> { fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'static> {
Box::pin(async move {
Ok(Response::new()) Ok(Response::new())
})
} }
} }
/// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints /// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints
/// a warning message and returns an `Err` of `Status::NotFound`. /// a warning message and returns an `Err` of `Status::NotFound`.
impl<'r, R: Responder<'r>> Responder<'r> for Option<R> { impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Option<R> {
fn respond_to(self, req: &Request<'_>) -> response::Result<'r> { fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
self.map_or_else(|| { Box::pin(async move {
match self {
Some(r) => r.respond_to(req).await,
None => {
warn_!("Response was `None`."); warn_!("Response was `None`.");
Err(Status::NotFound) Err(Status::NotFound)
}, |r| r.respond_to(req)) },
}
})
} }
} }
/// Responds with the wrapped `Responder` in `self`, whether it is `Ok` or /// Responds with the wrapped `Responder` in `self`, whether it is `Ok` or
/// `Err`. /// `Err`.
impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result<R, E> { impl<'r, R: Responder<'r> + Send + 'r, E: Responder<'r> + Send + 'r> Responder<'r> for Result<R, E> {
fn respond_to(self, req: &Request<'_>) -> response::Result<'r> { fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
match self { match self {
Ok(responder) => responder.respond_to(req), Ok(responder) => responder.respond_to(req).await,
Err(responder) => responder.respond_to(req), Err(responder) => responder.respond_to(req).await,
} }
})
} }
} }
@ -295,8 +314,9 @@ 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 /// `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 /// status code emit an error message and forward to the `500` (internal server
/// error) catcher. /// error) catcher.
impl Responder<'_> for Status { impl<'r> Responder<'r> for Status {
fn respond_to(self, _: &Request<'_>) -> response::Result<'static> { fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
match self.class() { match self.class() {
StatusClass::ClientError | StatusClass::ServerError => Err(self), StatusClass::ClientError | StatusClass::ServerError => Err(self),
StatusClass::Success if self.code < 206 => { StatusClass::Success if self.code < 206 => {
@ -311,5 +331,6 @@ impl Responder<'_> for Status {
Err(Status::InternalServerError) Err(Status::InternalServerError)
} }
} }
})
} }
} }

View File

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

View File

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

View File

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

View File

@ -267,7 +267,7 @@ impl Rocket {
} }
// Run the response fairings. // 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. // Strip the body if this is a `HEAD` request.
if was_head_request { if was_head_request {

View File

@ -15,7 +15,7 @@ type Selector = Method;
// A handler to use when one is needed temporarily. // 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>> { 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)] #[derive(Default)]

View File

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