diff --git a/contrib/lib/src/json.rs b/contrib/lib/src/json.rs index e5a3ca74..fa71547e 100644 --- a/contrib/lib/src/json.rs +++ b/contrib/lib/src/json.rs @@ -19,6 +19,7 @@ use std::io; use std::iter::FromIterator; use tokio::io::AsyncReadExt; +use rocket::futures::future::BoxFuture; use rocket::request::Request; use rocket::outcome::Outcome::*; @@ -169,14 +170,19 @@ impl<'a, T: Deserialize<'a>> FromData<'a> for Json { /// JSON and a fixed-size body with the serialized value. If serialization /// fails, an `Err` of `Status::InternalServerError` is returned. impl<'r, T: Serialize> Responder<'r> for Json { - fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> { - match serde_json::to_string(&self.0) { - Ok(string) => Box::pin(async move { Ok(content::Json(string).respond_to(req).await.unwrap()) }), - Err(e) => Box::pin(async move { - error_!("JSON failed to serialize: {:?}", e); - Err(Status::InternalServerError) - }) - } + fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>> + where 'a: 'x, 'r: 'x, Self: 'x + { + let json_string = serde_json::to_string(&self.0); + Box::pin(async move { + match json_string { + Ok(string) => Ok(content::Json(string).respond_to(req).await.unwrap()), + Err(e) => { + error_!("JSON failed to serialize: {:?}", e); + Err(Status::InternalServerError) + } + } + }) } } @@ -291,10 +297,10 @@ impl FromIterator for JsonValue where serde_json::Value: FromIterator { /// Serializes the value into JSON. Returns a response with Content-Type JSON /// and a fixed-size body with the serialized value. +#[rocket::async_trait] impl<'r> Responder<'r> for JsonValue { - #[inline] - fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> { - content::Json(self.0.to_string()).respond_to(req) + async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { + content::Json(self.0.to_string()).respond_to(req).await } } diff --git a/contrib/lib/src/msgpack.rs b/contrib/lib/src/msgpack.rs index 31b18c0b..bfb73283 100644 --- a/contrib/lib/src/msgpack.rs +++ b/contrib/lib/src/msgpack.rs @@ -23,6 +23,7 @@ use rocket::outcome::Outcome::*; use rocket::data::{Data, FromData, FromDataFuture, Transform::*, TransformFuture, Transformed}; use rocket::http::Status; use rocket::response::{self, content, Responder}; +use rocket::futures::future::BoxFuture; use serde::Serialize; use serde::de::Deserialize; @@ -157,13 +158,15 @@ impl<'a, T: Deserialize<'a>> FromData<'a> for MsgPack { /// Content-Type `MsgPack` and a fixed-size body with the serialization. If /// serialization fails, an `Err` of `Status::InternalServerError` is returned. impl<'r, T: Serialize> Responder<'r> for MsgPack { - fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> { + fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>> + where 'a: 'x, 'r: 'x, Self: 'x + { match rmp_serde::to_vec(&self.0) { Ok(buf) => content::MsgPack(buf).respond_to(req), - Err(e) => Box::pin(async move { + Err(e) => { error_!("MsgPack failed to serialize: {:?}", e); - Err(Status::InternalServerError) - }), + Box::pin(async { Err(Status::InternalServerError) }) + } } } } diff --git a/contrib/lib/src/templates/mod.rs b/contrib/lib/src/templates/mod.rs index 0ad40e69..b6b00ad4 100644 --- a/contrib/lib/src/templates/mod.rs +++ b/contrib/lib/src/templates/mod.rs @@ -383,21 +383,20 @@ impl Template { /// Returns a response with the Content-Type derived from the template's /// extension and a fixed-size body containing the rendered template. If /// rendering fails, an `Err` of `Status::InternalServerError` is returned. +#[rocket::async_trait] impl<'r> Responder<'r> for Template { - fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> { - Box::pin(async move { - let (render, content_type) = { - let ctxt = req.guard::>().await.succeeded().ok_or_else(|| { - error_!("Uninitialized template context: missing fairing."); - info_!("To use templates, you must attach `Template::fairing()`."); - info_!("See the `Template` documentation for more information."); - Status::InternalServerError - })?.inner().context(); + async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { + let (render, content_type) = { + let ctxt = req.guard::>().await.succeeded().ok_or_else(|| { + error_!("Uninitialized template context: missing fairing."); + info_!("To use templates, you must attach `Template::fairing()`."); + info_!("See the `Template` documentation for more information."); + Status::InternalServerError + })?.inner().context(); - self.finalize(&ctxt)? - }; + self.finalize(&ctxt)? + }; - Content(content_type, render).respond_to(req).await - }) + Content(content_type, render).respond_to(req).await } } diff --git a/core/codegen/src/attribute/catch.rs b/core/codegen/src/attribute/catch.rs index 4b6d91b8..71d09455 100644 --- a/core/codegen/src/attribute/catch.rs +++ b/core/codegen/src/attribute/catch.rs @@ -103,7 +103,6 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result { .status(#status) .merge(__response) .ok() - .await }) } diff --git a/core/codegen/src/derive/responder.rs b/core/codegen/src/derive/responder.rs index e10fb29f..d80c86c4 100644 --- a/core/codegen/src/derive/responder.rs +++ b/core/codegen/src/derive/responder.rs @@ -30,10 +30,12 @@ pub fn derive_responder(input: TokenStream) -> TokenStream { false => Ok(()) }) .function(|_, inner| quote! { - fn respond_to( + fn respond_to<'__i, '__x>( self, - __req: &'__r ::rocket::Request - ) -> ::rocket::response::ResultFuture<'__r> { + __req: &'__r ::rocket::request::Request<'__i> + ) -> ::rocket::futures::future::BoxFuture<'__x, ::rocket::response::Result<'__r>> + where '__i: '__x, '__r: '__x, Self: '__x + { #inner } }) diff --git a/core/codegen/tests/responder.rs b/core/codegen/tests/responder.rs index 4902dd2f..55f137a3 100644 --- a/core/codegen/tests/responder.rs +++ b/core/codegen/tests/responder.rs @@ -28,8 +28,7 @@ async fn responder_foo() { let req = local_req.inner(); let mut response = Foo::First("hello".into()) - .respond_to(req) - .await + .respond_to(req).await .expect("response okay"); assert_eq!(response.status(), Status::Ok); @@ -37,8 +36,7 @@ async fn responder_foo() { assert_eq!(response.body_string().await, Some("hello".into())); let mut response = Foo::Second("just a test".into()) - .respond_to(req) - .await + .respond_to(req).await .expect("response okay"); assert_eq!(response.status(), Status::InternalServerError); @@ -46,8 +44,7 @@ async fn responder_foo() { assert_eq!(response.body_string().await, Some("just a test".into())); let mut response = Foo::Third { responder: "well, hi", ct: ContentType::JSON } - .respond_to(req) - .await + .respond_to(req).await .expect("response okay"); assert_eq!(response.status(), Status::NotFound); @@ -55,8 +52,7 @@ async fn responder_foo() { assert_eq!(response.body_string().await, Some("well, hi".into())); let mut response = Foo::Fourth { string: "goodbye", ct: ContentType::JSON } - .respond_to(req) - .await + .respond_to(req).await .expect("response okay"); assert_eq!(response.status(), Status::raw(105)); @@ -106,8 +102,7 @@ async fn responder_baz() { let req = local_req.inner(); let mut response = Baz { responder: "just a custom" } - .respond_to(req) - .await + .respond_to(req).await .expect("response okay"); assert_eq!(response.status(), Status::Ok); diff --git a/core/http/src/accept.rs b/core/http/src/accept.rs index a8c944ec..6e441ff8 100644 --- a/core/http/src/accept.rs +++ b/core/http/src/accept.rs @@ -158,10 +158,7 @@ impl PartialEq for AcceptParams { /// use rocket::http::Accept; /// use rocket::response::Response; /// -/// # #[allow(unused_variables)] -/// # rocket::async_test(async { -/// let response = Response::build().header(Accept::JSON).await; -/// # }) +/// let response = Response::build().header(Accept::JSON).finalize(); /// ``` #[derive(Debug, Clone, PartialEq)] pub struct Accept(pub(crate) AcceptParams); diff --git a/core/http/src/content_type.rs b/core/http/src/content_type.rs index d3c8b954..9856fb2b 100644 --- a/core/http/src/content_type.rs +++ b/core/http/src/content_type.rs @@ -38,10 +38,7 @@ use crate::ext::IntoCollection; /// use rocket::http::ContentType; /// use rocket::response::Response; /// -/// # #[allow(unused_variables)] -/// # rocket::async_test(async { -/// let response = Response::build().header(ContentType::HTML).await; -/// # }) +/// let response = Response::build().header(ContentType::HTML).finalize(); /// ``` #[derive(Debug, Clone, PartialEq, Hash)] pub struct ContentType(pub MediaType); diff --git a/core/lib/Cargo.toml b/core/lib/Cargo.toml index 639d6bca..53ead5be 100644 --- a/core/lib/Cargo.toml +++ b/core/lib/Cargo.toml @@ -28,6 +28,7 @@ ctrl_c_shutdown = ["tokio/signal"] rocket_codegen = { version = "0.5.0-dev", path = "../codegen" } rocket_http = { version = "0.5.0-dev", path = "../http" } futures-util = "0.3.0" +futures = "0.3.0" tokio = { version = "0.2.9", features = ["fs", "io-std", "io-util", "rt-threaded", "sync"] } yansi = "0.5" log = { version = "0.4", features = ["std"] } diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index 61798419..3861253e 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -99,6 +99,9 @@ pub use async_trait::*; #[macro_use] extern crate log; #[macro_use] extern crate pear; +pub use futures; +pub use tokio; + #[doc(hidden)] #[macro_use] pub mod logger; #[macro_use] pub mod outcome; pub mod local; diff --git a/core/lib/src/response/content.rs b/core/lib/src/response/content.rs index 1e0862a4..2a0ab96a 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, ResultFuture}; +use crate::response::{self, Response, Responder}; use crate::http::ContentType; /// Sets the Content-Type of a `Responder` to a chosen value. @@ -46,21 +46,21 @@ 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. +#[crate::async_trait] impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Content { #[inline(always)] - 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() - .await - }) + async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { + Response::build() + .merge(self.1.respond_to(req).await?) + .header(self.0) + .ok() } } macro_rules! ctrs { ($($name:ident: $ct:ident, $name_str:expr, $ct_str:expr),+) => { + use futures_util::future::BoxFuture; + $( #[doc="Override the `Content-Type` of the response to "] #[doc=$name_str] @@ -75,7 +75,9 @@ 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> + Send + 'r> Responder<'r> for $name { - fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> { + fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>> + where 'a: 'x, 'r: 'x, Self: 'x + { 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 f09084b4..2e51bc34 100644 --- a/core/lib/src/response/debug.rs +++ b/core/lib/src/response/debug.rs @@ -63,12 +63,11 @@ impl From for Debug { } } +#[crate::async_trait] 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().await - }) + async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { + 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 b9c7f308..ba47233a 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::{Responder, ResultFuture}; +use crate::response::{self, Responder}; use crate::request::{self, Request, FromRequest}; use crate::http::{Status, Cookie}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -198,11 +198,12 @@ 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`. +#[crate::async_trait] impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Flash { - fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> { + async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { trace_!("Flash: setting message: {}:{}", self.name, self.message); req.cookies().add(self.cookie()); - self.inner.respond_to(req) + self.inner.respond_to(req).await } } diff --git a/core/lib/src/response/mod.rs b/core/lib/src/response/mod.rs index d86da660..ceedf04e 100644 --- a/core/lib/src/response/mod.rs +++ b/core/lib/src/response/mod.rs @@ -46,8 +46,5 @@ pub use self::stream::Stream; pub use self::debug::Debug; #[doc(inline)] pub use self::content::Content; -/// Type alias for the `Result` of a `Responder::respond` call. +/// Type alias for the `Result` of a [`Responder::respond_to()`] call. pub type Result<'r> = std::result::Result, crate::http::Status>; - -/// Type alias for the `Future` returned by a `Responder::respond` call. -pub type ResultFuture<'r> = futures_util::future::BoxFuture<'r, Result<'r>>; diff --git a/core/lib/src/response/named_file.rs b/core/lib/src/response/named_file.rs index e5cfdcd5..ed993c43 100644 --- a/core/lib/src/response/named_file.rs +++ b/core/lib/src/response/named_file.rs @@ -78,18 +78,17 @@ 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. +#[crate::async_trait] 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); - } + async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { + 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 ab64c760..01ad5924 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, ResultFuture}; +use crate::response::{self, Response, Responder}; use crate::http::uri::Uri; use crate::http::Status; @@ -147,19 +147,17 @@ 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. +#[crate::async_trait] 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() - .await - } else { - error!("Invalid URI used for redirect."); - Err(Status::InternalServerError) - } - }) + async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { + 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 03117b6c..1812787d 100644 --- a/core/lib/src/response/responder.rs +++ b/core/lib/src/response/responder.rs @@ -1,8 +1,8 @@ use std::fs::File; use std::io::Cursor; -use futures_util::future::ready; use tokio::io::BufReader; +use futures_util::future::BoxFuture; use crate::http::{Status, ContentType, StatusClass}; use crate::response::{self, Response, Body}; @@ -113,13 +113,50 @@ use crate::request::Request; /// regardless of the incoming request. Thus, knowing the type is sufficient to /// fully determine its functionality. /// +/// # Async Trait +/// +/// [`Responder`] is an _async_ trait. Implementations of `Responder` may be +/// decorated with an attribute of `#[rocket::async_trait]`, allowing the +/// `respond_to` method to be implemented as an `async fn`: +/// +/// ```rust +/// use rocket::response::{self, Responder}; +/// use rocket::request::Request; +/// # struct MyType; +/// +/// #[rocket::async_trait] +/// impl<'r> Responder<'r> for MyType { +/// async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { +/// /* .. */ +/// # unimplemented!() +/// } +/// } +/// ``` +/// +/// In certain cases, including when implementing a _wrapping_ responder, it may +/// be desirable to perform work before entering an `async` section, or to +/// return a future directly. In this case, `Responder` can be implemented as: +/// +/// ```rust +/// use rocket::response::{self, Responder}; +/// use rocket::futures::future::BoxFuture; +/// use rocket::request::Request; +/// # struct MyType { inner: R }; +/// +/// impl<'r, R: Responder<'r>> Responder<'r> for MyType { +/// fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>> +/// where 'a: 'x, 'r: 'x, Self: 'x +/// { +/// self.inner.respond_to(req) +/// } +/// } +/// ``` +/// /// # Example /// /// Say that you have a custom type, `Person`: /// /// ```rust -/// -/// # #[allow(dead_code)] /// struct Person { /// name: String, /// age: u16 @@ -129,11 +166,16 @@ use crate::request::Request; /// You'd like to use `Person` as a `Responder` so that you can return a /// `Person` directly from a handler: /// -/// ```rust,ignore +/// ```rust +/// # #[macro_use] extern crate rocket; +/// # type Person = String; /// #[get("/person/")] /// fn person(id: usize) -> Option { +/// # /* /// Person::from_id(id) +/// # */ None /// } +/// # fn main() {} /// ``` /// /// You want the `Person` responder to set two header fields: `X-Person-Name` @@ -154,17 +196,16 @@ use crate::request::Request; /// use rocket::response::{self, Response, Responder}; /// use rocket::http::ContentType; /// +/// #[rocket::async_trait] /// impl<'r> Responder<'r> for Person { -/// fn respond_to(self, _: &'r Request) -> response::ResultFuture<'r> { -/// Box::pin(async move { -/// Response::build() -/// .sized_body(Cursor::new(format!("{}:{}", self.name, self.age))) -/// .raw_header("X-Person-Name", self.name) -/// .raw_header("X-Person-Age", self.age.to_string()) -/// .header(ContentType::new("application", "x-person")) -/// .ok() -/// .await -/// }) +/// async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { +/// let person_string = format!("{}:{}", self.name, self.age); +/// Response::build() +/// .sized_body(Cursor::new(person_string)).await +/// .raw_header("X-Person-Name", self.name) +/// .raw_header("X-Person-Age", self.age.to_string()) +/// .header(ContentType::new("application", "x-person")) +/// .ok() /// } /// } /// # @@ -172,6 +213,7 @@ use crate::request::Request; /// # fn person() -> Person { Person { name: "a".to_string(), age: 20 } } /// # fn main() { } /// ``` +#[crate::async_trait] pub trait Responder<'r> { /// Returns `Ok` if a `Response` could be generated successfully. Otherwise, /// returns an `Err` with a failing `Status`. @@ -184,113 +226,108 @@ 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: &'r Request<'_>) -> response::ResultFuture<'r>; + async fn respond_to(self, request: &'r Request<'_>) -> response::Result<'r>; } /// Returns a response with Content-Type `text/plain` and a fixed-size body /// containing the string `self`. Always returns `Ok`. +#[crate::async_trait] impl<'r> Responder<'r> for &'r str { - fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> { - Box::pin(async move { - Response::build() - .header(ContentType::Plain) - .sized_body(Cursor::new(self)) - .ok() - .await - }) + async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { + Response::build() + .header(ContentType::Plain) + .sized_body(Cursor::new(self)).await + .ok() } } /// Returns a response with Content-Type `text/plain` and a fixed-size body /// containing the string `self`. Always returns `Ok`. +#[crate::async_trait] impl<'r> Responder<'r> for String { - fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> { - Box::pin(async move { - Response::build() - .header(ContentType::Plain) - .sized_body(Cursor::new(self)) - .ok() - .await - }) + async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { + Response::build() + .header(ContentType::Plain) + .sized_body(Cursor::new(self)).await + .ok() } } /// Returns a response with Content-Type `application/octet-stream` and a /// fixed-size body containing the data in `self`. Always returns `Ok`. +#[crate::async_trait] impl<'r> Responder<'r> for &'r [u8] { - fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> { - Box::pin(async move { - Response::build() - .header(ContentType::Binary) - .sized_body(Cursor::new(self)) - .ok() - .await - }) + async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { + Response::build() + .header(ContentType::Binary) + .sized_body(Cursor::new(self)).await + .ok() } } /// Returns a response with Content-Type `application/octet-stream` and a /// fixed-size body containing the data in `self`. Always returns `Ok`. +#[crate::async_trait] impl<'r> Responder<'r> for Vec { - fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> { - Box::pin(async move { - Response::build() - .header(ContentType::Binary) - .sized_body(Cursor::new(self)) - .ok() - .await - }) + async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { + Response::build() + .header(ContentType::Binary) + .sized_body(Cursor::new(self)).await + .ok() } } /// Returns a response with a sized body for the file. Always returns `Ok`. +#[crate::async_trait] impl<'r> Responder<'r> for File { - fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> { - tokio::fs::File::from(self).respond_to(req) + async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { + tokio::fs::File::from(self).respond_to(req).await } } /// Returns a response with a sized body for the file. Always returns `Ok`. +#[crate::async_trait] impl<'r> Responder<'r> for tokio::fs::File { - fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> { - Box::pin(async move { - let metadata = self.metadata().await; - let stream = BufReader::new(self); - match metadata { - Ok(md) => Response::build().raw_body(Body::Sized(stream, md.len())).ok().await, - Err(_) => Response::build().streamed_body(stream).ok().await - } - }) + async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { + let metadata = self.metadata().await; + let stream = BufReader::new(self); + 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`. +#[crate::async_trait] impl<'r> Responder<'r> for () { - fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> { - Box::pin(async move { - Ok(Response::new()) - }) + async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { + 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: &'r Request<'_>) -> response::ResultFuture<'r> { + fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>> + where 'a: 'x, 'r: 'x, Self: 'x + { match self { Some(r) => r.respond_to(req), None => { warn_!("Response was `None`."); - Box::pin(ready(Err(Status::NotFound))) + Box::pin(async { Err(Status::NotFound) }) }, } } } -/// 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`. impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result { - fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> { + fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>> + where 'a: 'x, 'r: 'x, Self: 'x + { match self { Ok(responder) => responder.respond_to(req), Err(responder) => responder.respond_to(req), @@ -312,23 +349,22 @@ 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. +#[crate::async_trait] 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().await - } - StatusClass::Informational if self.code == 100 => { - Response::build().status(self).ok().await - } - _ => { - error_!("Invalid status used as responder: {}.", self); - warn_!("Fowarding to 500 (Internal Server Error) catcher."); - Err(Status::InternalServerError) - } + async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { + 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) + } + } } } diff --git a/core/lib/src/response/response.rs b/core/lib/src/response/response.rs index 1e0f808f..2606f3aa 100644 --- a/core/lib/src/response/response.rs +++ b/core/lib/src/response/response.rs @@ -1,12 +1,10 @@ use std::{io, fmt, str}; use std::borrow::Cow; -use std::future::Future; use std::pin::Pin; -use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt}; -use crate::response::{Responder, ResultFuture}; +use crate::response::{self, Responder}; use crate::http::{Header, HeaderMap, Status, ContentType, Cookie}; /// The default size, in bytes, of a chunk for streamed responses. @@ -100,12 +98,6 @@ impl fmt::Debug for Body { } } -/// Internal workaround for `Box` not being allowed. -/// -/// https://github.com/rust-lang/rfcs/issues/2035 -trait AsyncReadAsyncSeek: AsyncRead + AsyncSeek + Unpin + Send {} -impl AsyncReadAsyncSeek for T {} - /// Type for easily building `Response`s. /// /// Building a [`Response`] can be a low-level ordeal; this structure presents a @@ -119,9 +111,8 @@ impl AsyncReadAsyncSeek for T {} /// with field(s) modified in the `Responder` being built. These method calls /// can be chained: `build.a().b()`. /// -/// To finish building and retrieve the built `Response`, `.await` the builder. -/// The [`ok()` method](#method.ok) is also provided as a convenience -/// for `Responder` implementations. +/// To finish building and retrieve the built `Response`, use the +/// [`finalize()`](#method.finalize) or [`ok()`](#method.ok) methods. /// /// ## Headers /// @@ -157,23 +148,18 @@ impl AsyncReadAsyncSeek for T {} /// use rocket::http::{Status, ContentType}; /// /// # rocket::async_test(async { -/// -/// # #[allow(unused_variables)] /// let response = Response::build() /// .status(Status::ImATeapot) /// .header(ContentType::Plain) /// .raw_header("X-Teapot-Make", "Rocket") /// .raw_header("X-Teapot-Model", "Utopia") /// .raw_header_adjoin("X-Teapot-Model", "Series 1") -/// .sized_body(Cursor::new("Brewing the best coffee!")) -/// .await; -/// -/// # }) +/// .sized_body(Cursor::new("Brewing the best coffee!")).await +/// .finalize(); +/// # }); /// ``` pub struct ResponseBuilder<'r> { response: Response<'r>, - pending_sized_body: Option>, - fut: Option> + Send + 'r>>>, } impl<'r> ResponseBuilder<'r> { @@ -192,8 +178,6 @@ impl<'r> ResponseBuilder<'r> { pub fn new(base: Response<'r>) -> ResponseBuilder<'r> { ResponseBuilder { response: base, - pending_sized_body: None, - fut: None, } } @@ -205,14 +189,9 @@ impl<'r> ResponseBuilder<'r> { /// use rocket::Response; /// use rocket::http::Status; /// - /// # rocket::async_test(async { - /// - /// # #[allow(unused_variables)] /// let response = Response::build() /// .status(Status::NotFound) - /// .await; - /// - /// # }) + /// .finalize(); /// ``` #[inline(always)] pub fn status(&mut self, status: Status) -> &mut ResponseBuilder<'r> { @@ -227,14 +206,13 @@ impl<'r> ResponseBuilder<'r> { /// /// ```rust /// use rocket::Response; + /// use rocket::http::Status; /// - /// # rocket::async_test(async { - /// - /// # #[allow(unused_variables)] /// let response = Response::build() /// .raw_status(699, "Alien Encounter") - /// .await; - /// # }) + /// .finalize(); + /// + /// assert_eq!(response.status(), Status::new(699, "Alien Encounter")); /// ``` #[inline(always)] pub fn raw_status(&mut self, code: u16, reason: &'static str) -> &mut ResponseBuilder<'r> { @@ -257,16 +235,12 @@ impl<'r> ResponseBuilder<'r> { /// use rocket::Response; /// use rocket::http::ContentType; /// - /// # rocket::async_test(async { - /// /// let response = Response::build() /// .header(ContentType::JSON) /// .header(ContentType::HTML) - /// .await; + /// .finalize(); /// /// assert_eq!(response.headers().get("Content-Type").count(), 1); - /// - /// # }) /// ``` #[inline(always)] pub fn header<'h: 'r, H>(&mut self, header: H) -> &mut ResponseBuilder<'r> @@ -292,16 +266,12 @@ impl<'r> ResponseBuilder<'r> { /// use rocket::http::Header; /// use rocket::http::hyper::header::ACCEPT; /// - /// # rocket::async_test(async { - /// /// let response = Response::build() /// .header_adjoin(Header::new(ACCEPT.as_str(), "application/json")) /// .header_adjoin(Header::new(ACCEPT.as_str(), "text/plain")) - /// .await; + /// .finalize(); /// /// assert_eq!(response.headers().get("Accept").count(), 2); - /// - /// # }) /// ``` #[inline(always)] pub fn header_adjoin<'h: 'r, H>(&mut self, header: H) -> &mut ResponseBuilder<'r> @@ -321,16 +291,12 @@ impl<'r> ResponseBuilder<'r> { /// ```rust /// use rocket::Response; /// - /// # rocket::async_test(async { - /// /// let response = Response::build() /// .raw_header("X-Custom", "first") /// .raw_header("X-Custom", "second") - /// .await; + /// .finalize(); /// /// assert_eq!(response.headers().get("X-Custom").count(), 1); - /// - /// # }) /// ``` #[inline(always)] pub fn raw_header<'a, 'b, N, V>(&mut self, name: N, value: V) -> &mut ResponseBuilder<'r> @@ -351,16 +317,12 @@ impl<'r> ResponseBuilder<'r> { /// ```rust /// use rocket::Response; /// - /// # rocket::async_test(async { - /// /// let response = Response::build() /// .raw_header_adjoin("X-Custom", "first") /// .raw_header_adjoin("X-Custom", "second") - /// .await; + /// .finalize(); /// /// assert_eq!(response.headers().get("X-Custom").count(), 2); - /// - /// # }) /// ``` #[inline(always)] pub fn raw_header_adjoin<'a, 'b, N, V>(&mut self, name: N, value: V) -> &mut ResponseBuilder<'r> @@ -375,24 +337,19 @@ impl<'r> ResponseBuilder<'r> { /// # Example /// /// ```rust + /// use std::io::Cursor; /// use rocket::Response; - /// use tokio::fs::File; - /// # use std::io; /// - /// # #[allow(dead_code)] - /// # async fn test() -> io::Result<()> { - /// # #[allow(unused_variables)] + /// # rocket::async_test(async { /// let response = Response::build() - /// .sized_body(File::open("body.txt").await?) - /// .await; - /// # Ok(()) - /// # } + /// .sized_body(Cursor::new("Hello, world!")).await + /// .finalize(); + /// # }) /// ``` - #[inline(always)] - pub fn sized_body(&mut self, body: B) -> &mut ResponseBuilder<'r> + pub async fn sized_body(&mut self, body: B) -> &mut ResponseBuilder<'r> where B: AsyncRead + AsyncSeek + Send + Unpin + 'r { - self.pending_sized_body = Some(Box::new(body)); + self.response.set_sized_body(body).await; self } @@ -401,25 +358,18 @@ impl<'r> ResponseBuilder<'r> { /// # Example /// /// ```rust + /// use std::io::Cursor; /// use rocket::Response; - /// use tokio::fs::File; - /// # use std::io; /// - /// # #[allow(dead_code)] - /// # async fn test() -> io::Result<()> { - /// # #[allow(unused_variables)] /// let response = Response::build() - /// .streamed_body(File::open("body.txt").await?) - /// .await; - /// # Ok(()) - /// # } + /// .streamed_body(Cursor::new("Hello, world!")) + /// .finalize(); /// ``` #[inline(always)] pub fn streamed_body(&mut self, body: B) -> &mut ResponseBuilder<'r> where B: AsyncRead + Send + 'r { self.response.set_streamed_body(body); - self.pending_sized_body = None; self } @@ -433,21 +383,18 @@ impl<'r> ResponseBuilder<'r> { /// use tokio::fs::File; /// # use std::io; /// - /// # #[allow(dead_code)] - /// # async fn test() -> io::Result<()> { - /// # #[allow(unused_variables)] + /// # async fn test<'r>() -> io::Result> { /// let response = Response::build() /// .chunked_body(File::open("body.txt").await?, 8096) - /// .await; - /// # Ok(()) + /// .ok(); + /// # response /// # } /// ``` #[inline(always)] - pub fn chunked_body(&mut self, body: B, chunk_size: u64) - -> &mut ResponseBuilder<'r> + pub fn chunked_body(&mut self, body: B, chunk_size: u64) -> &mut ResponseBuilder<'r> + where B: AsyncRead + Send + 'r { self.response.set_chunked_body(body, chunk_size); - self.pending_sized_body = None; self } @@ -462,20 +409,16 @@ impl<'r> ResponseBuilder<'r> { /// use rocket::response::{Response, Body}; /// /// # rocket::async_test(async { - /// - /// # #[allow(unused_variables)] /// let response = Response::build() /// .raw_body(Body::Sized(Cursor::new("Hello!"), 6)) - /// .await; - /// + /// .finalize(); /// # }) /// ``` #[inline(always)] - pub fn raw_body(&mut self, body: Body) - -> &mut ResponseBuilder<'r> + pub fn raw_body(&mut self, body: Body) -> &mut ResponseBuilder<'r> + where T: AsyncRead + Send + Unpin + 'r { self.response.set_raw_body(body); - self.pending_sized_body = None; self } @@ -491,40 +434,29 @@ impl<'r> ResponseBuilder<'r> { /// use rocket::Response; /// use rocket::http::{Status, ContentType}; /// - /// # rocket::async_test(async { - /// /// let base = Response::build() /// .status(Status::NotFound) /// .header(ContentType::HTML) /// .raw_header("X-Custom", "value 1") - /// .await; + /// .finalize(); /// /// let response = Response::build() /// .status(Status::ImATeapot) /// .raw_header("X-Custom", "value 2") /// .raw_header_adjoin("X-Custom", "value 3") /// .merge(base) - /// .await; + /// .finalize(); /// /// assert_eq!(response.status(), Status::NotFound); /// - /// # { /// let ctype: Vec<_> = response.headers().get("Content-Type").collect(); /// assert_eq!(ctype, vec![ContentType::HTML.to_string()]); - /// # } /// - /// # { /// let custom_values: Vec<_> = response.headers().get("X-Custom").collect(); /// assert_eq!(custom_values, vec!["value 1"]); - /// # } - /// - /// # }); /// ``` #[inline(always)] - pub fn merge(&mut self, mut other: Response<'r>) -> &mut ResponseBuilder<'r> { - if other.body().is_some() { - self.pending_sized_body = None; - } + pub fn merge(&mut self, other: Response<'r>) -> &mut ResponseBuilder<'r> { self.response.merge(other); self } @@ -542,34 +474,26 @@ impl<'r> ResponseBuilder<'r> { /// use rocket::Response; /// use rocket::http::{Status, ContentType}; /// - /// # rocket::async_test(async { - /// /// let other = Response::build() /// .status(Status::NotFound) /// .header(ContentType::HTML) /// .raw_header("X-Custom", "value 1") - /// .await; + /// .finalize(); /// /// let response = Response::build() /// .status(Status::ImATeapot) /// .raw_header("X-Custom", "value 2") /// .raw_header_adjoin("X-Custom", "value 3") /// .join(other) - /// .await; + /// .finalize(); /// /// assert_eq!(response.status(), Status::ImATeapot); /// - /// # { /// let ctype: Vec<_> = response.headers().get("Content-Type").collect(); /// assert_eq!(ctype, vec![ContentType::HTML.to_string()]); - /// # } /// - /// # { /// let custom_values: Vec<_> = response.headers().get("X-Custom").collect(); /// assert_eq!(custom_values, vec!["value 2", "value 3", "value 1"]); - /// # } - /// - /// # }) /// ``` #[inline(always)] pub fn join(&mut self, other: Response<'r>) -> &mut ResponseBuilder<'r> { @@ -577,48 +501,47 @@ impl<'r> ResponseBuilder<'r> { self } - /// Retrieve the built `Response` wrapped in `Ok`. + /// Return the `Response` structure that was being built by this builder. + /// After calling this method, `self` is cleared and must be rebuilt as if + /// from `new()`. + /// + /// # Example + /// + /// ```rust + /// use std::io::Cursor; + /// + /// use rocket::Response; + /// use rocket::http::Status; + /// + /// # rocket::async_test(async { + /// let response = Response::build() + /// .status(Status::ImATeapot) + /// .sized_body(Cursor::new("Brewing the best coffee!")).await + /// .raw_header("X-Custom", "value 2") + /// .finalize(); + /// # }) + /// ``` + pub fn finalize(&mut self) -> Response<'r> { + std::mem::replace(&mut self.response, Response::new()) + } + + /// Retrieve the built `Response` wrapped in `Ok`. After calling this + /// method, `self` is cleared and must be rebuilt as if from `new()`. /// /// # Example /// /// ```rust /// use rocket::Response; /// - /// # rocket::async_test(async { - /// /// let response: Result = Response::build() /// // build the response - /// .ok() - /// .await; + /// .ok(); /// /// assert!(response.is_ok()); - /// - /// # }) /// ``` #[inline(always)] - pub async fn ok(&mut self) -> Result, E> { - Ok(self.await) - } -} - -impl<'r> Future for ResponseBuilder<'r> { - type Output = Response<'r>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - if this.fut.is_none() { - let mut response = std::mem::replace(&mut this.response, Response::new()); - let pending_sized_body = this.pending_sized_body.take(); - this.fut = Some(Box::pin(async { - if let Some(sb) = pending_sized_body { - // TODO: Avoid double boxing (Pin>>>>) - response.set_sized_body(sb).await; - } - response - })); - } - - this.fut.as_mut().expect("this.fut.is_none() checked and assigned Some").as_mut().poll(cx) + pub fn ok(&mut self) -> Result, E> { + Ok(self.finalize()) } } @@ -1139,7 +1062,8 @@ impl<'r> Response<'r> { /// ``` #[inline(always)] pub fn set_chunked_body(&mut self, body: B, chunk_size: u64) - where B: AsyncRead + Send + 'r { + where B: AsyncRead + Send + 'r + { self.body = Some(Body::Chunked(Box::pin(body), chunk_size)); } @@ -1181,34 +1105,26 @@ impl<'r> Response<'r> { /// use rocket::Response; /// use rocket::http::{Status, ContentType}; /// - /// # rocket::async_test(async { - /// /// let base = Response::build() /// .status(Status::NotFound) /// .header(ContentType::HTML) /// .raw_header("X-Custom", "value 1") - /// .await; + /// .finalize(); /// /// let response = Response::build() /// .status(Status::ImATeapot) /// .raw_header("X-Custom", "value 2") /// .raw_header_adjoin("X-Custom", "value 3") /// .merge(base) - /// .await; + /// .finalize(); /// /// assert_eq!(response.status(), Status::NotFound); /// - /// # { /// let ctype: Vec<_> = response.headers().get("Content-Type").collect(); /// assert_eq!(ctype, vec![ContentType::HTML.to_string()]); - /// # } /// - /// # { /// let custom_values: Vec<_> = response.headers().get("X-Custom").collect(); /// assert_eq!(custom_values, vec!["value 1"]); - /// # } - /// - /// # }) /// ``` pub fn merge(&mut self, other: Response<'r>) { if let Some(status) = other.status { @@ -1234,34 +1150,26 @@ impl<'r> Response<'r> { /// use rocket::Response; /// use rocket::http::{Status, ContentType}; /// - /// # rocket::async_test(async { - /// /// let other = Response::build() /// .status(Status::NotFound) /// .header(ContentType::HTML) /// .raw_header("X-Custom", "value 1") - /// .await; + /// .finalize(); /// /// let response = Response::build() /// .status(Status::ImATeapot) /// .raw_header("X-Custom", "value 2") /// .raw_header_adjoin("X-Custom", "value 3") /// .join(other) - /// .await; + /// .finalize(); /// /// assert_eq!(response.status(), Status::ImATeapot); /// - /// # { /// let ctype: Vec<_> = response.headers().get("Content-Type").collect(); /// assert_eq!(ctype, vec![ContentType::HTML.to_string()]); - /// # } /// - /// # { /// let custom_values: Vec<_> = response.headers().get("X-Custom").collect(); /// assert_eq!(custom_values, vec!["value 2", "value 3", "value 1"]); - /// # } - /// - /// # }) /// ``` pub fn join(&mut self, other: Response<'r>) { if self.status.is_none() { @@ -1295,11 +1203,10 @@ impl fmt::Debug for Response<'_> { use crate::request::Request; +#[crate::async_trait] impl<'r> Responder<'r> for Response<'r> { /// This is the identity implementation. It simply returns `Ok(self)`. - fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> { - Box::pin(async { - Ok(self) - }) + async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { + Ok(self) } } diff --git a/core/lib/src/response/status.rs b/core/lib/src/response/status.rs index 41c85a56..50aaf21a 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, ResultFuture}; +use crate::response::{self, Responder, Response}; use crate::http::Status; /// Sets the status of the response to 201 (Created). @@ -160,23 +160,21 @@ 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. +#[crate::async_trait] 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?); - } + async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { + 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() - .await - }) + response.status(Status::Created) + .raw_header("Location", self.0) + .ok() } } @@ -209,16 +207,15 @@ 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. +#[crate::async_trait] 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?); - } + async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { + let mut build = Response::build(); + if let Some(responder) = self.0 { + build.merge(responder.respond_to(req).await?); + } - build.status(Status::Accepted).ok().await - }) + build.status(Status::Accepted).ok() } } @@ -276,16 +273,15 @@ 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. +#[crate::async_trait] 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?); - } + async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { + let mut build = Response::build(); + if let Some(responder) = self.0 { + build.merge(responder.respond_to(req).await?); + } - build.status(Status::BadRequest).ok().await - }) + build.status(Status::BadRequest).ok() } } @@ -385,14 +381,12 @@ 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. +#[crate::async_trait] 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() - .await - }) + async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { + Response::build_from(self.0.respond_to(req).await?) + .status(Status::NotFound) + .ok() } } @@ -453,14 +447,12 @@ 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. +#[crate::async_trait] 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() - .await - }) + async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { + 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 3f1ddd88..cfec48cb 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 tokio::io::AsyncRead; use crate::request::Request; -use crate::response::{Response, Responder, ResultFuture, DEFAULT_CHUNK_SIZE}; +use crate::response::{self, Response, Responder, DEFAULT_CHUNK_SIZE}; /// Streams a response to a client from an arbitrary `AsyncRead`er type. /// @@ -66,10 +66,9 @@ impl From for Stream { /// If reading from the input stream fails at any point during the response, the /// response is abandoned, and the response ends abruptly. An error is printed /// to the console with an indication of what went wrong. +#[crate::async_trait] impl<'r, T: AsyncRead + Send + 'r> Responder<'r> for Stream { - fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> { - Box::pin(async { - Response::build().chunked_body(self.0, self.1).ok().await - }) + async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { + Response::build().chunked_body(self.0, self.1).ok() } } diff --git a/core/lib/src/router/mod.rs b/core/lib/src/router/mod.rs index 0ab30640..9810500d 100644 --- a/core/lib/src/router/mod.rs +++ b/core/lib/src/router/mod.rs @@ -14,7 +14,9 @@ use crate::http::Method; type Selector = Method; // A handler to use when one is needed temporarily. -pub(crate) fn dummy_handler<'r>(r: &'r Request<'_>, _: crate::Data) -> BoxFuture<'r, crate::handler::Outcome<'r>> { +pub(crate) fn dummy_handler<'r>( + r: &'r Request<'_>, _: crate::Data +) -> BoxFuture<'r, crate::handler::Outcome<'r>> { crate::Outcome::from(r, ()) } diff --git a/core/lib/tests/conditionally-set-server-header-996.rs b/core/lib/tests/conditionally-set-server-header-996.rs index af160970..6a19a68e 100644 --- a/core/lib/tests/conditionally-set-server-header-996.rs +++ b/core/lib/tests/conditionally-set-server-header-996.rs @@ -9,7 +9,7 @@ use rocket::http::Header; async fn do_not_overwrite() -> Response<'static> { Response::build() .header(Header::new("Server", "Test")) - .await + .finalize() } #[get("/use_default")] diff --git a/core/lib/tests/responder_lifetime-issue-345.rs b/core/lib/tests/responder_lifetime-issue-345.rs index 73dbc8d2..8ef87716 100644 --- a/core/lib/tests/responder_lifetime-issue-345.rs +++ b/core/lib/tests/responder_lifetime-issue-345.rs @@ -3,8 +3,9 @@ #[macro_use] extern crate rocket; -use rocket::State; -use rocket::response::{self, Responder}; +use rocket::{Request, State}; +use rocket::futures::future::BoxFuture; +use rocket::response::{Responder, Result}; struct SomeState; @@ -14,7 +15,9 @@ pub struct CustomResponder<'r, R> { } impl<'r, R: Responder<'r>> Responder<'r> for CustomResponder<'r, R> { - fn respond_to(self, _: &rocket::Request) -> response::ResultFuture<'r> { + fn respond_to<'a, 'x>(self, _: &'r Request<'a>) -> BoxFuture<'x, Result<'r>> + where 'a: 'x, 'r: 'x, Self: 'x + { unimplemented!() } } diff --git a/examples/manual_routes/src/main.rs b/examples/manual_routes/src/main.rs index 31c1c209..6296a660 100644 --- a/examples/manual_routes/src/main.rs +++ b/examples/manual_routes/src/main.rs @@ -5,14 +5,14 @@ mod tests; use std::env; -use tokio::fs::File; - use rocket::{Request, Handler, Route, Data, Catcher, try_outcome}; use rocket::http::{Status, RawStr}; use rocket::response::{self, Responder, status::Custom}; use rocket::handler::{Outcome, HandlerFuture}; use rocket::outcome::IntoOutcome; use rocket::http::Method::*; +use rocket::futures::future::BoxFuture; +use rocket::tokio::fs::File; fn forward<'r>(_req: &'r Request, data: Data) -> HandlerFuture<'r> { Box::pin(async move { Outcome::forward(data) }) @@ -68,7 +68,7 @@ fn get_upload<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> { Outcome::from(req, std::fs::File::open(env::temp_dir().join("upload.txt")).ok()) } -fn not_found_handler<'r>(req: &'r Request) -> response::ResultFuture<'r> { +fn not_found_handler<'r>(req: &'r Request) -> BoxFuture<'r, response::Result<'r>> { let res = Custom(Status::NotFound, format!("Couldn't find: {}", req.uri())); res.respond_to(req) }