Use 'AsyncSeek' for sized bodies in 'Response's.

In order to avoid making 'ResponseBuilder::sized_body' an asynchronous
function, the seeking is deferred until finalization. 'finalize()' is
replaced with '.await', and 'ResponseBuilder::ok()' is an 'async fn'.
This commit is contained in:
Michael Howell 2020-01-07 06:28:57 +00:00 committed by Sergio Benitez
parent dcd4068ca0
commit c9d0af09d6
15 changed files with 164 additions and 78 deletions

View File

@ -28,5 +28,5 @@ version_check = "0.9.1"
[dev-dependencies] [dev-dependencies]
rocket = { version = "0.5.0-dev", path = "../lib" } rocket = { version = "0.5.0-dev", path = "../lib" }
tokio = { version = "0.2.0", features = ["io-util"] } tokio = { version = "0.2.9", features = ["io-util"] }
compiletest_rs = { version = "0.3", features = ["stable"] } compiletest_rs = { version = "0.3", features = ["stable"] }

View File

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

View File

@ -29,7 +29,7 @@ time = "0.2.11"
indexmap = "1.0" indexmap = "1.0"
state = "0.4" state = "0.4"
tokio-rustls = { version = "0.12.0", optional = true } tokio-rustls = { version = "0.12.0", optional = true }
tokio = { version = "0.2.0", features = ["sync", "tcp", "time"] } tokio = { version = "0.2.9", features = ["sync", "tcp", "time"] }
cookie = { version = "0.14.0", features = ["percent-encode"] } cookie = { version = "0.14.0", features = ["percent-encode"] }
pear = "0.1" pear = "0.1"
unicode-xid = "0.2" unicode-xid = "0.2"

View File

@ -159,7 +159,9 @@ impl PartialEq for AcceptParams {
/// use rocket::response::Response; /// use rocket::response::Response;
/// ///
/// # #[allow(unused_variables)] /// # #[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

@ -39,7 +39,9 @@ use crate::ext::IntoCollection;
/// use rocket::response::Response; /// use rocket::response::Response;
/// ///
/// # #[allow(unused_variables)] /// # #[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,7 +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"
tokio = { version = "0.2.0", 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"] }
toml = "0.4.7" toml = "0.4.7"
@ -47,4 +47,4 @@ version_check = "0.9.1"
[dev-dependencies] [dev-dependencies]
# TODO: Find a way to not depend on this. # TODO: Find a way to not depend on this.
lazy_static = "1.0" lazy_static = "1.0"
tokio = { version = "0.2.0", features = ["macros"] } tokio = { version = "0.2.9", features = ["macros"] }

View File

@ -54,6 +54,7 @@ impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Content<R> {
.merge(self.1.respond_to(req).await?) .merge(self.1.respond_to(req).await?)
.header(self.0) .header(self.0)
.ok() .ok()
.await
}) })
} }
} }

View File

@ -68,7 +68,7 @@ impl<'r, E: std::fmt::Debug + Send + 'r> Responder<'r> for Debug<E> {
Box::pin(async move { Box::pin(async move {
warn_!("Debug: {:?}", Paint::default(self.0)); warn_!("Debug: {:?}", Paint::default(self.0));
warn_!("Debug always responds with {}.", Status::InternalServerError); warn_!("Debug always responds with {}.", Status::InternalServerError);
Response::build().status(Status::InternalServerError).ok() Response::build().status(Status::InternalServerError).ok().await
}) })
} }
} }

View File

@ -155,6 +155,7 @@ impl<'r> Responder<'r> for Redirect {
.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

@ -163,6 +163,7 @@ use crate::request::Request;
/// .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
/// }) /// })
/// } /// }
/// } /// }
@ -195,6 +196,7 @@ impl<'r> Responder<'r> for &'r str {
.header(ContentType::Plain) .header(ContentType::Plain)
.sized_body(Cursor::new(self)) .sized_body(Cursor::new(self))
.ok() .ok()
.await
}) })
} }
} }
@ -208,6 +210,7 @@ impl Responder<'_> for String {
.header(ContentType::Plain) .header(ContentType::Plain)
.sized_body(Cursor::new(self)) .sized_body(Cursor::new(self))
.ok() .ok()
.await
}) })
} }
} }
@ -221,6 +224,7 @@ impl<'r> Responder<'r> for &'r [u8] {
.header(ContentType::Binary) .header(ContentType::Binary)
.sized_body(Cursor::new(self)) .sized_body(Cursor::new(self))
.ok() .ok()
.await
}) })
} }
} }
@ -234,6 +238,7 @@ impl Responder<'_> for Vec<u8> {
.header(ContentType::Binary) .header(ContentType::Binary)
.sized_body(Cursor::new(self)) .sized_body(Cursor::new(self))
.ok() .ok()
.await
}) })
} }
} }
@ -246,8 +251,8 @@ impl Responder<'_> for File {
let metadata = file.metadata().await; let metadata = file.metadata().await;
let stream = BufReader::new(file); let stream = BufReader::new(file);
match metadata { match metadata {
Ok(md) => Response::build().raw_body(Body::Sized(stream, md.len())).ok(), Ok(md) => Response::build().raw_body(Body::Sized(stream, md.len())).ok().await,
Err(_) => Response::build().streamed_body(stream).ok() Err(_) => Response::build().streamed_body(stream).ok().await
} }
}) })
} }
@ -307,10 +312,10 @@ impl<'r> Responder<'r> for Status {
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() Response::build().status(self).ok().await
} }
StatusClass::Informational if self.code == 100 => { StatusClass::Informational if self.code == 100 => {
Response::build().status(self).ok() Response::build().status(self).ok().await
} }
_ => { _ => {
error_!("Invalid status used as responder: {}.", self); error_!("Invalid status used as responder: {}.", self);

View File

@ -2,8 +2,9 @@ use std::{io, fmt, str};
use std::borrow::Cow; use std::borrow::Cow;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncReadExt}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt};
use futures_util::future::FutureExt; use futures_util::future::FutureExt;
use crate::response::{Responder, ResultFuture}; use crate::response::{Responder, ResultFuture};
@ -104,6 +105,12 @@ 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
@ -117,8 +124,9 @@ impl<T> fmt::Debug for Body<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`, use the /// To finish building and retrieve the built `Response`, `.await` the builder.
/// [`finalize()`](#method.finalize) or [`ok()`](#method.ok) methods. /// The [`ok()` method](#method.ok) is also provided as a convenience
/// for `Responder` implementations.
/// ///
/// ## Headers /// ## Headers
/// ///
@ -153,6 +161,8 @@ impl<T> fmt::Debug for Body<T> {
/// use rocket::response::Response; /// use rocket::response::Response;
/// use rocket::http::{Status, ContentType}; /// use rocket::http::{Status, ContentType};
/// ///
/// # rocket::async_test(async {
///
/// # #[allow(unused_variables)] /// # #[allow(unused_variables)]
/// let response = Response::build() /// let response = Response::build()
/// .status(Status::ImATeapot) /// .status(Status::ImATeapot)
@ -161,10 +171,14 @@ impl<T> fmt::Debug for Body<T> {
/// .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!"))
/// .finalize(); /// .await;
///
/// # })
/// ``` /// ```
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> {
@ -182,7 +196,9 @@ impl<'r> ResponseBuilder<'r> {
#[inline(always)] #[inline(always)]
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,
} }
} }
@ -194,10 +210,14 @@ 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)] /// # #[allow(unused_variables)]
/// let response = Response::build() /// let response = Response::build()
/// .status(Status::NotFound) /// .status(Status::NotFound)
/// .finalize(); /// .await;
///
/// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn status(&mut self, status: Status) -> &mut ResponseBuilder<'r> { pub fn status(&mut self, status: Status) -> &mut ResponseBuilder<'r> {
@ -213,10 +233,13 @@ impl<'r> ResponseBuilder<'r> {
/// ```rust /// ```rust
/// use rocket::Response; /// use rocket::Response;
/// ///
/// # rocket::async_test(async {
///
/// # #[allow(unused_variables)] /// # #[allow(unused_variables)]
/// let response = Response::build() /// let response = Response::build()
/// .raw_status(699, "Alien Encounter") /// .raw_status(699, "Alien Encounter")
/// .finalize(); /// .await;
/// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn raw_status(&mut self, code: u16, reason: &'static str) pub fn raw_status(&mut self, code: u16, reason: &'static str)
@ -240,12 +263,16 @@ 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)
/// .finalize(); /// .await;
/// ///
/// 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>
@ -271,12 +298,16 @@ 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"))
/// .finalize(); /// .await;
/// ///
/// 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>
@ -296,12 +327,16 @@ 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")
/// .finalize(); /// .await;
/// ///
/// 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: 'r, 'b: 'r, N, V>(&mut self, name: N, value: V) pub fn raw_header<'a: 'r, 'b: 'r, N, V>(&mut self, name: N, value: V)
@ -323,12 +358,16 @@ 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")
/// .finalize(); /// .await;
/// ///
/// 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: 'r, 'b: 'r, N, V>(&mut self, name: N, value: V) pub fn raw_header_adjoin<'a: 'r, 'b: 'r, N, V>(&mut self, name: N, value: V)
@ -339,12 +378,11 @@ impl<'r> ResponseBuilder<'r> {
self self
} }
// TODO.async: un-ignore this test once Seek/AsyncSeek situation has been resolved.
/// Sets the body of the `Response` to be the fixed-sized `body`. /// Sets the body of the `Response` to be the fixed-sized `body`.
/// ///
/// # Example /// # Example
/// ///
/// ```rust,ignore /// ```rust
/// use rocket::Response; /// use rocket::Response;
/// use tokio::fs::File; /// use tokio::fs::File;
/// # use std::io; /// # use std::io;
@ -354,15 +392,15 @@ impl<'r> ResponseBuilder<'r> {
/// # #[allow(unused_variables)] /// # #[allow(unused_variables)]
/// let response = Response::build() /// let response = Response::build()
/// .sized_body(File::open("body.txt").await?) /// .sized_body(File::open("body.txt").await?)
/// .finalize(); /// .await;
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub 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 + io::Seek + Send + Unpin + 'r where B: AsyncRead + AsyncSeek + Send + Unpin + 'r
{ {
self.response.set_sized_body(body); self.pending_sized_body = Some(Box::new(body));
self self
} }
@ -380,7 +418,7 @@ impl<'r> ResponseBuilder<'r> {
/// # #[allow(unused_variables)] /// # #[allow(unused_variables)]
/// let response = Response::build() /// let response = Response::build()
/// .streamed_body(File::open("body.txt").await?) /// .streamed_body(File::open("body.txt").await?)
/// .finalize(); /// .await;
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
@ -389,6 +427,7 @@ impl<'r> 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
} }
@ -407,7 +446,7 @@ impl<'r> ResponseBuilder<'r> {
/// # #[allow(unused_variables)] /// # #[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)
/// .finalize(); /// .await;
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
@ -416,6 +455,7 @@ impl<'r> ResponseBuilder<'r> {
-> &mut ResponseBuilder<'r> -> &mut ResponseBuilder<'r>
{ {
self.response.set_chunked_body(body, chunk_size); self.response.set_chunked_body(body, chunk_size);
self.pending_sized_body = None;
self self
} }
@ -429,16 +469,21 @@ impl<'r> ResponseBuilder<'r> {
/// use std::io::Cursor; /// use std::io::Cursor;
/// use rocket::response::{Response, Body}; /// use rocket::response::{Response, Body};
/// ///
/// # rocket::async_test(async {
///
/// # #[allow(unused_variables)] /// # #[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))
/// .finalize(); /// .await;
///
/// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn raw_body<T: AsyncRead + Send + Unpin + 'r>(&mut self, body: Body<T>) pub fn raw_body<T: AsyncRead + Send + Unpin + 'r>(&mut self, body: Body<T>)
-> &mut ResponseBuilder<'r> -> &mut ResponseBuilder<'r>
{ {
self.response.set_raw_body(body); self.response.set_raw_body(body);
self.pending_sized_body = None;
self self
} }
@ -454,18 +499,20 @@ 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")
/// .finalize(); /// .await;
/// ///
/// 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)
/// .finalize(); /// .await;
/// ///
/// assert_eq!(response.status(), Status::NotFound); /// assert_eq!(response.status(), Status::NotFound);
/// ///
@ -478,9 +525,14 @@ impl<'r> ResponseBuilder<'r> {
/// 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, other: Response<'r>) -> &mut ResponseBuilder<'r> { pub fn merge(&mut self, mut 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
} }
@ -498,18 +550,20 @@ 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")
/// .finalize(); /// .await;
/// ///
/// 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)
/// .finalize(); /// .await;
/// ///
/// assert_eq!(response.status(), Status::ImATeapot); /// assert_eq!(response.status(), Status::ImATeapot);
/// ///
@ -522,6 +576,8 @@ impl<'r> ResponseBuilder<'r> {
/// 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> {
@ -529,23 +585,6 @@ impl<'r> ResponseBuilder<'r> {
self self
} }
/// Retrieve the built `Response`.
///
/// # Example
///
/// ```rust
/// use rocket::Response;
///
/// # #[allow(unused_variables)]
/// let response = Response::build()
/// // build the response
/// .finalize();
/// ```
#[inline(always)]
pub fn finalize(&mut self) -> Response<'r> {
std::mem::replace(&mut self.response, Response::new())
}
/// Retrieve the built `Response` wrapped in `Ok`. /// Retrieve the built `Response` wrapped in `Ok`.
/// ///
/// # Example /// # Example
@ -553,15 +592,39 @@ impl<'r> ResponseBuilder<'r> {
/// ```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 fn ok<T>(&mut self) -> Result<Response<'r>, T> { pub async fn ok<E>(&mut self) -> Result<Response<'r>, E> {
Ok(self.finalize()) Ok(self.await)
}
}
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)
} }
} }
@ -897,7 +960,7 @@ impl<'r> Response<'r> {
/// let mut response = Response::new(); /// let mut response = Response::new();
/// assert!(response.body().is_none()); /// assert!(response.body().is_none());
/// ///
/// response.set_sized_body(Cursor::new("Hello, world!")); /// response.set_sized_body(Cursor::new("Hello, world!")).await;
/// assert_eq!(response.body_string().await, Some("Hello, world!".to_string())); /// assert_eq!(response.body_string().await, Some("Hello, world!".to_string()));
/// # }) /// # })
/// ``` /// ```
@ -928,7 +991,7 @@ impl<'r> Response<'r> {
/// let mut response = Response::new(); /// let mut response = Response::new();
/// assert!(response.body().is_none()); /// assert!(response.body().is_none());
/// ///
/// response.set_sized_body(Cursor::new("Hello, world!")); /// response.set_sized_body(Cursor::new("Hello, world!")).await;
/// assert_eq!(response.body_string().await, Some("Hello, world!".to_string())); /// assert_eq!(response.body_string().await, Some("Hello, world!".to_string()));
/// assert!(response.body().is_none()); /// assert!(response.body().is_none());
/// # }) /// # })
@ -958,7 +1021,7 @@ impl<'r> Response<'r> {
/// let mut response = Response::new(); /// let mut response = Response::new();
/// assert!(response.body().is_none()); /// assert!(response.body().is_none());
/// ///
/// response.set_sized_body(Cursor::new("hi!")); /// response.set_sized_body(Cursor::new("hi!")).await;
/// assert_eq!(response.body_bytes().await, Some(vec![0x68, 0x69, 0x21])); /// assert_eq!(response.body_bytes().await, Some(vec![0x68, 0x69, 0x21]));
/// assert!(response.body().is_none()); /// assert!(response.body().is_none());
/// # }) /// # })
@ -987,7 +1050,7 @@ impl<'r> Response<'r> {
/// let mut response = Response::new(); /// let mut response = Response::new();
/// assert!(response.body().is_none()); /// assert!(response.body().is_none());
/// ///
/// response.set_sized_body(Cursor::new("Hello, world!")); /// response.set_sized_body(Cursor::new("Hello, world!")).await;
/// assert!(response.body().is_some()); /// assert!(response.body().is_some());
/// ///
/// let body = response.take_body(); /// let body = response.take_body();
@ -1018,7 +1081,8 @@ impl<'r> Response<'r> {
/// Sets the body of `self` to be the fixed-sized `body`. The size of the /// Sets the body of `self` to be the fixed-sized `body`. The size of the
/// body is obtained by `seek`ing to the end and then `seek`ing back to the /// body is obtained by `seek`ing to the end and then `seek`ing back to the
/// start. /// start. Since this is an asynchronous operation, it returns a future
/// and should be `await`-ed on.
/// ///
/// # Panics /// # Panics
/// ///
@ -1034,17 +1098,16 @@ impl<'r> Response<'r> {
/// ///
/// # rocket::async_test(async { /// # rocket::async_test(async {
/// let mut response = Response::new(); /// let mut response = Response::new();
/// response.set_sized_body(Cursor::new("Hello, world!")); /// response.set_sized_body(Cursor::new("Hello, world!")).await;
/// assert_eq!(response.body_string().await, Some("Hello, world!".to_string())); /// assert_eq!(response.body_string().await, Some("Hello, world!".to_string()));
/// # }) /// # })
/// ``` /// ```
#[inline] pub async fn set_sized_body<B>(&mut self, mut body: B)
pub fn set_sized_body<B>(&mut self, mut body: B) where B: AsyncRead + AsyncSeek + Send + Unpin + 'r
where B: AsyncRead + io::Seek + Send + Unpin + 'r
{ {
let size = body.seek(io::SeekFrom::End(0)) let size = body.seek(io::SeekFrom::End(0)).await
.expect("Attempted to retrieve size by seeking, but failed."); .expect("Attempted to retrieve size by seeking, but failed.");
body.seek(io::SeekFrom::Start(0)) body.seek(io::SeekFrom::Start(0)).await
.expect("Attempted to reset body by seeking after getting size."); .expect("Attempted to reset body by seeking after getting size.");
self.body = Some(Body::Sized(Box::pin(body.take(size)), size)); self.body = Some(Body::Sized(Box::pin(body.take(size)), size));
} }
@ -1130,18 +1193,20 @@ 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")
/// .finalize(); /// .await;
/// ///
/// 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)
/// .finalize(); /// .await;
/// ///
/// assert_eq!(response.status(), Status::NotFound); /// assert_eq!(response.status(), Status::NotFound);
/// ///
@ -1154,6 +1219,8 @@ impl<'r> Response<'r> {
/// 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 {
@ -1179,18 +1246,20 @@ 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")
/// .finalize(); /// .await;
/// ///
/// 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)
/// .finalize(); /// .await;
/// ///
/// assert_eq!(response.status(), Status::ImATeapot); /// assert_eq!(response.status(), Status::ImATeapot);
/// ///
@ -1203,6 +1272,8 @@ impl<'r> Response<'r> {
/// 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() {

View File

@ -175,6 +175,7 @@ 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
}) })
} }
} }
@ -216,7 +217,7 @@ impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Accepted<R> {
build.merge(responder.respond_to(req).await?); build.merge(responder.respond_to(req).await?);
} }
build.status(Status::Accepted).ok() build.status(Status::Accepted).ok().await
}) })
} }
} }
@ -283,7 +284,7 @@ impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for BadRequest<R> {
build.merge(responder.respond_to(req).await?); build.merge(responder.respond_to(req).await?);
} }
build.status(Status::BadRequest).ok() build.status(Status::BadRequest).ok().await
}) })
} }
} }
@ -390,6 +391,7 @@ impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for NotFound<R> {
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
}) })
} }
} }
@ -457,6 +459,7 @@ impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Custom<R> {
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

@ -69,7 +69,7 @@ impl<T: AsyncRead> From<T> for Stream<T> {
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> { fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async { Box::pin(async {
Response::build().chunked_body(self.0, self.1).ok() Response::build().chunked_body(self.0, self.1).ok().await
}) })
} }
} }

View File

@ -6,10 +6,10 @@ use rocket::Response;
use rocket::http::Header; use rocket::http::Header;
#[get("/do_not_overwrite")] #[get("/do_not_overwrite")]
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"))
.finalize() .await
} }
#[get("/use_default")] #[get("/use_default")]

View File

@ -54,7 +54,7 @@ impl Fairing for Counter {
let body = format!("Get: {}\nPost: {}", get_count, post_count); let body = format!("Get: {}\nPost: {}", get_count, post_count);
response.set_status(Status::Ok); response.set_status(Status::Ok);
response.set_header(ContentType::Plain); response.set_header(ContentType::Plain);
response.set_sized_body(Cursor::new(body)); response.set_sized_body(Cursor::new(body)).await;
} }
}) })
} }
@ -95,7 +95,7 @@ fn rocket() -> rocket::Rocket {
Box::pin(async move { Box::pin(async move {
if req.uri().path() == "/" { if req.uri().path() == "/" {
println!(" => Rewriting response body."); println!(" => Rewriting response body.");
res.set_sized_body(Cursor::new("Hello, fairings!")); res.set_sized_body(Cursor::new("Hello, fairings!")).await;
} }
}) })
})) }))