From 7c34a3a93e90e51dd487743ff3c6a753b034ee0c Mon Sep 17 00:00:00 2001 From: Jeb Rosen Date: Wed, 24 Jul 2019 08:21:52 -0700 Subject: [PATCH] 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. --- core/codegen/src/attribute/catch.rs | 2 +- core/codegen/src/attribute/route.rs | 2 +- core/codegen/src/derive/responder.rs | 18 +-- core/lib/src/catcher.rs | 2 +- core/lib/src/fairing/ad_hoc.rs | 10 +- core/lib/src/fairing/fairings.rs | 13 +- core/lib/src/fairing/mod.rs | 9 +- core/lib/src/handler.rs | 46 +++--- core/lib/src/response/content.rs | 20 +-- core/lib/src/response/debug.rs | 12 +- core/lib/src/response/flash.rs | 6 +- core/lib/src/response/mod.rs | 2 + core/lib/src/response/named_file.rs | 18 +-- core/lib/src/response/redirect.rs | 26 ++-- core/lib/src/response/responder.rs | 135 ++++++++++-------- core/lib/src/response/response.rs | 8 +- core/lib/src/response/status.rs | 84 ++++++----- core/lib/src/response/stream.rs | 8 +- core/lib/src/rocket.rs | 2 +- core/lib/src/router/mod.rs | 2 +- .../fairing_before_head_strip-issue-546.rs | 20 +-- .../lib/tests/responder_lifetime-issue-345.rs | 2 +- 22 files changed, 257 insertions(+), 190 deletions(-) diff --git a/core/codegen/src/attribute/catch.rs b/core/codegen/src/attribute/catch.rs index cf519365..9b87b69b 100644 --- a/core/codegen/src/attribute/catch.rs +++ b/core/codegen/src/attribute/catch.rs @@ -75,7 +75,7 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result { // 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. diff --git a/core/codegen/src/attribute/route.rs b/core/codegen/src/attribute/route.rs index 2be3853a..b3b18fab 100644 --- a/core/codegen/src/attribute/route.rs +++ b/core/codegen/src/attribute/route.rs @@ -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 } } diff --git a/core/codegen/src/derive/responder.rs b/core/codegen/src/derive/responder.rs index a547ce72..e10fb29f 100644 --- a/core/codegen/src/derive/responder.rs +++ b/core/codegen/src/derive/responder.rs @@ -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() diff --git a/core/lib/src/catcher.rs b/core/lib/src/catcher.rs index f27ebb72..daf7edd2 100644 --- a/core/lib/src/catcher.rs +++ b/core/lib/src/catcher.rs @@ -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() } diff --git a/core/lib/src/fairing/ad_hoc.rs b/core/lib/src/fairing/ad_hoc.rs index 1952b6d4..dc160491 100644 --- a/core/lib/src/fairing/ad_hoc.rs +++ b/core/lib/src/fairing/ad_hoc.rs @@ -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, &Data) + Send + Sync + 'static>), /// An ad-hoc **response** fairing. Called when a response is ready to be /// sent to a client. - Response(Box, &mut Response<'_>) + Send + Sync + 'static>), + Response(Box Fn(&'a Request<'r>, &'a mut Response<'r>) -> Pin + Send + 'a>> + Send + Sync + 'static>), } impl AdHoc { @@ -124,7 +126,7 @@ impl AdHoc { /// }); /// ``` pub fn on_response(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 + 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 + Send + 'a>> { if let AdHocKind::Response(ref callback) = self.kind { callback(request, response) + } else { + Box::pin(async { }) } } } diff --git a/core/lib/src/fairing/fairings.rs b/core/lib/src/fairing/fairings.rs index 0646a1cb..fdf90eb0 100644 --- a/core/lib/src/fairing/fairings.rs +++ b/core/lib/src/fairing/fairings.rs @@ -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 + 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]> { diff --git a/core/lib/src/fairing/mod.rs b/core/lib/src/fairing/mod.rs index 12dd2a4d..75113631 100644 --- a/core/lib/src/fairing/mod.rs +++ b/core/lib/src/fairing/mod.rs @@ -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 + Send + 'a>> { + Box::pin(async { }) + } } impl Fairing for std::sync::Arc { @@ -433,7 +438,7 @@ impl Fairing for std::sync::Arc { } #[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 + Send + 'a>> { (self as &T).on_response(request, response) } } diff --git a/core/lib/src/handler.rs b/core/lib/src/handler.rs index b8d33716..ab9fb048 100644 --- a/core/lib/src/handler.rs +++ b/core/lib/src/handler.rs @@ -206,11 +206,13 @@ impl<'r> Outcome<'r> { /// } /// ``` #[inline] - pub fn from>(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 + 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(req: &Request<'_>, result: Result) -> Outcome<'r> - where T: Responder<'r>, E: std::fmt::Debug + pub fn try_from(req: &'r Request<'_>, result: Result) -> 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(req: &Request<'_>, data: Data, responder: T) -> Outcome<'r> - where T: Responder<'r> + pub fn from_or_forward(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 diff --git a/core/lib/src/response/content.rs b/core/lib/src/response/content.rs index 84cb60cb..d44bf23a 100644 --- a/core/lib/src/response/content.rs +++ b/core/lib/src/response/content.rs @@ -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(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 { +impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Content { #[inline(always)] - fn respond_to(self, req: &Request<'_>) -> Result, 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 { - fn respond_to(self, req: &Request<'_>) -> Result, Status> { + impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for $name { + fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> { Content(ContentType::$ct, self.0).respond_to(req) } } diff --git a/core/lib/src/response/debug.rs b/core/lib/src/response/debug.rs index e12c5296..88dee478 100644 --- a/core/lib/src/response/debug.rs +++ b/core/lib/src/response/debug.rs @@ -61,10 +61,12 @@ impl From for Debug { } } -impl<'r, E: std::fmt::Debug> Responder<'r> for Debug { - 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 { + 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() + }) } } diff --git a/core/lib/src/response/flash.rs b/core/lib/src/response/flash.rs index d2b8ee06..a5bca9ab 100644 --- a/core/lib/src/response/flash.rs +++ b/core/lib/src/response/flash.rs @@ -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 { /// 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 { - fn respond_to(self, req: &Request<'_>) -> Result, Status> { +impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Flash { + 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) diff --git a/core/lib/src/response/mod.rs b/core/lib/src/response/mod.rs index eaeb238e..e77e585f 100644 --- a/core/lib/src/response/mod.rs +++ b/core/lib/src/response/mod.rs @@ -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, crate::http::Status>; +/// Type alias for the `Result` of a `Responder::respond` call. +pub type ResultFuture<'r> = std::pin::Pin> + Send + 'r>>; diff --git a/core/lib/src/response/named_file.rs b/core/lib/src/response/named_file.rs index 5c98d6aa..e5cfdcd5 100644 --- a/core/lib/src/response/named_file.rs +++ b/core/lib/src/response/named_file.rs @@ -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) + }) } } diff --git a/core/lib/src/response/redirect.rs b/core/lib/src/response/redirect.rs index 0d478bcd..a1d68be0 100644 --- a/core/lib/src/response/redirect.rs +++ b/core/lib/src/response/redirect.rs @@ -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, 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) + } + }) } } diff --git a/core/lib/src/response/responder.rs b/core/lib/src/response/responder.rs index 725dad88..18605960 100644 --- a/core/lib/src/response/responder.rs +++ b/core/lib/src/response/responder.rs @@ -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 { - 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 { - 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 { + 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 { - 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 { + 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 { /// `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) - } - } + }) } } diff --git a/core/lib/src/response/response.rs b/core/lib/src/response/response.rs index 4b407953..19f87785 100644 --- a/core/lib/src/response/response.rs +++ b/core/lib/src/response/response.rs @@ -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, Status> { - Ok(self) + fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> { + Box::pin(async { + Ok(self) + }) } } diff --git a/core/lib/src/response/status.rs b/core/lib/src/response/status.rs index 73319e0a..33a7494a 100644 --- a/core/lib/src/response/status.rs +++ b/core/lib/src/response/status.rs @@ -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 { /// 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 { - fn respond_to(self, req: &Request<'_>) -> Result, 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 { + 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(pub Option); /// 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 { - fn respond_to(self, req: &Request<'_>) -> Result, 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 { + 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(pub Option); /// 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 { - fn respond_to(self, req: &Request<'_>) -> Result, 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 { + 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 { pub struct NotFound(pub R); /// Sets the status code of the response to 404 Not Found. -impl<'r, R: Responder<'r>> Responder<'r> for NotFound { - fn respond_to(self, req: &Request<'_>) -> Result, Status> { - Response::build_from(self.0.respond_to(req)?) - .status(Status::NotFound) - .ok() +impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for NotFound { + 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(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 { - fn respond_to(self, req: &Request<'_>) -> Result, Status> { - Response::build_from(self.1.respond_to(req)?) - .status(self.0) - .ok() +impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Custom { + 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() + }) } } diff --git a/core/lib/src/response/stream.rs b/core/lib/src/response/stream.rs index 1a48c7e5..7f625bb6 100644 --- a/core/lib/src/response/stream.rs +++ b/core/lib/src/response/stream.rs @@ -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 From for Stream { /// 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 { - fn respond_to(self, _: &Request<'_>) -> Result, 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() + }) } } diff --git a/core/lib/src/rocket.rs b/core/lib/src/rocket.rs index be767bc0..87b32f62 100644 --- a/core/lib/src/rocket.rs +++ b/core/lib/src/rocket.rs @@ -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 { diff --git a/core/lib/src/router/mod.rs b/core/lib/src/router/mod.rs index d423e962..6f99d329 100644 --- a/core/lib/src/router/mod.rs +++ b/core/lib/src/router/mod.rs @@ -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> + Send + 'r>> { - futures::future::ready(crate::Outcome::from(r, ())).boxed() + crate::Outcome::from(r, ()) } #[derive(Default)] diff --git a/core/lib/tests/fairing_before_head_strip-issue-546.rs b/core/lib/tests/fairing_before_head_strip-issue-546.rs index 648046ac..cc1536b4 100644 --- a/core/lib/tests/fairing_before_head_strip-issue-546.rs +++ b/core/lib/tests/fairing_before_head_strip-issue-546.rs @@ -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()); } } diff --git a/core/lib/tests/responder_lifetime-issue-345.rs b/core/lib/tests/responder_lifetime-issue-345.rs index 8ca2964a..8e5b3ed2 100644 --- a/core/lib/tests/responder_lifetime-issue-345.rs +++ b/core/lib/tests/responder_lifetime-issue-345.rs @@ -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!() } }