Use 'async_trait' for 'Responder'.

Also:

  * Remove 'response::ResultFuture'.
  * Re-export 'tokio' and 'futures' from the crate root.
  * Make 'ResponseBuilder::sized_body()' and 'async fn'.
  * Remove the 'Future' implementation for 'ResponseBuilder'.
  * Add 'ResponseBuilder::finalize()' for finalizing the builder.
This commit is contained in:
Sergio Benitez 2020-02-03 00:30:22 -08:00
parent 58f81d392e
commit c0c6c79a7f
24 changed files with 342 additions and 405 deletions

View File

@ -19,6 +19,7 @@ use std::io;
use std::iter::FromIterator; use std::iter::FromIterator;
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
use rocket::futures::future::BoxFuture;
use rocket::request::Request; use rocket::request::Request;
use rocket::outcome::Outcome::*; use rocket::outcome::Outcome::*;
@ -169,15 +170,20 @@ impl<'a, T: Deserialize<'a>> FromData<'a> for Json<T> {
/// JSON and a fixed-size body with the serialized value. If serialization /// JSON and a fixed-size body with the serialized value. If serialization
/// fails, an `Err` of `Status::InternalServerError` is returned. /// fails, an `Err` of `Status::InternalServerError` is returned.
impl<'r, T: Serialize> Responder<'r> for Json<T> { impl<'r, T: Serialize> Responder<'r> for Json<T> {
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>>
match serde_json::to_string(&self.0) { where 'a: 'x, 'r: 'x, Self: 'x
Ok(string) => Box::pin(async move { Ok(content::Json(string).respond_to(req).await.unwrap()) }), {
Err(e) => Box::pin(async move { 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); error_!("JSON failed to serialize: {:?}", e);
Err(Status::InternalServerError) Err(Status::InternalServerError)
})
} }
} }
})
}
} }
impl<T> Deref for Json<T> { impl<T> Deref for Json<T> {
@ -291,10 +297,10 @@ impl<T> FromIterator<T> for JsonValue where serde_json::Value: FromIterator<T> {
/// Serializes the value into JSON. Returns a response with Content-Type JSON /// Serializes the value into JSON. Returns a response with Content-Type JSON
/// and a fixed-size body with the serialized value. /// and a fixed-size body with the serialized value.
#[rocket::async_trait]
impl<'r> Responder<'r> for JsonValue { impl<'r> Responder<'r> for JsonValue {
#[inline] async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> { content::Json(self.0.to_string()).respond_to(req).await
content::Json(self.0.to_string()).respond_to(req)
} }
} }

View File

@ -23,6 +23,7 @@ use rocket::outcome::Outcome::*;
use rocket::data::{Data, FromData, FromDataFuture, Transform::*, TransformFuture, Transformed}; use rocket::data::{Data, FromData, FromDataFuture, Transform::*, TransformFuture, Transformed};
use rocket::http::Status; use rocket::http::Status;
use rocket::response::{self, content, Responder}; use rocket::response::{self, content, Responder};
use rocket::futures::future::BoxFuture;
use serde::Serialize; use serde::Serialize;
use serde::de::Deserialize; use serde::de::Deserialize;
@ -157,13 +158,15 @@ impl<'a, T: Deserialize<'a>> FromData<'a> for MsgPack<T> {
/// Content-Type `MsgPack` and a fixed-size body with the serialization. If /// Content-Type `MsgPack` and a fixed-size body with the serialization. If
/// serialization fails, an `Err` of `Status::InternalServerError` is returned. /// serialization fails, an `Err` of `Status::InternalServerError` is returned.
impl<'r, T: Serialize> Responder<'r> for MsgPack<T> { impl<'r, T: Serialize> Responder<'r> for MsgPack<T> {
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) { match rmp_serde::to_vec(&self.0) {
Ok(buf) => content::MsgPack(buf).respond_to(req), Ok(buf) => content::MsgPack(buf).respond_to(req),
Err(e) => Box::pin(async move { Err(e) => {
error_!("MsgPack failed to serialize: {:?}", e); error_!("MsgPack failed to serialize: {:?}", e);
Err(Status::InternalServerError) Box::pin(async { Err(Status::InternalServerError) })
}), }
} }
} }
} }

View File

@ -383,9 +383,9 @@ impl Template {
/// Returns a response with the Content-Type derived from the template's /// Returns a response with the Content-Type derived from the template's
/// extension and a fixed-size body containing the rendered template. If /// extension and a fixed-size body containing the rendered template. If
/// rendering fails, an `Err` of `Status::InternalServerError` is returned. /// rendering fails, an `Err` of `Status::InternalServerError` is returned.
#[rocket::async_trait]
impl<'r> Responder<'r> for Template { impl<'r> Responder<'r> for Template {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> { async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
Box::pin(async move {
let (render, content_type) = { let (render, content_type) = {
let ctxt = req.guard::<State<'_, ContextManager>>().await.succeeded().ok_or_else(|| { let ctxt = req.guard::<State<'_, ContextManager>>().await.succeeded().ok_or_else(|| {
error_!("Uninitialized template context: missing fairing."); error_!("Uninitialized template context: missing fairing.");
@ -398,6 +398,5 @@ impl<'r> Responder<'r> for Template {
}; };
Content(content_type, render).respond_to(req).await Content(content_type, render).respond_to(req).await
})
} }
} }

View File

@ -103,7 +103,6 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
.status(#status) .status(#status)
.merge(__response) .merge(__response)
.ok() .ok()
.await
}) })
} }

View File

@ -30,10 +30,12 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
false => Ok(()) false => Ok(())
}) })
.function(|_, inner| quote! { .function(|_, inner| quote! {
fn respond_to( fn respond_to<'__i, '__x>(
self, self,
__req: &'__r ::rocket::Request __req: &'__r ::rocket::request::Request<'__i>
) -> ::rocket::response::ResultFuture<'__r> { ) -> ::rocket::futures::future::BoxFuture<'__x, ::rocket::response::Result<'__r>>
where '__i: '__x, '__r: '__x, Self: '__x
{
#inner #inner
} }
}) })

View File

@ -28,8 +28,7 @@ async fn responder_foo() {
let req = local_req.inner(); let req = local_req.inner();
let mut response = Foo::First("hello".into()) let mut response = Foo::First("hello".into())
.respond_to(req) .respond_to(req).await
.await
.expect("response okay"); .expect("response okay");
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
@ -37,8 +36,7 @@ async fn responder_foo() {
assert_eq!(response.body_string().await, Some("hello".into())); assert_eq!(response.body_string().await, Some("hello".into()));
let mut response = Foo::Second("just a test".into()) let mut response = Foo::Second("just a test".into())
.respond_to(req) .respond_to(req).await
.await
.expect("response okay"); .expect("response okay");
assert_eq!(response.status(), Status::InternalServerError); 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())); assert_eq!(response.body_string().await, Some("just a test".into()));
let mut response = Foo::Third { responder: "well, hi", ct: ContentType::JSON } let mut response = Foo::Third { responder: "well, hi", ct: ContentType::JSON }
.respond_to(req) .respond_to(req).await
.await
.expect("response okay"); .expect("response okay");
assert_eq!(response.status(), Status::NotFound); assert_eq!(response.status(), Status::NotFound);
@ -55,8 +52,7 @@ async fn responder_foo() {
assert_eq!(response.body_string().await, Some("well, hi".into())); assert_eq!(response.body_string().await, Some("well, hi".into()));
let mut response = Foo::Fourth { string: "goodbye", ct: ContentType::JSON } let mut response = Foo::Fourth { string: "goodbye", ct: ContentType::JSON }
.respond_to(req) .respond_to(req).await
.await
.expect("response okay"); .expect("response okay");
assert_eq!(response.status(), Status::raw(105)); assert_eq!(response.status(), Status::raw(105));
@ -106,8 +102,7 @@ async fn responder_baz() {
let req = local_req.inner(); let req = local_req.inner();
let mut response = Baz { responder: "just a custom" } let mut response = Baz { responder: "just a custom" }
.respond_to(req) .respond_to(req).await
.await
.expect("response okay"); .expect("response okay");
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);

View File

@ -158,10 +158,7 @@ impl PartialEq for AcceptParams {
/// use rocket::http::Accept; /// use rocket::http::Accept;
/// use rocket::response::Response; /// use rocket::response::Response;
/// ///
/// # #[allow(unused_variables)] /// let response = Response::build().header(Accept::JSON).finalize();
/// # rocket::async_test(async {
/// let response = Response::build().header(Accept::JSON).await;
/// # })
/// ``` /// ```
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Accept(pub(crate) AcceptParams); pub struct Accept(pub(crate) AcceptParams);

View File

@ -38,10 +38,7 @@ use crate::ext::IntoCollection;
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// use rocket::response::Response; /// use rocket::response::Response;
/// ///
/// # #[allow(unused_variables)] /// let response = Response::build().header(ContentType::HTML).finalize();
/// # rocket::async_test(async {
/// let response = Response::build().header(ContentType::HTML).await;
/// # })
/// ``` /// ```
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct ContentType(pub MediaType); pub struct ContentType(pub MediaType);

View File

@ -28,6 +28,7 @@ ctrl_c_shutdown = ["tokio/signal"]
rocket_codegen = { version = "0.5.0-dev", path = "../codegen" } rocket_codegen = { version = "0.5.0-dev", path = "../codegen" }
rocket_http = { version = "0.5.0-dev", path = "../http" } rocket_http = { version = "0.5.0-dev", path = "../http" }
futures-util = "0.3.0" futures-util = "0.3.0"
futures = "0.3.0"
tokio = { version = "0.2.9", features = ["fs", "io-std", "io-util", "rt-threaded", "sync"] } tokio = { version = "0.2.9", features = ["fs", "io-std", "io-util", "rt-threaded", "sync"] }
yansi = "0.5" yansi = "0.5"
log = { version = "0.4", features = ["std"] } log = { version = "0.4", features = ["std"] }

View File

@ -99,6 +99,9 @@ pub use async_trait::*;
#[macro_use] extern crate log; #[macro_use] extern crate log;
#[macro_use] extern crate pear; #[macro_use] extern crate pear;
pub use futures;
pub use tokio;
#[doc(hidden)] #[macro_use] pub mod logger; #[doc(hidden)] #[macro_use] pub mod logger;
#[macro_use] pub mod outcome; #[macro_use] pub mod outcome;
pub mod local; pub mod local;

View File

@ -23,7 +23,7 @@
//! ``` //! ```
use crate::request::Request; use crate::request::Request;
use crate::response::{Response, Responder, ResultFuture}; use crate::response::{self, Response, Responder};
use crate::http::ContentType; use crate::http::ContentType;
/// Sets the Content-Type of a `Responder` to a chosen value. /// Sets the Content-Type of a `Responder` to a chosen value.
@ -46,21 +46,21 @@ 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.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + '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: &'r Request<'_>) -> ResultFuture<'r> { async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
Box::pin(async move {
Response::build() Response::build()
.merge(self.1.respond_to(req).await?) .merge(self.1.respond_to(req).await?)
.header(self.0) .header(self.0)
.ok() .ok()
.await
})
} }
} }
macro_rules! ctrs { macro_rules! ctrs {
($($name:ident: $ct:ident, $name_str:expr, $ct_str:expr),+) => { ($($name:ident: $ct:ident, $name_str:expr, $ct_str:expr),+) => {
use futures_util::future::BoxFuture;
$( $(
#[doc="Override the `Content-Type` of the response to <b>"] #[doc="Override the `Content-Type` of the response to <b>"]
#[doc=$name_str] #[doc=$name_str]
@ -75,7 +75,9 @@ 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> + Send + 'r> Responder<'r> for $name<R> { impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for $name<R> {
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) Content(ContentType::$ct, self.0).respond_to(req)
} }
} }

View File

@ -63,12 +63,11 @@ impl<E> From<E> for Debug<E> {
} }
} }
#[crate::async_trait]
impl<'r, E: std::fmt::Debug + Send + 'r> Responder<'r> for Debug<E> { impl<'r, E: std::fmt::Debug + Send + 'r> Responder<'r> for Debug<E> {
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'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().await 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::{Responder, ResultFuture}; use crate::response::{self, Responder};
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,11 +198,12 @@ 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`.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Flash<R> { impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Flash<R> {
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); 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).await
} }
} }

View File

@ -46,8 +46,5 @@ pub use self::stream::Stream;
pub use self::debug::Debug; pub use self::debug::Debug;
#[doc(inline)] pub use self::content::Content; #[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<self::Response<'r>, crate::http::Status>; pub type Result<'r> = std::result::Result<self::Response<'r>, 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>>;

View File

@ -78,9 +78,9 @@ 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.
#[crate::async_trait]
impl<'r> Responder<'r> for NamedFile { impl<'r> Responder<'r> for NamedFile {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> { async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
Box::pin(async move {
let mut response = self.1.respond_to(req).await?; 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()) {
@ -89,7 +89,6 @@ impl<'r> Responder<'r> 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, ResultFuture}; use crate::response::{self, Response, Responder};
use crate::http::uri::Uri; use crate::http::uri::Uri;
use crate::http::Status; use crate::http::Status;
@ -147,19 +147,17 @@ 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.
#[crate::async_trait]
impl<'r> Responder<'r> for Redirect { impl<'r> Responder<'r> for Redirect {
fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'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)
.raw_header("Location", uri.to_string()) .raw_header("Location", uri.to_string())
.ok() .ok()
.await
} else { } else {
error!("Invalid URI used for redirect."); error!("Invalid URI used for redirect.");
Err(Status::InternalServerError) Err(Status::InternalServerError)
} }
})
} }
} }

View File

@ -1,8 +1,8 @@
use std::fs::File; use std::fs::File;
use std::io::Cursor; use std::io::Cursor;
use futures_util::future::ready;
use tokio::io::BufReader; use tokio::io::BufReader;
use futures_util::future::BoxFuture;
use crate::http::{Status, ContentType, StatusClass}; use crate::http::{Status, ContentType, StatusClass};
use crate::response::{self, Response, Body}; 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 /// regardless of the incoming request. Thus, knowing the type is sufficient to
/// fully determine its functionality. /// 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<R> { inner: R };
///
/// impl<'r, R: Responder<'r>> Responder<'r> for MyType<R> {
/// 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 /// # Example
/// ///
/// Say that you have a custom type, `Person`: /// Say that you have a custom type, `Person`:
/// ///
/// ```rust /// ```rust
///
/// # #[allow(dead_code)]
/// struct Person { /// struct Person {
/// name: String, /// name: String,
/// age: u16 /// 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 /// You'd like to use `Person` as a `Responder` so that you can return a
/// `Person` directly from a handler: /// `Person` directly from a handler:
/// ///
/// ```rust,ignore /// ```rust
/// # #[macro_use] extern crate rocket;
/// # type Person = String;
/// #[get("/person/<id>")] /// #[get("/person/<id>")]
/// fn person(id: usize) -> Option<Person> { /// fn person(id: usize) -> Option<Person> {
/// # /*
/// Person::from_id(id) /// Person::from_id(id)
/// # */ None
/// } /// }
/// # fn main() {}
/// ``` /// ```
/// ///
/// You want the `Person` responder to set two header fields: `X-Person-Name` /// 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::response::{self, Response, Responder};
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// ///
/// #[rocket::async_trait]
/// impl<'r> Responder<'r> for Person { /// impl<'r> Responder<'r> for Person {
/// fn respond_to(self, _: &'r Request) -> response::ResultFuture<'r> { /// async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
/// Box::pin(async move { /// let person_string = format!("{}:{}", self.name, self.age);
/// Response::build() /// Response::build()
/// .sized_body(Cursor::new(format!("{}:{}", self.name, self.age))) /// .sized_body(Cursor::new(person_string)).await
/// .raw_header("X-Person-Name", self.name) /// .raw_header("X-Person-Name", self.name)
/// .raw_header("X-Person-Age", self.age.to_string()) /// .raw_header("X-Person-Age", self.age.to_string())
/// .header(ContentType::new("application", "x-person")) /// .header(ContentType::new("application", "x-person"))
/// .ok() /// .ok()
/// .await
/// })
/// } /// }
/// } /// }
/// # /// #
@ -172,6 +213,7 @@ use crate::request::Request;
/// # fn person() -> Person { Person { name: "a".to_string(), age: 20 } } /// # fn person() -> Person { Person { name: "a".to_string(), age: 20 } }
/// # fn main() { } /// # fn main() { }
/// ``` /// ```
#[crate::async_trait]
pub trait Responder<'r> { pub trait Responder<'r> {
/// Returns `Ok` if a `Response` could be generated successfully. Otherwise, /// Returns `Ok` if a `Response` could be generated successfully. Otherwise,
/// returns an `Err` with a failing `Status`. /// 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 /// 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: &'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 /// 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`.
#[crate::async_trait]
impl<'r> Responder<'r> for &'r str { impl<'r> Responder<'r> for &'r str {
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'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)).await
.ok() .ok()
.await
})
} }
} }
/// 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`.
#[crate::async_trait]
impl<'r> Responder<'r> for String { impl<'r> Responder<'r> for String {
fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'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)).await
.ok() .ok()
.await
})
} }
} }
/// 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`.
#[crate::async_trait]
impl<'r> Responder<'r> for &'r [u8] { impl<'r> Responder<'r> for &'r [u8] {
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'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)).await
.ok() .ok()
.await
})
} }
} }
/// 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`.
#[crate::async_trait]
impl<'r> Responder<'r> for Vec<u8> { impl<'r> Responder<'r> for Vec<u8> {
fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'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)).await
.ok() .ok()
.await
})
} }
} }
/// 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`.
#[crate::async_trait]
impl<'r> Responder<'r> for File { impl<'r> Responder<'r> for File {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> { async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
tokio::fs::File::from(self).respond_to(req) tokio::fs::File::from(self).respond_to(req).await
} }
} }
/// 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`.
#[crate::async_trait]
impl<'r> Responder<'r> for tokio::fs::File { impl<'r> Responder<'r> for tokio::fs::File {
fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Box::pin(async move {
let metadata = self.metadata().await; let metadata = self.metadata().await;
let stream = BufReader::new(self); let stream = BufReader::new(self);
match metadata { match metadata {
Ok(md) => Response::build().raw_body(Body::Sized(stream, md.len())).ok().await, Ok(md) => Response::build().raw_body(Body::Sized(stream, md.len())).ok(),
Err(_) => Response::build().streamed_body(stream).ok().await Err(_) => Response::build().streamed_body(stream).ok()
} }
})
} }
} }
/// Returns an empty, default `Response`. Always returns `Ok`. /// Returns an empty, default `Response`. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for () { impl<'r> Responder<'r> for () {
fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
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>> Responder<'r> for Option<R> {
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 { match self {
Some(r) => r.respond_to(req), Some(r) => r.respond_to(req),
None => { None => {
warn_!("Response was `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`. /// `Err`.
impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result<R, E> { impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result<R, E> {
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 { match self {
Ok(responder) => responder.respond_to(req), Ok(responder) => responder.respond_to(req),
Err(responder) => responder.respond_to(req), Err(responder) => responder.respond_to(req),
@ -312,16 +349,16 @@ 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.
#[crate::async_trait]
impl<'r> Responder<'r> for Status { impl<'r> Responder<'r> for Status {
fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'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 => {
Response::build().status(self).ok().await Response::build().status(self).ok()
} }
StatusClass::Informational if self.code == 100 => { StatusClass::Informational if self.code == 100 => {
Response::build().status(self).ok().await Response::build().status(self).ok()
} }
_ => { _ => {
error_!("Invalid status used as responder: {}.", self); error_!("Invalid status used as responder: {}.", self);
@ -329,6 +366,5 @@ impl<'r> Responder<'r> for Status {
Err(Status::InternalServerError) Err(Status::InternalServerError)
} }
} }
})
} }
} }

View File

@ -1,12 +1,10 @@
use std::{io, fmt, str}; use std::{io, fmt, str};
use std::borrow::Cow; use std::borrow::Cow;
use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt}; 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}; use crate::http::{Header, HeaderMap, Status, ContentType, Cookie};
/// The default size, in bytes, of a chunk for streamed responses. /// The default size, in bytes, of a chunk for streamed responses.
@ -100,12 +98,6 @@ impl<T> fmt::Debug for Body<T> {
} }
} }
/// Internal workaround for `Box<dyn AsyncRead + AsyncSeek>` not being allowed.
///
/// https://github.com/rust-lang/rfcs/issues/2035
trait AsyncReadAsyncSeek: AsyncRead + AsyncSeek + Unpin + Send {}
impl<T: AsyncRead + AsyncSeek + Unpin + Send> AsyncReadAsyncSeek for T {}
/// Type for easily building `Response`s. /// Type for easily building `Response`s.
/// ///
/// Building a [`Response`] can be a low-level ordeal; this structure presents a /// Building a [`Response`] can be a low-level ordeal; this structure presents a
@ -119,9 +111,8 @@ impl<T: AsyncRead + AsyncSeek + Unpin + Send> AsyncReadAsyncSeek for T {}
/// with field(s) modified in the `Responder` being built. These method calls /// with field(s) modified in the `Responder` being built. These method calls
/// can be chained: `build.a().b()`. /// can be chained: `build.a().b()`.
/// ///
/// To finish building and retrieve the built `Response`, `.await` the builder. /// To finish building and retrieve the built `Response`, use the
/// The [`ok()` method](#method.ok) is also provided as a convenience /// [`finalize()`](#method.finalize) or [`ok()`](#method.ok) methods.
/// for `Responder` implementations.
/// ///
/// ## Headers /// ## Headers
/// ///
@ -157,23 +148,18 @@ impl<T: AsyncRead + AsyncSeek + Unpin + Send> AsyncReadAsyncSeek for T {}
/// use rocket::http::{Status, ContentType}; /// use rocket::http::{Status, ContentType};
/// ///
/// # rocket::async_test(async { /// # rocket::async_test(async {
///
/// # #[allow(unused_variables)]
/// let response = Response::build() /// let response = Response::build()
/// .status(Status::ImATeapot) /// .status(Status::ImATeapot)
/// .header(ContentType::Plain) /// .header(ContentType::Plain)
/// .raw_header("X-Teapot-Make", "Rocket") /// .raw_header("X-Teapot-Make", "Rocket")
/// .raw_header("X-Teapot-Model", "Utopia") /// .raw_header("X-Teapot-Model", "Utopia")
/// .raw_header_adjoin("X-Teapot-Model", "Series 1") /// .raw_header_adjoin("X-Teapot-Model", "Series 1")
/// .sized_body(Cursor::new("Brewing the best coffee!")) /// .sized_body(Cursor::new("Brewing the best coffee!")).await
/// .await; /// .finalize();
/// /// # });
/// # })
/// ``` /// ```
pub struct ResponseBuilder<'r> { pub struct ResponseBuilder<'r> {
response: Response<'r>, response: Response<'r>,
pending_sized_body: Option<Box<dyn AsyncReadAsyncSeek + 'r>>,
fut: Option<Pin<Box<dyn Future<Output=Response<'r>> + Send + 'r>>>,
} }
impl<'r> ResponseBuilder<'r> { impl<'r> ResponseBuilder<'r> {
@ -192,8 +178,6 @@ impl<'r> ResponseBuilder<'r> {
pub fn new(base: Response<'r>) -> ResponseBuilder<'r> { pub fn new(base: Response<'r>) -> ResponseBuilder<'r> {
ResponseBuilder { ResponseBuilder {
response: base, response: base,
pending_sized_body: None,
fut: None,
} }
} }
@ -205,14 +189,9 @@ impl<'r> ResponseBuilder<'r> {
/// use rocket::Response; /// use rocket::Response;
/// use rocket::http::Status; /// use rocket::http::Status;
/// ///
/// # rocket::async_test(async {
///
/// # #[allow(unused_variables)]
/// let response = Response::build() /// let response = Response::build()
/// .status(Status::NotFound) /// .status(Status::NotFound)
/// .await; /// .finalize();
///
/// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn status(&mut self, status: Status) -> &mut ResponseBuilder<'r> { pub fn status(&mut self, status: Status) -> &mut ResponseBuilder<'r> {
@ -227,14 +206,13 @@ impl<'r> ResponseBuilder<'r> {
/// ///
/// ```rust /// ```rust
/// use rocket::Response; /// use rocket::Response;
/// use rocket::http::Status;
/// ///
/// # rocket::async_test(async {
///
/// # #[allow(unused_variables)]
/// let response = Response::build() /// let response = Response::build()
/// .raw_status(699, "Alien Encounter") /// .raw_status(699, "Alien Encounter")
/// .await; /// .finalize();
/// # }) ///
/// assert_eq!(response.status(), Status::new(699, "Alien Encounter"));
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn raw_status(&mut self, code: u16, reason: &'static str) -> &mut ResponseBuilder<'r> { 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::Response;
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// ///
/// # rocket::async_test(async {
///
/// let response = Response::build() /// let response = Response::build()
/// .header(ContentType::JSON) /// .header(ContentType::JSON)
/// .header(ContentType::HTML) /// .header(ContentType::HTML)
/// .await; /// .finalize();
/// ///
/// assert_eq!(response.headers().get("Content-Type").count(), 1); /// assert_eq!(response.headers().get("Content-Type").count(), 1);
///
/// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn header<'h: 'r, H>(&mut self, header: H) -> &mut ResponseBuilder<'r> 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::Header;
/// use rocket::http::hyper::header::ACCEPT; /// use rocket::http::hyper::header::ACCEPT;
/// ///
/// # rocket::async_test(async {
///
/// let response = Response::build() /// let response = Response::build()
/// .header_adjoin(Header::new(ACCEPT.as_str(), "application/json")) /// .header_adjoin(Header::new(ACCEPT.as_str(), "application/json"))
/// .header_adjoin(Header::new(ACCEPT.as_str(), "text/plain")) /// .header_adjoin(Header::new(ACCEPT.as_str(), "text/plain"))
/// .await; /// .finalize();
/// ///
/// assert_eq!(response.headers().get("Accept").count(), 2); /// assert_eq!(response.headers().get("Accept").count(), 2);
///
/// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn header_adjoin<'h: 'r, H>(&mut self, header: H) -> &mut ResponseBuilder<'r> pub fn header_adjoin<'h: 'r, H>(&mut self, header: H) -> &mut ResponseBuilder<'r>
@ -321,16 +291,12 @@ impl<'r> ResponseBuilder<'r> {
/// ```rust /// ```rust
/// use rocket::Response; /// use rocket::Response;
/// ///
/// # rocket::async_test(async {
///
/// let response = Response::build() /// let response = Response::build()
/// .raw_header("X-Custom", "first") /// .raw_header("X-Custom", "first")
/// .raw_header("X-Custom", "second") /// .raw_header("X-Custom", "second")
/// .await; /// .finalize();
/// ///
/// assert_eq!(response.headers().get("X-Custom").count(), 1); /// assert_eq!(response.headers().get("X-Custom").count(), 1);
///
/// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn raw_header<'a, 'b, N, V>(&mut self, name: N, value: V) -> &mut ResponseBuilder<'r> 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 /// ```rust
/// use rocket::Response; /// use rocket::Response;
/// ///
/// # rocket::async_test(async {
///
/// let response = Response::build() /// let response = Response::build()
/// .raw_header_adjoin("X-Custom", "first") /// .raw_header_adjoin("X-Custom", "first")
/// .raw_header_adjoin("X-Custom", "second") /// .raw_header_adjoin("X-Custom", "second")
/// .await; /// .finalize();
/// ///
/// assert_eq!(response.headers().get("X-Custom").count(), 2); /// assert_eq!(response.headers().get("X-Custom").count(), 2);
///
/// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn raw_header_adjoin<'a, 'b, N, V>(&mut self, name: N, value: V) -> &mut ResponseBuilder<'r> 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 /// # Example
/// ///
/// ```rust /// ```rust
/// use std::io::Cursor;
/// use rocket::Response; /// use rocket::Response;
/// use tokio::fs::File;
/// # use std::io;
/// ///
/// # #[allow(dead_code)] /// # rocket::async_test(async {
/// # async fn test() -> io::Result<()> {
/// # #[allow(unused_variables)]
/// let response = Response::build() /// let response = Response::build()
/// .sized_body(File::open("body.txt").await?) /// .sized_body(Cursor::new("Hello, world!")).await
/// .await; /// .finalize();
/// # Ok(()) /// # })
/// # }
/// ``` /// ```
#[inline(always)] pub async fn sized_body<B>(&mut self, body: B) -> &mut ResponseBuilder<'r>
pub fn sized_body<B>(&mut self, body: B) -> &mut ResponseBuilder<'r>
where B: AsyncRead + AsyncSeek + Send + Unpin + 'r where B: AsyncRead + AsyncSeek + Send + Unpin + 'r
{ {
self.pending_sized_body = Some(Box::new(body)); self.response.set_sized_body(body).await;
self self
} }
@ -401,25 +358,18 @@ impl<'r> ResponseBuilder<'r> {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use std::io::Cursor;
/// use rocket::Response; /// 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() /// let response = Response::build()
/// .streamed_body(File::open("body.txt").await?) /// .streamed_body(Cursor::new("Hello, world!"))
/// .await; /// .finalize();
/// # Ok(())
/// # }
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn streamed_body<B>(&mut self, body: B) -> &mut ResponseBuilder<'r> pub fn streamed_body<B>(&mut self, body: B) -> &mut ResponseBuilder<'r>
where B: AsyncRead + Send + 'r where B: AsyncRead + Send + 'r
{ {
self.response.set_streamed_body(body); self.response.set_streamed_body(body);
self.pending_sized_body = None;
self self
} }
@ -433,21 +383,18 @@ impl<'r> ResponseBuilder<'r> {
/// use tokio::fs::File; /// use tokio::fs::File;
/// # use std::io; /// # use std::io;
/// ///
/// # #[allow(dead_code)] /// # async fn test<'r>() -> io::Result<Response<'r>> {
/// # async fn test() -> io::Result<()> {
/// # #[allow(unused_variables)]
/// let response = Response::build() /// let response = Response::build()
/// .chunked_body(File::open("body.txt").await?, 8096) /// .chunked_body(File::open("body.txt").await?, 8096)
/// .await; /// .ok();
/// # Ok(()) /// # response
/// # } /// # }
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn chunked_body<B: AsyncRead + Send + 'r>(&mut self, body: B, chunk_size: u64) pub fn chunked_body<B>(&mut self, body: B, chunk_size: u64) -> &mut ResponseBuilder<'r>
-> &mut ResponseBuilder<'r> where B: AsyncRead + Send + 'r
{ {
self.response.set_chunked_body(body, chunk_size); self.response.set_chunked_body(body, chunk_size);
self.pending_sized_body = None;
self self
} }
@ -462,20 +409,16 @@ impl<'r> ResponseBuilder<'r> {
/// use rocket::response::{Response, Body}; /// use rocket::response::{Response, Body};
/// ///
/// # rocket::async_test(async { /// # rocket::async_test(async {
///
/// # #[allow(unused_variables)]
/// let response = Response::build() /// let response = Response::build()
/// .raw_body(Body::Sized(Cursor::new("Hello!"), 6)) /// .raw_body(Body::Sized(Cursor::new("Hello!"), 6))
/// .await; /// .finalize();
///
/// # }) /// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn raw_body<T: AsyncRead + Send + Unpin + 'r>(&mut self, body: Body<T>) pub fn raw_body<T>(&mut self, body: Body<T>) -> &mut ResponseBuilder<'r>
-> &mut ResponseBuilder<'r> where T: AsyncRead + Send + Unpin + 'r
{ {
self.response.set_raw_body(body); self.response.set_raw_body(body);
self.pending_sized_body = None;
self self
} }
@ -491,40 +434,29 @@ impl<'r> ResponseBuilder<'r> {
/// use rocket::Response; /// use rocket::Response;
/// use rocket::http::{Status, ContentType}; /// use rocket::http::{Status, ContentType};
/// ///
/// # rocket::async_test(async {
///
/// let base = Response::build() /// let base = Response::build()
/// .status(Status::NotFound) /// .status(Status::NotFound)
/// .header(ContentType::HTML) /// .header(ContentType::HTML)
/// .raw_header("X-Custom", "value 1") /// .raw_header("X-Custom", "value 1")
/// .await; /// .finalize();
/// ///
/// let response = Response::build() /// let response = Response::build()
/// .status(Status::ImATeapot) /// .status(Status::ImATeapot)
/// .raw_header("X-Custom", "value 2") /// .raw_header("X-Custom", "value 2")
/// .raw_header_adjoin("X-Custom", "value 3") /// .raw_header_adjoin("X-Custom", "value 3")
/// .merge(base) /// .merge(base)
/// .await; /// .finalize();
/// ///
/// assert_eq!(response.status(), Status::NotFound); /// assert_eq!(response.status(), Status::NotFound);
/// ///
/// # {
/// let ctype: Vec<_> = response.headers().get("Content-Type").collect(); /// let ctype: Vec<_> = response.headers().get("Content-Type").collect();
/// assert_eq!(ctype, vec![ContentType::HTML.to_string()]); /// assert_eq!(ctype, vec![ContentType::HTML.to_string()]);
/// # }
/// ///
/// # {
/// let custom_values: Vec<_> = response.headers().get("X-Custom").collect(); /// let custom_values: Vec<_> = response.headers().get("X-Custom").collect();
/// assert_eq!(custom_values, vec!["value 1"]); /// assert_eq!(custom_values, vec!["value 1"]);
/// # }
///
/// # });
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn merge(&mut self, mut other: Response<'r>) -> &mut ResponseBuilder<'r> { pub fn merge(&mut self, other: Response<'r>) -> &mut ResponseBuilder<'r> {
if other.body().is_some() {
self.pending_sized_body = None;
}
self.response.merge(other); self.response.merge(other);
self self
} }
@ -542,34 +474,26 @@ impl<'r> ResponseBuilder<'r> {
/// use rocket::Response; /// use rocket::Response;
/// use rocket::http::{Status, ContentType}; /// use rocket::http::{Status, ContentType};
/// ///
/// # rocket::async_test(async {
///
/// let other = Response::build() /// let other = Response::build()
/// .status(Status::NotFound) /// .status(Status::NotFound)
/// .header(ContentType::HTML) /// .header(ContentType::HTML)
/// .raw_header("X-Custom", "value 1") /// .raw_header("X-Custom", "value 1")
/// .await; /// .finalize();
/// ///
/// let response = Response::build() /// let response = Response::build()
/// .status(Status::ImATeapot) /// .status(Status::ImATeapot)
/// .raw_header("X-Custom", "value 2") /// .raw_header("X-Custom", "value 2")
/// .raw_header_adjoin("X-Custom", "value 3") /// .raw_header_adjoin("X-Custom", "value 3")
/// .join(other) /// .join(other)
/// .await; /// .finalize();
/// ///
/// assert_eq!(response.status(), Status::ImATeapot); /// assert_eq!(response.status(), Status::ImATeapot);
/// ///
/// # {
/// let ctype: Vec<_> = response.headers().get("Content-Type").collect(); /// let ctype: Vec<_> = response.headers().get("Content-Type").collect();
/// assert_eq!(ctype, vec![ContentType::HTML.to_string()]); /// assert_eq!(ctype, vec![ContentType::HTML.to_string()]);
/// # }
/// ///
/// # {
/// let custom_values: Vec<_> = response.headers().get("X-Custom").collect(); /// let custom_values: Vec<_> = response.headers().get("X-Custom").collect();
/// assert_eq!(custom_values, vec!["value 2", "value 3", "value 1"]); /// assert_eq!(custom_values, vec!["value 2", "value 3", "value 1"]);
/// # }
///
/// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn join(&mut self, other: Response<'r>) -> &mut ResponseBuilder<'r> { pub fn join(&mut self, other: Response<'r>) -> &mut ResponseBuilder<'r> {
@ -577,48 +501,47 @@ impl<'r> ResponseBuilder<'r> {
self 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 /// # Example
/// ///
/// ```rust /// ```rust
/// use rocket::Response; /// use rocket::Response;
/// ///
/// # rocket::async_test(async {
///
/// let response: Result<Response, ()> = Response::build() /// let response: Result<Response, ()> = Response::build()
/// // build the response /// // build the response
/// .ok() /// .ok();
/// .await;
/// ///
/// assert!(response.is_ok()); /// assert!(response.is_ok());
///
/// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub async fn ok<E>(&mut self) -> Result<Response<'r>, E> { pub fn ok<E>(&mut self) -> Result<Response<'r>, E> {
Ok(self.await) Ok(self.finalize())
}
}
impl<'r> Future for ResponseBuilder<'r> {
type Output = Response<'r>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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<Box<Take<Pin<Box<dyn AsyncReadAsyncSeek>>>>>)
response.set_sized_body(sb).await;
}
response
}));
}
this.fut.as_mut().expect("this.fut.is_none() checked and assigned Some").as_mut().poll(cx)
} }
} }
@ -1139,7 +1062,8 @@ impl<'r> Response<'r> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn set_chunked_body<B>(&mut self, body: B, chunk_size: u64) pub fn set_chunked_body<B>(&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)); self.body = Some(Body::Chunked(Box::pin(body), chunk_size));
} }
@ -1181,34 +1105,26 @@ impl<'r> Response<'r> {
/// use rocket::Response; /// use rocket::Response;
/// use rocket::http::{Status, ContentType}; /// use rocket::http::{Status, ContentType};
/// ///
/// # rocket::async_test(async {
///
/// let base = Response::build() /// let base = Response::build()
/// .status(Status::NotFound) /// .status(Status::NotFound)
/// .header(ContentType::HTML) /// .header(ContentType::HTML)
/// .raw_header("X-Custom", "value 1") /// .raw_header("X-Custom", "value 1")
/// .await; /// .finalize();
/// ///
/// let response = Response::build() /// let response = Response::build()
/// .status(Status::ImATeapot) /// .status(Status::ImATeapot)
/// .raw_header("X-Custom", "value 2") /// .raw_header("X-Custom", "value 2")
/// .raw_header_adjoin("X-Custom", "value 3") /// .raw_header_adjoin("X-Custom", "value 3")
/// .merge(base) /// .merge(base)
/// .await; /// .finalize();
/// ///
/// assert_eq!(response.status(), Status::NotFound); /// assert_eq!(response.status(), Status::NotFound);
/// ///
/// # {
/// let ctype: Vec<_> = response.headers().get("Content-Type").collect(); /// let ctype: Vec<_> = response.headers().get("Content-Type").collect();
/// assert_eq!(ctype, vec![ContentType::HTML.to_string()]); /// assert_eq!(ctype, vec![ContentType::HTML.to_string()]);
/// # }
/// ///
/// # {
/// let custom_values: Vec<_> = response.headers().get("X-Custom").collect(); /// let custom_values: Vec<_> = response.headers().get("X-Custom").collect();
/// assert_eq!(custom_values, vec!["value 1"]); /// assert_eq!(custom_values, vec!["value 1"]);
/// # }
///
/// # })
/// ``` /// ```
pub fn merge(&mut self, other: Response<'r>) { pub fn merge(&mut self, other: Response<'r>) {
if let Some(status) = other.status { if let Some(status) = other.status {
@ -1234,34 +1150,26 @@ impl<'r> Response<'r> {
/// use rocket::Response; /// use rocket::Response;
/// use rocket::http::{Status, ContentType}; /// use rocket::http::{Status, ContentType};
/// ///
/// # rocket::async_test(async {
///
/// let other = Response::build() /// let other = Response::build()
/// .status(Status::NotFound) /// .status(Status::NotFound)
/// .header(ContentType::HTML) /// .header(ContentType::HTML)
/// .raw_header("X-Custom", "value 1") /// .raw_header("X-Custom", "value 1")
/// .await; /// .finalize();
/// ///
/// let response = Response::build() /// let response = Response::build()
/// .status(Status::ImATeapot) /// .status(Status::ImATeapot)
/// .raw_header("X-Custom", "value 2") /// .raw_header("X-Custom", "value 2")
/// .raw_header_adjoin("X-Custom", "value 3") /// .raw_header_adjoin("X-Custom", "value 3")
/// .join(other) /// .join(other)
/// .await; /// .finalize();
/// ///
/// assert_eq!(response.status(), Status::ImATeapot); /// assert_eq!(response.status(), Status::ImATeapot);
/// ///
/// # {
/// let ctype: Vec<_> = response.headers().get("Content-Type").collect(); /// let ctype: Vec<_> = response.headers().get("Content-Type").collect();
/// assert_eq!(ctype, vec![ContentType::HTML.to_string()]); /// assert_eq!(ctype, vec![ContentType::HTML.to_string()]);
/// # }
/// ///
/// # {
/// let custom_values: Vec<_> = response.headers().get("X-Custom").collect(); /// let custom_values: Vec<_> = response.headers().get("X-Custom").collect();
/// assert_eq!(custom_values, vec!["value 2", "value 3", "value 1"]); /// assert_eq!(custom_values, vec!["value 2", "value 3", "value 1"]);
/// # }
///
/// # })
/// ``` /// ```
pub fn join(&mut self, other: Response<'r>) { pub fn join(&mut self, other: Response<'r>) {
if self.status.is_none() { if self.status.is_none() {
@ -1295,11 +1203,10 @@ impl fmt::Debug for Response<'_> {
use crate::request::Request; use crate::request::Request;
#[crate::async_trait]
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, _: &'r Request<'_>) -> ResultFuture<'r> { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'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, ResultFuture}; use crate::response::{self, Responder, Response};
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).
@ -160,9 +160,9 @@ 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.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Created<R> { impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Created<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> { async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'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).await?); response.merge(responder.respond_to(req).await?);
@ -175,8 +175,6 @@ impl<'r, R: Responder<'r> + Send + '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()
.await
})
} }
} }
@ -209,16 +207,15 @@ 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.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Accepted<R> { impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Accepted<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> { async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'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).await?); 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<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.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for BadRequest<R> { impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for BadRequest<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> { async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'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).await?); 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<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.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for NotFound<R> { impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for NotFound<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> { async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
Box::pin(async move {
Response::build_from(self.0.respond_to(req).await?) Response::build_from(self.0.respond_to(req).await?)
.status(Status::NotFound) .status(Status::NotFound)
.ok() .ok()
.await
})
} }
} }
@ -453,14 +447,12 @@ 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.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Custom<R> { impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Custom<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> { async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
Box::pin(async move {
Response::build_from(self.1.respond_to(req).await?) Response::build_from(self.1.respond_to(req).await?)
.status(self.0) .status(self.0)
.ok() .ok()
.await
})
} }
} }

View File

@ -3,7 +3,7 @@ use std::fmt::{self, Debug};
use tokio::io::AsyncRead; use tokio::io::AsyncRead;
use crate::request::Request; 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. /// Streams a response to a client from an arbitrary `AsyncRead`er type.
/// ///
@ -66,10 +66,9 @@ impl<T: AsyncRead> From<T> for Stream<T> {
/// If reading from the input stream fails at any point during the response, the /// 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 /// 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.
#[crate::async_trait]
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, _: &'r Request<'_>) -> ResultFuture<'r> { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Box::pin(async { Response::build().chunked_body(self.0, self.1).ok()
Response::build().chunked_body(self.0, self.1).ok().await
})
} }
} }

View File

@ -14,7 +14,9 @@ use crate::http::Method;
type Selector = Method; 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) -> 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, ()) crate::Outcome::from(r, ())
} }

View File

@ -9,7 +9,7 @@ use rocket::http::Header;
async fn do_not_overwrite() -> Response<'static> { async fn do_not_overwrite() -> Response<'static> {
Response::build() Response::build()
.header(Header::new("Server", "Test")) .header(Header::new("Server", "Test"))
.await .finalize()
} }
#[get("/use_default")] #[get("/use_default")]

View File

@ -3,8 +3,9 @@
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
use rocket::State; use rocket::{Request, State};
use rocket::response::{self, Responder}; use rocket::futures::future::BoxFuture;
use rocket::response::{Responder, Result};
struct SomeState; struct SomeState;
@ -14,7 +15,9 @@ 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::ResultFuture<'r> { fn respond_to<'a, 'x>(self, _: &'r Request<'a>) -> BoxFuture<'x, Result<'r>>
where 'a: 'x, 'r: 'x, Self: 'x
{
unimplemented!() unimplemented!()
} }
} }

View File

@ -5,14 +5,14 @@ mod tests;
use std::env; use std::env;
use tokio::fs::File;
use rocket::{Request, Handler, Route, Data, Catcher, try_outcome}; use rocket::{Request, Handler, Route, Data, Catcher, try_outcome};
use rocket::http::{Status, RawStr}; use rocket::http::{Status, RawStr};
use rocket::response::{self, Responder, status::Custom}; use rocket::response::{self, Responder, status::Custom};
use rocket::handler::{Outcome, HandlerFuture}; use rocket::handler::{Outcome, HandlerFuture};
use rocket::outcome::IntoOutcome; use rocket::outcome::IntoOutcome;
use rocket::http::Method::*; use rocket::http::Method::*;
use rocket::futures::future::BoxFuture;
use rocket::tokio::fs::File;
fn forward<'r>(_req: &'r Request, data: Data) -> HandlerFuture<'r> { fn forward<'r>(_req: &'r Request, data: Data) -> HandlerFuture<'r> {
Box::pin(async move { Outcome::forward(data) }) 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()) 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())); let res = Custom(Status::NotFound, format!("Couldn't find: {}", req.uri()));
res.respond_to(req) res.respond_to(req)
} }