Revamp 'Response', 'Body' types.

This is a breaking change for many consumers of the 'Response' and all
consumers of the 'Body' API. The summary of breaking changes is:

  * 'Response::body()', 'Response::body_mut()' are infallible.
  * A 'Body' can represent an empty body in more cases.
  * 'ResponseBuilder' is now simply 'Builder'.
  * Direct body read methods on 'Response' were removed in favor of
    chaining through 'body_mut()': 'r.body_mut().to_string()'.
  * Notion of a 'chunked_body' was removed as it was inaccurate.
  * Maximum chunk size can be set on any body.
  * 'Response' no longer implements 'Responder'.

A few bugs were fixed in the process. Specifically, 'Body' will emit an
accurate size even for bodies that are partially read, and the size of
seek-determined bodies is emitted on HEAD request where it wasn't
before. Specifics on transport were clarified, and 'Body' docs greatly
improved as a result.
This commit is contained in:
Sergio Benitez 2021-04-28 01:51:51 -07:00
parent e7b28f18a9
commit 63e6845386
12 changed files with 629 additions and 486 deletions

View File

@ -25,37 +25,37 @@ async fn responder_foo() {
let local_req = client.get("/"); let local_req = client.get("/");
let req = local_req.inner(); let req = local_req.inner();
let mut response = Foo::First("hello".into()) let mut r = Foo::First("hello".into())
.respond_to(req) .respond_to(req)
.expect("response okay"); .expect("response okay");
assert_eq!(response.status(), Status::Ok); assert_eq!(r.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain)); assert_eq!(r.content_type(), Some(ContentType::Plain));
assert_eq!(response.body_string().await, Some("hello".into())); assert_eq!(r.body_mut().to_string().await.unwrap(), "hello");
let mut response = Foo::Second("just a test".into()) let mut r = Foo::Second("just a test".into())
.respond_to(req) .respond_to(req)
.expect("response okay"); .expect("response okay");
assert_eq!(response.status(), Status::InternalServerError); assert_eq!(r.status(), Status::InternalServerError);
assert_eq!(response.content_type(), Some(ContentType::Binary)); assert_eq!(r.content_type(), Some(ContentType::Binary));
assert_eq!(response.body_string().await, Some("just a test".into())); assert_eq!(r.body_mut().to_string().await.unwrap(), "just a test");
let mut response = Foo::Third { responder: "well, hi", ct: ContentType::JSON } let mut r = Foo::Third { responder: "well, hi", ct: ContentType::JSON }
.respond_to(req) .respond_to(req)
.expect("response okay"); .expect("response okay");
assert_eq!(response.status(), Status::NotFound); assert_eq!(r.status(), Status::NotFound);
assert_eq!(response.content_type(), Some(ContentType::HTML)); assert_eq!(r.content_type(), Some(ContentType::HTML));
assert_eq!(response.body_string().await, Some("well, hi".into())); assert_eq!(r.body_mut().to_string().await.unwrap(), "well, hi");
let mut response = Foo::Fourth { string: "goodbye", ct: ContentType::JSON } let mut r = Foo::Fourth { string: "goodbye", ct: ContentType::JSON }
.respond_to(req) .respond_to(req)
.expect("response okay"); .expect("response okay");
assert_eq!(response.status(), Status::raw(105)); assert_eq!(r.status(), Status::raw(105));
assert_eq!(response.content_type(), Some(ContentType::JSON)); assert_eq!(r.content_type(), Some(ContentType::JSON));
assert_eq!(response.body_string().await, Some("goodbye".into())); assert_eq!(r.body_mut().to_string().await.unwrap(), "goodbye");
} }
#[derive(Responder)] #[derive(Responder)]
@ -74,17 +74,17 @@ async fn responder_bar() {
let local_req = client.get("/"); let local_req = client.get("/");
let req = local_req.inner(); let req = local_req.inner();
let mut response = Bar { let mut r = Bar {
responder: Foo::Second("foo foo".into()), responder: Foo::Second("foo foo".into()),
other: ContentType::HTML, other: ContentType::HTML,
third: Cookie::new("cookie", "here!"), third: Cookie::new("cookie", "here!"),
_yet_another: "uh..hi?".into() _yet_another: "uh..hi?".into()
}.respond_to(req).expect("response okay"); }.respond_to(req).expect("response okay");
assert_eq!(response.status(), Status::InternalServerError); assert_eq!(r.status(), Status::InternalServerError);
assert_eq!(response.content_type(), Some(ContentType::Plain)); assert_eq!(r.content_type(), Some(ContentType::Plain));
assert_eq!(response.body_string().await, Some("foo foo".into())); assert_eq!(r.body_mut().to_string().await.unwrap(), "foo foo");
assert_eq!(response.headers().get_one("Set-Cookie"), Some("cookie=here!")); assert_eq!(r.headers().get_one("Set-Cookie"), Some("cookie=here!"));
} }
#[derive(Responder)] #[derive(Responder)]
@ -99,11 +99,11 @@ async fn responder_baz() {
let local_req = client.get("/"); let local_req = client.get("/");
let req = local_req.inner(); let req = local_req.inner();
let mut response = Baz { responder: "just a custom" } let mut r = Baz { responder: "just a custom" }
.respond_to(req) .respond_to(req)
.expect("response okay"); .expect("response okay");
assert_eq!(response.status(), Status::Ok); assert_eq!(r.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::new("application", "x-custom"))); assert_eq!(r.content_type(), Some(ContentType::new("application", "x-custom")));
assert_eq!(response.body_string().await, Some("just a custom".into())); assert_eq!(r.body_mut().to_string().await.unwrap(), "just a custom");
} }

View File

@ -40,7 +40,7 @@ use crate::{Request, Response};
/// ///
/// // Check metadata validity. /// // Check metadata validity.
/// assert_eq!(response.status(), Status::Ok); /// assert_eq!(response.status(), Status::Ok);
/// assert_eq!(response.body().unwrap().known_size(), Some(13)); /// assert_eq!(response.body().preset_size(), Some(13));
/// ///
/// // Read 10 bytes of the body. Note: in reality, we'd use `into_string()`. /// // Read 10 bytes of the body. Note: in reality, we'd use `into_string()`.
/// let mut buffer = [0; 10]; /// let mut buffer = [0; 10];
@ -107,12 +107,12 @@ impl LocalResponse<'_> {
&self.cookies &self.cookies
} }
pub(crate) async fn _into_string(mut self) -> Option<String> { pub(crate) async fn _into_string(mut self) -> io::Result<String> {
self.response.body_string().await self.response.body_mut().to_string().await
} }
pub(crate) async fn _into_bytes(mut self) -> Option<Vec<u8>> { pub(crate) async fn _into_bytes(mut self) -> io::Result<Vec<u8>> {
self.response.body_bytes().await self.response.body_mut().to_bytes().await
} }
// Generates the public API methods, which call the private methods above. // Generates the public API methods, which call the private methods above.
@ -126,12 +126,7 @@ impl AsyncRead for LocalResponse<'_> {
cx: &mut Context<'_>, cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>, buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> { ) -> Poll<io::Result<()>> {
let body = match self.response.body_mut() { Pin::new(self.response.body_mut()).poll_read(cx, buf)
Some(body) => body,
_ => return Poll::Ready(Ok(()))
};
Pin::new(body.as_reader()).poll_read(cx, buf)
} }
} }

View File

@ -37,7 +37,7 @@ use super::Client;
/// ///
/// // Check metadata validity. /// // Check metadata validity.
/// assert_eq!(response.status(), Status::Ok); /// assert_eq!(response.status(), Status::Ok);
/// assert_eq!(response.body().unwrap().known_size(), Some(13)); /// assert_eq!(response.body().preset_size(), Some(13));
/// ///
/// // Read 10 bytes of the body. Note: in reality, we'd use `into_string()`. /// // Read 10 bytes of the body. Note: in reality, we'd use `into_string()`.
/// let mut buffer = [0; 10]; /// let mut buffer = [0; 10];
@ -63,11 +63,11 @@ impl LocalResponse<'_> {
self.inner._cookies() self.inner._cookies()
} }
fn _into_string(self) -> Option<String> { fn _into_string(self) -> io::Result<String> {
self.client.block_on(self.inner._into_string()) self.client.block_on(self.inner._into_string())
} }
fn _into_bytes(self) -> Option<Vec<u8>> { fn _into_bytes(self) -> io::Result<Vec<u8>> {
self.client.block_on(self.inner._into_bytes()) self.client.block_on(self.inner._into_bytes())
} }

View File

@ -55,11 +55,13 @@ macro_rules! pub_response_impl {
} }
getter_method!($doc_prelude, "response body, if there is one,", getter_method!($doc_prelude, "response body, if there is one,",
body -> Option<&crate::response::ResponseBody<'_>>); body -> &crate::response::Body<'_>);
/// Consumes `self` and reads the entirety of its body into a string. If /// Consumes `self` and reads the entirety of its body into a string.
/// `self` doesn't have a body, reading fails, or string conversion (for ///
/// non-UTF-8 bodies) fails, returns `None`. /// If reading fails, the body contains invalid UTF-8 characters, or the
/// body is unset in the response, returns `None`. Otherwise, returns
/// `Some`. The string may be empty if the body is empty.
/// ///
/// # Example /// # Example
/// ///
@ -73,11 +75,19 @@ macro_rules! pub_response_impl {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub $($prefix)? fn into_string(self) -> Option<String> { pub $($prefix)? fn into_string(self) -> Option<String> {
self._into_string() $(.$suffix)? if self._response().body().is_none() {
return None;
} }
/// Consumes `self` and reads the entirety of its body into a `Vec` of `u8` self._into_string() $(.$suffix)? .ok()
/// bytes. If `self` doesn't have a body or reading fails, returns `None`. }
/// Consumes `self` and reads the entirety of its body into a `Vec` of
/// bytes.
///
/// If reading fails or the body is unset in the response, return `None`.
/// Otherwise, returns `Some`. The returned vector may be empty if the body
/// is empty.
/// ///
/// # Example /// # Example
/// ///
@ -91,7 +101,11 @@ macro_rules! pub_response_impl {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub $($prefix)? fn into_bytes(self) -> Option<Vec<u8>> { pub $($prefix)? fn into_bytes(self) -> Option<Vec<u8>> {
self._into_bytes() $(.$suffix)? if self._response().body().is_none() {
return None;
}
self._into_bytes() $(.$suffix)? .ok()
} }
#[cfg(test)] #[cfg(test)]

View File

@ -0,0 +1,423 @@
use std::{io, fmt};
use std::task::{Context, Poll};
use std::pin::Pin;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, ReadBuf};
/// The body of a [`Response`].
///
/// A `Body` is never created directly, but instead, through the following
/// methods on `Response` and `Builder`:
///
/// * [`Builder::sized_body()`]
/// * [`Response::set_sized_body()`]
/// * [`Builder::streamed_body()`]
/// * [`Response::set_streamed_body()`]
///
/// [`Response`]: crate::Response
/// [`Builder`]: crate::response::Builder
/// [`Response::set_sized_body()`]: crate::Response::set_sized_body
/// [`Response::set_streamed_body()`]: crate::Response::set_streamed_body
/// [`Builder::sized_body()`]: crate::response::Builder::sized_body
/// [`Builder::streamed_body()`]: crate::response::Builder::streamed_body
///
/// An unset body in a `Response` begins as a [`Body::default()`], a `None`
/// body with a preset size of `0`.
///
/// # Sizing
///
/// A response body may be sized or unsized ("streamed"). A "sized" body is
/// transferred with a `Content-Length` equal to its size while an "unsized"
/// body is chunk-encoded. The body data is streamed in _all_ cases and is never
/// buffered in memory beyond a minimal amount for efficient transmission.
///
/// ## Sized
///
/// A sized body may have a _preset_ size ([`Body::preset_size()`]) or may have
/// its size computed on the fly by seeking ([`Body::size()`]). As such, sized
/// bodies must implement [`AsyncSeek`]. If a body does not have a preset size
/// and the fails to be computed dynamically, a sized body is treated as an
/// unsized body when written out to the network.
///
/// ## Unsized
///
/// An unsized body's data is streamed as it arrives. In otherwords, as soon as
/// the body's [`AsyncRead`] implementation returns bytes, the bytes are written
/// to the network. Individual unsized bodies may use an internal buffer to
/// curtail writes to the network.
///
/// The maximum number of bytes written to the network at once is controlled via
/// the [`Body::max_chunk_size()`] parameter which can be set via
/// [`Response::set_max_chunk_size()`] and [`Builder::max_chunk_size()`].
///
/// [`Response::set_max_chunk_size()`]: crate::Response::set_max_chunk_size
/// [`Builder::max_chunk_size()`]: crate::response::Builder::max_chunk_size
///
/// # Reading
///
/// The contents of a body, decoded, can be read through [`Body::to_bytes()`],
/// [`Body::to_string()`], or directly though `Body`'s [`AsyncRead`]
/// implementation.
#[derive(Debug)]
pub struct Body<'r> {
/// The size of the body, if it is known.
size: Option<usize>,
/// The body itself.
inner: Inner<'r>,
/// The maximum chunk size.
max_chunk: usize,
}
/// A "trait alias" of sorts so we can use `AsyncRead + AsyncSeek` in `dyn`.
pub trait AsyncReadSeek: AsyncRead + AsyncSeek { }
/// Implemented for all `AsyncRead + AsyncSeek`, of course.
impl<T: AsyncRead + AsyncSeek> AsyncReadSeek for T { }
/// A pinned `AsyncRead + AsyncSeek` body type.
type SizedBody<'r> = Pin<Box<dyn AsyncReadSeek + Send + 'r>>;
/// A pinned `AsyncRead` (not `AsyncSeek`) body type.
type UnsizedBody<'r> = Pin<Box<dyn AsyncRead + Send + 'r>>;
enum Inner<'r> {
/// A body that can be seeked to determine it's size.
Seekable(SizedBody<'r>),
/// A body that has no known size.
Unsized(UnsizedBody<'r>),
/// A body that "exists" but only for metadata calculations.
Phantom(SizedBody<'r>),
/// An empty body: no body at all.
None,
}
impl Default for Body<'_> {
fn default() -> Self {
Body {
size: Some(0),
inner: Inner::None,
max_chunk: Body::DEFAULT_MAX_CHUNK,
}
}
}
impl<'r> Body<'r> {
/// The default max size, in bytes, of chunks for streamed responses.
///
/// The present value is `4096`.
pub const DEFAULT_MAX_CHUNK: usize = 4096;
pub(crate) fn with_sized<T>(body: T, preset_size: Option<usize>) -> Self
where T: AsyncReadSeek + Send + 'r
{
Body {
size: preset_size,
inner: Inner::Seekable(Box::pin(body)),
max_chunk: Body::DEFAULT_MAX_CHUNK,
}
}
pub(crate) fn with_unsized<T>(body: T) -> Self
where T: AsyncRead + Send + 'r
{
Body {
size: None,
inner: Inner::Unsized(Box::pin(body)),
max_chunk: Body::DEFAULT_MAX_CHUNK,
}
}
pub(crate) fn set_max_chunk_size(&mut self, max_chunk: usize) {
self.max_chunk = max_chunk;
}
pub(crate) fn strip(&mut self) {
let body = std::mem::replace(self, Body::default());
*self = match body.inner {
Inner::Seekable(b) | Inner::Phantom(b) => Body {
size: body.size,
inner: Inner::Phantom(b),
max_chunk: body.max_chunk,
},
Inner::Unsized(_) | Inner::None => Body::default()
};
}
/// Returns `true` if the body is `None` or unset, the default.
///
/// # Example
///
/// ```rust
/// use rocket::response::Response;
///
/// let r = Response::build().finalize();
/// assert!(r.body().is_none());
/// ```
#[inline(always)]
pub fn is_none(&self) -> bool {
matches!(self.inner, Inner::None)
}
/// Returns `true` if the body is _not_ `None`, anything other than the
/// default.
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::response::Response;
///
/// let body = "Brewing the best coffee!";
/// let r = Response::build()
/// .sized_body(body.len(), Cursor::new(body))
/// .finalize();
///
/// assert!(r.body().is_some());
/// ```
#[inline(always)]
pub fn is_some(&self) -> bool {
!self.is_none()
}
/// A body's preset size, which may have been computed by a previous call to
/// [`Body::size()`].
///
/// Unsized bodies _always_ return `None`, while sized bodies return `Some`
/// if the body size was supplied directly on creation or a call to
/// [`Body::size()`] successfully computed the size and `None` otherwise.
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::response::Response;
///
/// # rocket::async_test(async {
/// let body = "Brewing the best coffee!";
/// let r = Response::build()
/// .sized_body(body.len(), Cursor::new(body))
/// .finalize();
///
/// // This will _always_ return `Some`.
/// assert_eq!(r.body().preset_size(), Some(body.len()));
///
/// let r = Response::build()
/// .streamed_body(Cursor::new(body))
/// .finalize();
///
/// // This will _never_ return `Some`.
/// assert_eq!(r.body().preset_size(), None);
///
/// let mut r = Response::build()
/// .sized_body(None, Cursor::new(body))
/// .finalize();
///
/// // This returns `Some` only after a call to `size()`.
/// assert_eq!(r.body().preset_size(), None);
/// assert_eq!(r.body_mut().size().await, Some(body.len()));
/// assert_eq!(r.body().preset_size(), Some(body.len()));
/// # });
/// ```
pub fn preset_size(&self) -> Option<usize> {
self.size
}
/// Returns the maximum chunk size for chunked transfers.
///
/// If none is explicitly set, defaults to [`Body::DEFAULT_MAX_CHUNK`].
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::response::{Response, Body};
///
/// let body = "Brewing the best coffee!";
/// let r = Response::build()
/// .sized_body(body.len(), Cursor::new(body))
/// .finalize();
///
/// assert_eq!(r.body().max_chunk_size(), Body::DEFAULT_MAX_CHUNK);
///
/// let r = Response::build()
/// .sized_body(body.len(), Cursor::new(body))
/// .max_chunk_size(1024)
/// .finalize();
///
/// assert_eq!(r.body().max_chunk_size(), 1024);
/// ```
pub fn max_chunk_size(&self) -> usize {
self.max_chunk
}
/// Attempts to compute the body's size and returns it if the body is sized.
///
/// If the size was preset (see [`Body::preset_size()`]), the value is
/// returned immediately as `Some`. If the body is unsized or computing the
/// size fails, returns `None`. Otherwise, the size is computed by seeking,
/// and the `preset_size` is updated to reflect the known value.
///
/// **Note:** the number of bytes read from the reader and/or written to the
/// network may differ from the value returned by this method. Some examples
/// include:
///
/// * bodies in response to `HEAD` requests are never read or written
/// * the client may close the connection before the body is read fully
/// * reading the body may fail midway
/// * a preset size may differ from the actual body size
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::response::Response;
///
/// # rocket::async_test(async {
/// let body = "Hello, Rocketeers!";
/// let mut r = Response::build()
/// .sized_body(None, Cursor::new(body))
/// .finalize();
///
/// assert_eq!(r.body().preset_size(), None);
/// assert_eq!(r.body_mut().size().await, Some(body.len()));
/// assert_eq!(r.body().preset_size(), Some(body.len()));
/// # });
/// ```
pub async fn size(&mut self) -> Option<usize> {
if let Some(size) = self.size {
return Some(size);
}
if let Inner::Seekable(ref mut body) | Inner::Phantom(ref mut body) = self.inner {
let pos = body.seek(io::SeekFrom::Current(0)).await.ok()?;
let end = body.seek(io::SeekFrom::End(0)).await.ok()?;
body.seek(io::SeekFrom::Start(pos)).await.ok()?;
let size = end as usize - pos as usize;
self.size = Some(size);
return Some(size);
}
None
}
/// Moves the body out of `self` and returns it, leaving a
/// [`Body::default()`] in its place.
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::response::Response;
///
/// let mut r = Response::build()
/// .sized_body(None, Cursor::new("Hi"))
/// .finalize();
///
/// assert!(r.body().is_some());
///
/// let body = r.body_mut().take();
/// assert!(body.is_some());
/// assert!(r.body().is_none());
/// ```
#[inline(always)]
pub fn take(&mut self) -> Self {
std::mem::replace(self, Body::default())
}
/// Reads all of `self` into a vector of bytes, consuming the contents.
///
/// If reading fails, returns `Err`. Otherwise, returns `Ok`. Calling this
/// method may partially or fully consume the body's content. As such,
/// subsequent calls to `to_bytes()` will likely return different result.
///
/// # Example
///
/// ```rust
/// use std::io;
/// use rocket::response::Response;
///
/// # let ok: io::Result<()> = rocket::async_test(async {
/// let mut r = Response::build()
/// .streamed_body(io::Cursor::new(&[1, 2, 3, 11, 13, 17]))
/// .finalize();
///
/// assert_eq!(r.body_mut().to_bytes().await?, &[1, 2, 3, 11, 13, 17]);
/// # Ok(())
/// # });
/// # assert!(ok.is_ok());
/// ```
pub async fn to_bytes(&mut self) -> io::Result<Vec<u8>> {
let mut vec = Vec::new();
let n = match self.read_to_end(&mut vec).await {
Ok(n) => n,
Err(e) => {
error_!("Error reading body: {:?}", e);
return Err(e);
}
};
if let Some(ref mut size) = self.size {
*size = size.checked_sub(n).unwrap_or(0);
}
Ok(vec)
}
/// Reads all of `self` into a string, consuming the contents.
///
/// If reading fails, or the body contains invalid UTF-8 characters, returns
/// `Err`. Otherwise, returns `Ok`. Calling this method may partially or
/// fully consume the body's content. As such, subsequent calls to
/// `to_string()` will likely return different result.
///
/// # Example
///
/// ```rust
/// use std::io;
/// use rocket::response::Response;
///
/// # let ok: io::Result<()> = rocket::async_test(async {
/// let mut r = Response::build()
/// .streamed_body(io::Cursor::new("Hello, Rocketeers!"))
/// .finalize();
///
/// assert_eq!(r.body_mut().to_string().await?, "Hello, Rocketeers!");
/// # Ok(())
/// # });
/// # assert!(ok.is_ok());
/// ```
pub async fn to_string(&mut self) -> io::Result<String> {
String::from_utf8(self.to_bytes().await?).map_err(|e| {
error_!("Body is invalid UTF-8: {}", e);
io::Error::new(io::ErrorKind::InvalidData, e)
})
}
}
impl AsyncRead for Body<'_> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let reader = match self.inner {
Inner::Seekable(ref mut b) => b as &mut (dyn AsyncRead + Unpin),
Inner::Unsized(ref mut b) => b as &mut (dyn AsyncRead + Unpin),
Inner::Phantom(_) | Inner::None => return Poll::Ready(Ok(())),
};
Pin::new(reader).poll_read(cx, buf)
}
}
impl fmt::Debug for Inner<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Inner::Seekable(_) => "seekable".fmt(f),
Inner::Unsized(_) => "unsized".fmt(f),
Inner::Phantom(_) => "phantom".fmt(f),
Inner::None => "none".fmt(f),
}
}
}

View File

@ -26,24 +26,24 @@
mod responder; mod responder;
mod redirect; mod redirect;
mod named_file; mod named_file;
mod stream;
mod response; mod response;
mod debug; mod debug;
mod body;
pub(crate) mod flash; pub(crate) mod flash;
pub mod content; pub mod content;
pub mod status; pub mod status;
#[doc(hidden)] pub use rocket_codegen::Responder; #[doc(hidden)]
pub use rocket_codegen::Responder;
pub use self::response::DEFAULT_CHUNK_SIZE; pub use self::response::{Response, Builder};
pub use self::response::{Response, ResponseBody, ResponseBuilder, Body}; pub use self::body::Body;
pub use self::responder::Responder; pub use self::responder::Responder;
pub use self::redirect::Redirect; pub use self::redirect::Redirect;
pub use self::flash::Flash; pub use self::flash::Flash;
pub use self::named_file::NamedFile; pub use self::named_file::NamedFile;
pub use self::stream::Stream;
pub use self::debug::Debug; pub use self::debug::Debug;
/// Type alias for the `Result` of a [`Responder::respond_to()`] call. /// Type alias for the `Result` of a [`Responder::respond_to()`] call.

View File

@ -1,152 +1,23 @@
use std::{io, fmt, str}; use std::{fmt, str};
use std::borrow::Cow; use std::borrow::Cow;
use std::pin::Pin;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt}; use tokio::io::{AsyncRead, AsyncSeek};
use crate::response::{self, Responder};
use crate::http::{Header, HeaderMap, Status, ContentType, Cookie}; use crate::http::{Header, HeaderMap, Status, ContentType, Cookie};
use crate::response::Body;
/// The default size, in bytes, of a chunk for streamed responses. /// Builder for the [`Response`] type.
pub const DEFAULT_CHUNK_SIZE: usize = 4096;
/// The body of a response: can be sized or streamed/chunked.
pub enum Body<A, B> {
/// A fixed-size body.
Sized(A, Option<usize>),
/// A streamed/chunked body, akin to `Transfer-Encoding: chunked`.
Chunked(B, usize)
}
impl<A, B> Body<A, B> {
/// Returns a new `Body` with a mutable borrow to `self`'s inner type.
pub fn as_mut(&mut self) -> Body<&mut A, &mut B> {
match *self {
Body::Sized(ref mut a, n) => Body::Sized(a, n),
Body::Chunked(ref mut b, n) => Body::Chunked(b, n)
}
}
/// Consumes `self`. Passes the inner types as parameter to `f1` and `f2`
/// and constructs a new body with the values returned from calls to the
/// functions. The size or chunk size of the body is copied into the new
/// `Body`.
pub fn map<U, F1: FnOnce(A) -> U, F2: FnOnce(B) -> U>(self, f1: F1, f2: F2) -> Body<U, U> {
match self {
Body::Sized(a, n) => Body::Sized(f1(a), n),
Body::Chunked(b, n) => Body::Chunked(f2(b), n)
}
}
/// Returns `true` if `self` is a `Body::Sized`.
pub fn is_sized(&self) -> bool {
match *self {
Body::Sized(..) => true,
Body::Chunked(..) => false,
}
}
/// Returns `true` if `self` is a `Body::Chunked`.
pub fn is_chunked(&self) -> bool {
match *self {
Body::Chunked(..) => true,
Body::Sized(..) => false,
}
}
}
impl<T> Body<T, T> {
/// Consumes `self` and returns the inner body.
pub fn into_inner(self) -> T {
match self {
Body::Sized(b, _) | Body::Chunked(b, _) => b
}
}
}
impl<A, B> Body<A, B>
where A: AsyncRead + AsyncSeek + Send + Unpin,
B: AsyncRead + Send + Unpin
{
pub fn known_size(&self) -> Option<usize> {
match self {
Body::Sized(_, Some(known)) => Some(*known),
_ => None
}
}
/// Attempts to compute the size of `self` if it is `Body::Sized`. If it is
/// not, simply returned `None`. Also returned `None` if determining the
/// body's size failed.
pub async fn size(&mut self) -> Option<usize> {
if let Body::Sized(body, size) = self {
match *size {
Some(size) => Some(size),
None => async {
let pos = body.seek(io::SeekFrom::Current(0)).await.ok()?;
let end = body.seek(io::SeekFrom::End(0)).await.ok()?;
body.seek(io::SeekFrom::Start(pos)).await.ok()?;
Some(end as usize - pos as usize)
}.await
}
} else {
None
}
}
/// Monomorphizes the internal readers into a single `&mut (dyn AsyncRead +
/// Send + Unpin)`.
pub fn as_reader(&mut self) -> &mut (dyn AsyncRead + Send + Unpin) {
type Reader<'a> = &'a mut (dyn AsyncRead + Send + Unpin);
self.as_mut().map(|a| a as Reader<'_>, |b| b as Reader<'_>).into_inner()
}
/// Attempts to read `self` into a `Vec` and returns it. If reading fails,
/// returns `None`.
pub async fn into_bytes(mut self) -> Option<Vec<u8>> {
let mut vec = Vec::new();
if let Err(e) = self.as_reader().read_to_end(&mut vec).await {
error_!("Error reading body: {:?}", e);
return None;
}
Some(vec)
}
/// Attempts to read `self` into a `String` and returns it. If reading or
/// conversion fails, returns `None`.
pub async fn into_string(self) -> Option<String> {
self.into_bytes().await.and_then(|bytes| match String::from_utf8(bytes) {
Ok(string) => Some(string),
Err(e) => {
error_!("Body is invalid UTF-8: {}", e);
None
}
})
}
}
impl<A, B> fmt::Debug for Body<A, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Body::Sized(_, n) => writeln!(f, "Sized Body [{:?} bytes]", n),
Body::Chunked(_, n) => writeln!(f, "Chunked Body [{} bytes]", n),
}
}
}
/// 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
/// higher-level API that simplifies building `Response`s. /// higher-level API that simplifies building `Response`s.
/// ///
/// # Usage /// # Usage
/// ///
/// `ResponseBuilder` follows the builder pattern and is usually obtained by /// `Builder` follows the builder pattern and is usually obtained by calling
/// calling [`Response::build()`] on `Response`. Almost all methods take the /// [`Response::build()`] on `Response`. Almost all methods take the current
/// current builder as a mutable reference and return the same mutable reference /// builder as a mutable reference and return the same mutable reference with
/// with field(s) modified in the `Responder` being built. These method calls /// field(s) modified in the `Response` being built. These method calls can be
/// can be chained: `build.a().b()`. /// chained: `build.a().b()`.
/// ///
/// To finish building and retrieve the built `Response`, use the /// To finish building and retrieve the built `Response`, use the
/// [`finalize()`](#method.finalize) or [`ok()`](#method.ok) methods. /// [`finalize()`](#method.finalize) or [`ok()`](#method.ok) methods.
@ -194,25 +65,25 @@ impl<A, B> fmt::Debug for Body<A, B> {
/// .sized_body(body.len(), Cursor::new(body)) /// .sized_body(body.len(), Cursor::new(body))
/// .finalize(); /// .finalize();
/// ``` /// ```
pub struct ResponseBuilder<'r> { pub struct Builder<'r> {
response: Response<'r>, response: Response<'r>,
} }
impl<'r> ResponseBuilder<'r> { impl<'r> Builder<'r> {
/// Creates a new `ResponseBuilder` that will build on top of the `base` /// Creates a new `Builder` that will build on top of the `base`
/// `Response`. /// `Response`.
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use rocket::response::{ResponseBuilder, Response}; /// use rocket::response::{Builder, Response};
/// ///
/// # #[allow(unused_variables)] /// # #[allow(unused_variables)]
/// let builder = ResponseBuilder::new(Response::new()); /// let builder = Builder::new(Response::new());
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new(base: Response<'r>) -> ResponseBuilder<'r> { pub fn new(base: Response<'r>) -> Builder<'r> {
ResponseBuilder { Builder {
response: base, response: base,
} }
} }
@ -230,7 +101,7 @@ impl<'r> ResponseBuilder<'r> {
/// .finalize(); /// .finalize();
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn status(&mut self, status: Status) -> &mut ResponseBuilder<'r> { pub fn status(&mut self, status: Status) -> &mut Builder<'r> {
self.response.set_status(status); self.response.set_status(status);
self self
} }
@ -251,7 +122,7 @@ impl<'r> ResponseBuilder<'r> {
/// assert_eq!(response.status(), Status::new(699, "Alien Encounter")); /// 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 Builder<'r> {
self.response.set_raw_status(code, reason); self.response.set_raw_status(code, reason);
self self
} }
@ -279,7 +150,7 @@ impl<'r> ResponseBuilder<'r> {
/// 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 Builder<'r>
where H: Into<Header<'h>> where H: Into<Header<'h>>
{ {
self.response.set_header(header); self.response.set_header(header);
@ -310,7 +181,7 @@ impl<'r> ResponseBuilder<'r> {
/// 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 Builder<'r>
where H: Into<Header<'h>> where H: Into<Header<'h>>
{ {
self.response.adjoin_header(header); self.response.adjoin_header(header);
@ -335,7 +206,7 @@ impl<'r> ResponseBuilder<'r> {
/// 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 Builder<'r>
where N: Into<Cow<'a, str>>, V: Into<Cow<'b, str>>, 'a: 'r, 'b: 'r where N: Into<Cow<'a, str>>, V: Into<Cow<'b, str>>, 'a: 'r, 'b: 'r
{ {
self.response.set_raw_header(name, value); self.response.set_raw_header(name, value);
@ -361,7 +232,7 @@ impl<'r> ResponseBuilder<'r> {
/// 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 Builder<'r>
where N: Into<Cow<'a, str>>, V: Into<Cow<'b, str>>, 'a: 'r, 'b: 'r where N: Into<Cow<'a, str>>, V: Into<Cow<'b, str>>, 'a: 'r, 'b: 'r
{ {
self.response.adjoin_raw_header(name, value); self.response.adjoin_raw_header(name, value);
@ -370,8 +241,7 @@ impl<'r> ResponseBuilder<'r> {
/// Sets the body of the `Response` to be the fixed-sized `body` with size /// Sets the body of the `Response` to be the fixed-sized `body` with size
/// `size`, which may be `None`. If `size` is `None`, the body's size will /// `size`, which may be `None`. If `size` is `None`, the body's size will
/// be computing with calls to `seek` just before being written out in a /// be computed with calls to `seek` when the response is written out.
/// response.
/// ///
/// # Example /// # Example
/// ///
@ -384,8 +254,8 @@ impl<'r> ResponseBuilder<'r> {
/// .sized_body(body.len(), Cursor::new(body)) /// .sized_body(body.len(), Cursor::new(body))
/// .finalize(); /// .finalize();
/// ``` /// ```
pub fn sized_body<B, S>(&mut self, size: S, body: B) -> &mut ResponseBuilder<'r> pub fn sized_body<B, S>(&mut self, size: S, body: B) -> &mut Builder<'r>
where B: AsyncRead + AsyncSeek + Send + Unpin + 'r, where B: AsyncRead + AsyncSeek + Send + 'r,
S: Into<Option<usize>> S: Into<Option<usize>>
{ {
self.response.set_sized_body(size, body); self.response.set_sized_body(size, body);
@ -405,60 +275,31 @@ impl<'r> ResponseBuilder<'r> {
/// .finalize(); /// .finalize();
/// ``` /// ```
#[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 Builder<'r>
where B: AsyncRead + Send + 'r where B: AsyncRead + Send + 'r
{ {
self.response.set_streamed_body(body); self.response.set_streamed_body(body);
self self
} }
/// Sets the body of the `Response` to be the streamed `body` with a custom /// Sets the max chunk size of a body, if any, to `size`.
/// chunk size, in bytes.
/// ///
/// # Example /// See [`Response::set_max_chunk_size()`] for notes.
///
/// ```rust
/// use rocket::Response;
/// use tokio::fs::File;
/// # use std::io;
///
/// # async fn test<'r>() -> io::Result<Response<'r>> {
/// let response = Response::build()
/// .chunked_body(File::open("body.txt").await?, 8096)
/// .ok();
/// # response
/// # }
/// ```
#[inline(always)]
pub fn chunked_body<B>(&mut self, body: B, chunk_size: usize) -> &mut ResponseBuilder<'r>
where B: AsyncRead + Send + 'r
{
self.response.set_chunked_body(body, chunk_size);
self
}
/// Sets the body of `self` to be `body`. This method should typically not
/// be used, opting instead for one of `sized_body`, `streamed_body`, or
/// `chunked_body`.
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use std::io::Cursor; /// use std::io::Cursor;
/// use rocket::response::{Response, Body}; /// use rocket::Response;
/// ///
/// let s = "Hello!";
/// let body = Body::Sized(Cursor::new(s), Some(s.len()));
/// let response = Response::build() /// let response = Response::build()
/// .raw_body::<Cursor<&'static str>, Cursor<&'static str>>(body) /// .streamed_body(Cursor::new("Hello, world!"))
/// .max_chunk_size(3072)
/// .finalize(); /// .finalize();
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn raw_body<S, C>(&mut self, body: Body<S, C>) -> &mut ResponseBuilder<'r> pub fn max_chunk_size(&mut self, size: usize) -> &mut Builder<'r> {
where S: AsyncRead + AsyncSeek + Send + Unpin + 'r, self.response.set_max_chunk_size(size);
C: AsyncRead + Send + Unpin + 'r
{
self.response.set_raw_body(body);
self self
} }
@ -496,7 +337,7 @@ impl<'r> ResponseBuilder<'r> {
/// 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, other: Response<'r>) -> &mut Builder<'r> {
self.response.merge(other); self.response.merge(other);
self self
} }
@ -536,7 +377,7 @@ impl<'r> ResponseBuilder<'r> {
/// 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 Builder<'r> {
self.response.join(other); self.response.join(other);
self self
} }
@ -584,20 +425,15 @@ impl<'r> ResponseBuilder<'r> {
} }
} }
pub trait AsyncReadSeek: AsyncRead + AsyncSeek { } /// A response, as returned by types implementing
impl<T: AsyncRead + AsyncSeek> AsyncReadSeek for T { } /// [`Responder`](crate::response::Responder).
///
pub type ResponseBody<'r> = Body< /// See [`Builder`] for docs on how a `Response` is typically created.
Pin<Box<dyn AsyncReadSeek + Send + 'r>>,
Pin<Box<dyn AsyncRead + Send + 'r>>
>;
/// A response, as returned by types implementing [`Responder`].
#[derive(Default)] #[derive(Default)]
pub struct Response<'r> { pub struct Response<'r> {
status: Option<Status>, status: Option<Status>,
headers: HeaderMap<'r>, headers: HeaderMap<'r>,
body: Option<ResponseBody<'r>>, body: Body<'r>,
} }
impl<'r> Response<'r> { impl<'r> Response<'r> {
@ -620,14 +456,10 @@ impl<'r> Response<'r> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new() -> Response<'r> { pub fn new() -> Response<'r> {
Response { Response::default()
status: None,
headers: HeaderMap::new(),
body: None,
}
} }
/// Returns a `ResponseBuilder` with a base of `Response::new()`. /// Returns a `Builder` with a base of `Response::new()`.
/// ///
/// # Example /// # Example
/// ///
@ -638,11 +470,11 @@ impl<'r> Response<'r> {
/// let builder = Response::build(); /// let builder = Response::build();
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn build() -> ResponseBuilder<'r> { pub fn build() -> Builder<'r> {
Response::build_from(Response::new()) Response::build_from(Response::new())
} }
/// Returns a `ResponseBuilder` with a base of `other`. /// Returns a `Builder` with a base of `other`.
/// ///
/// # Example /// # Example
/// ///
@ -654,8 +486,8 @@ impl<'r> Response<'r> {
/// let builder = Response::build_from(other); /// let builder = Response::build_from(other);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn build_from(other: Response<'r>) -> ResponseBuilder<'r> { pub fn build_from(other: Response<'r>) -> Builder<'r> {
ResponseBuilder::new(other) Builder::new(other)
} }
/// Returns the status of `self`. /// Returns the status of `self`.
@ -925,8 +757,8 @@ impl<'r> Response<'r> {
/// # }) /// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn body(&self) -> Option<&ResponseBody<'r>> { pub fn body(&self) -> &Body<'r> {
self.body.as_ref() &self.body
} }
/// Returns a mutable borrow of the body of `self`, if there is one. A /// Returns a mutable borrow of the body of `self`, if there is one. A
@ -944,112 +776,20 @@ impl<'r> Response<'r> {
/// ///
/// let string = "Hello, world!"; /// let string = "Hello, world!";
/// response.set_sized_body(string.len(), Cursor::new(string)); /// response.set_sized_body(string.len(), Cursor::new(string));
/// assert!(response.body_mut().is_some()); /// let string = response.body_mut().to_string().await;
/// assert_eq!(string.unwrap(), "Hello, world!");
/// # }) /// # })
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn body_mut(&mut self) -> Option<&mut ResponseBody<'r>> { pub fn body_mut(&mut self) -> &mut Body<'r> {
self.body.as_mut() &mut self.body
} }
/// Consumes `self's` body and reads it into a string. If `self` doesn't // Makes the `AsyncRead`er in the body empty but leaves the size of the body
/// have a body, reading fails, or string conversion (for non-UTF-8 bodies) // if it exists. Meant to be used during HEAD handling.
/// fails, returns `None`. Note that `self`'s `body` is consumed after a
/// call to this method.
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::Response;
///
/// # rocket::async_test(async {
/// let mut response = Response::new();
/// assert!(response.body().is_none());
///
/// let string = "Hello, world!";
/// response.set_sized_body(string.len(), Cursor::new(string));
/// assert_eq!(response.body_string().await, Some("Hello, world!".to_string()));
/// assert!(response.body().is_none());
/// # })
/// ```
// NOTE: We _could_ return an impl Future bounded by the looser `r instead!
pub async fn body_string(&mut self) -> Option<String> {
match self.take_body() {
Some(body) => body.into_string().await,
None => None,
}
}
/// Consumes `self's` body and reads it into a `Vec` of `u8` bytes. If
/// `self` doesn't have a body or reading fails returns `None`. Note that
/// `self`'s `body` is consumed after a call to this method.
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::Response;
///
/// # rocket::async_test(async {
/// let mut response = Response::new();
/// assert!(response.body().is_none());
///
/// let string = "hi!";
/// response.set_sized_body(string.len(), Cursor::new(string));
/// assert_eq!(response.body_bytes().await, Some(vec![0x68, 0x69, 0x21]));
/// assert!(response.body().is_none());
/// # })
/// ```
// NOTE: We _could_ return an impl Future bounded by the looser `r instead!
pub async fn body_bytes(&mut self) -> Option<Vec<u8>> {
match self.take_body() {
Some(body) => body.into_bytes().await,
None => None,
}
}
/// Moves the body of `self` out and returns it, if there is one, leaving no
/// body in its place.
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::Response;
///
/// # rocket::async_test(async {
/// let mut response = Response::new();
/// assert!(response.body().is_none());
///
/// let string = "Hello, world!";
/// response.set_sized_body(string.len(), Cursor::new(string));
/// assert!(response.body().is_some());
///
/// let body = response.take_body();
/// let body_string = match body {
/// Some(b) => b.into_string().await,
/// None => None,
/// };
/// assert_eq!(body_string, Some("Hello, world!".to_string()));
/// assert!(response.body().is_none());
/// # })
/// ```
#[inline(always)]
pub fn take_body(&mut self) -> Option<ResponseBody<'r>> {
self.body.take()
}
// Makes the `AsyncRead`er in the body empty but leaves the size of the body if
// it exists. Only meant to be used to handle HEAD requests automatically.
#[inline(always)] #[inline(always)]
pub(crate) fn strip_body(&mut self) { pub(crate) fn strip_body(&mut self) {
if let Some(body) = self.take_body() { self.body.strip();
self.body = match body {
Body::Sized(_, n) => Some(Body::Sized(Box::pin(io::Cursor::new(&[])), n)),
Body::Chunked(..) => None
};
}
} }
/// Sets the body of `self` to be the fixed-sized `body` with size /// Sets the body of `self` to be the fixed-sized `body` with size
@ -1060,48 +800,64 @@ impl<'r> Response<'r> {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use std::io::Cursor; /// use std::io;
/// use rocket::Response; /// use rocket::Response;
/// ///
/// # rocket::async_test(async { /// # let o: io::Result<()> = rocket::async_test(async {
/// let string = "Hello, world!"; /// let string = "Hello, world!";
/// ///
/// let mut response = Response::new(); /// let mut response = Response::new();
/// response.set_sized_body(string.len(), Cursor::new(string)); /// response.set_sized_body(string.len(), io::Cursor::new(string));
/// assert_eq!(response.body_string().await.unwrap(), "Hello, world!"); /// assert_eq!(response.body_mut().to_string().await?, "Hello, world!");
/// # }) /// # Ok(())
/// # });
/// # assert!(o.is_ok());
/// ``` /// ```
pub fn set_sized_body<B, S>(&mut self, size: S, body: B) pub fn set_sized_body<B, S>(&mut self, size: S, body: B)
where B: AsyncRead + AsyncSeek + Send + Unpin + 'r, where B: AsyncRead + AsyncSeek + Send + 'r,
S: Into<Option<usize>> S: Into<Option<usize>>
{ {
self.body = Some(Body::Sized(Box::pin(body), size.into())); self.body = Body::with_sized(body, size.into());
} }
/// Sets the body of `self` to be `body`, which will be streamed. The chunk /// Sets the body of `self` to `body`, which will be streamed.
/// size of the stream is ///
/// [DEFAULT_CHUNK_SIZE](crate::response::DEFAULT_CHUNK_SIZE). Use /// The max chunk size is configured via [`Response::set_max_chunk_size()`]
/// [set_chunked_body](#method.set_chunked_body) for custom chunk sizes. /// and defaults to [`Body::DEFAULT_MAX_CHUNK`].
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// # use std::io;
/// use tokio::io::{repeat, AsyncReadExt}; /// use tokio::io::{repeat, AsyncReadExt};
/// use rocket::Response; /// use rocket::Response;
/// ///
/// # rocket::async_test(async { /// # let o: io::Result<()> = rocket::async_test(async {
/// let mut response = Response::new(); /// let mut response = Response::new();
/// response.set_streamed_body(repeat(97).take(5)); /// response.set_streamed_body(repeat(97).take(5));
/// assert_eq!(response.body_string().await.unwrap(), "aaaaa"); /// assert_eq!(response.body_mut().to_string().await?, "aaaaa");
/// # }) /// # Ok(())
/// # });
/// # assert!(o.is_ok());
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn set_streamed_body<B>(&mut self, body: B) where B: AsyncRead + Send + 'r { pub fn set_streamed_body<B>(&mut self, body: B)
self.set_chunked_body(body, DEFAULT_CHUNK_SIZE); where B: AsyncRead + Send + 'r
{
self.body = Body::with_unsized(body);
} }
/// Sets the body of `self` to be `body`, which will be streamed with chunk /// Sets the body's maximum chunk size to `size` bytes.
/// size `chunk_size`. ///
/// The default max chunk size is [`Body::DEFAULT_MAX_CHUNK`]. The max chunk
/// size is a property of the body and is thus reset whenever a body is set
/// via [`Response::set_streamed_body()`], [`Response::set_sized_body()`],
/// or the corresponding builer methods.
///
/// This setting does not typically need to be changed. Configuring a high
/// value can result in high memory usage. Similarly, configuring a low
/// value can result in excessive network writes. When unsure, leave the
/// value unchanged.
/// ///
/// # Example /// # Example
/// ///
@ -1109,48 +865,16 @@ impl<'r> Response<'r> {
/// use tokio::io::{repeat, AsyncReadExt}; /// use tokio::io::{repeat, AsyncReadExt};
/// use rocket::Response; /// use rocket::Response;
/// ///
/// # rocket::async_test(async { /// # let o: Option<()> = rocket::async_test(async {
/// let mut response = Response::new(); /// let mut response = Response::new();
/// response.set_chunked_body(repeat(97).take(5), 10); /// response.set_streamed_body(repeat(97).take(5));
/// assert_eq!(response.body_string().await.unwrap(), "aaaaa"); /// response.set_max_chunk_size(3072);
/// # }) /// # Some(())
/// ``` /// # });
/// # assert!(o.is_some());
#[inline(always)] #[inline(always)]
pub fn set_chunked_body<B>(&mut self, body: B, chunk_size: usize) pub fn set_max_chunk_size(&mut self, size: usize) {
where B: AsyncRead + Send + 'r self.body_mut().set_max_chunk_size(size);
{
self.body = Some(Body::Chunked(Box::pin(body), chunk_size));
}
/// Sets the body of `self` to be `body`. This method should typically not
/// be used, opting instead for one of `set_sized_body`,
/// `set_streamed_body`, or `set_chunked_body`.
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::response::{Response, Body};
///
/// # rocket::async_test(async {
/// let string = "Hello!";
///
/// let mut response = Response::new();
/// let body = Body::Sized(Cursor::new(string), Some(string.len()));
/// response.set_raw_body::<Cursor<&'static str>, Cursor<&'static str>>(body);
///
/// assert_eq!(response.body_string().await.unwrap(), "Hello!");
/// # })
/// ```
#[inline(always)]
pub fn set_raw_body<S, C>(&mut self, body: Body<S, C>)
where S: AsyncRead + AsyncSeek + Send + Unpin + 'r,
C: AsyncRead + Send + Unpin + 'r
{
self.body = Some(match body {
Body::Sized(a, n) => Body::Sized(Box::pin(a), n),
Body::Chunked(b, n) => Body::Chunked(Box::pin(b), n),
});
} }
/// Replaces this response's status and body with that of `other`, if they /// Replaces this response's status and body with that of `other`, if they
@ -1189,8 +913,8 @@ impl<'r> Response<'r> {
self.status = Some(status); self.status = Some(status);
} }
if let Some(body) = other.body { if other.body().is_some() {
self.body = Some(body); self.body = other.body;
} }
for (name, values) in other.headers.into_iter_raw() { for (name, values) in other.headers.into_iter_raw() {
@ -1252,18 +976,6 @@ impl fmt::Debug for Response<'_> {
writeln!(f, "{}", header)?; writeln!(f, "{}", header)?;
} }
match self.body { self.body.fmt(f)
Some(ref body) => body.fmt(f),
None => writeln!(f, "Empty Body")
}
}
}
use crate::request::Request;
impl<'r, 'o: 'r> Responder<'r, 'o> for Response<'o> {
/// This is the identity implementation. It simply returns `Ok(self)`.
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
Ok(self)
} }
} }

View File

@ -147,31 +147,20 @@ impl Rocket<Orbit> {
}) })
}; };
match response.body_mut() { let body = response.body_mut();
None => { if let Some(n) = body.size().await {
hyp_res = hyp_res.header(hyper::header::CONTENT_LENGTH, 0); hyp_res = hyp_res.header(hyper::header::CONTENT_LENGTH, n);
send_response(hyp_res, hyper::Body::empty())?;
}
Some(body) => {
if let Some(s) = body.size().await {
hyp_res = hyp_res.header(hyper::header::CONTENT_LENGTH, s);
} }
let chunk_size = match *body { let max_chunk_size = body.max_chunk_size();
Body::Chunked(_, chunk_size) => chunk_size as usize,
Body::Sized(_, _) => crate::response::DEFAULT_CHUNK_SIZE,
};
let (mut sender, hyp_body) = hyper::Body::channel(); let (mut sender, hyp_body) = hyper::Body::channel();
send_response(hyp_res, hyp_body)?; send_response(hyp_res, hyp_body)?;
let mut stream = body.as_reader().into_bytes_stream(chunk_size); let mut stream = body.into_bytes_stream(max_chunk_size);
while let Some(next) = stream.next().await { while let Some(next) = stream.next().await {
sender.send_data(next?).await sender.send_data(next?).await
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
} }
}
};
Ok(()) Ok(())
} }

View File

@ -1,13 +1,13 @@
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
use rocket::Response;
use rocket::http::Header; use rocket::http::Header;
#[derive(Responder)]
struct HeaderOnly((), Header<'static>);
#[get("/do_not_overwrite")] #[get("/do_not_overwrite")]
fn do_not_overwrite() -> Response<'static> { fn do_not_overwrite() -> HeaderOnly {
Response::build() HeaderOnly((), Header::new("Server", "Test"))
.header(Header::new("Server", "Test"))
.finalize()
} }
#[get("/use_default")] #[get("/use_default")]

View File

@ -17,12 +17,12 @@ fn auto() -> &'static str {
mod fairing_before_head_strip { mod fairing_before_head_strip {
use super::*; use super::*;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::io::Cursor;
use rocket::fairing::AdHoc;
use rocket::http::Method;
use rocket::local::blocking::Client;
use rocket::http::Status;
use rocket::State; use rocket::State;
use rocket::fairing::AdHoc;
use rocket::local::blocking::Client;
use rocket::http::{Method, Status};
#[test] #[test]
fn not_auto_handled() { fn not_auto_handled() {
@ -36,14 +36,16 @@ mod fairing_before_head_strip {
.attach(AdHoc::on_response("Check HEAD 2", |req, res| { .attach(AdHoc::on_response("Check HEAD 2", |req, res| {
Box::pin(async move { Box::pin(async move {
assert_eq!(req.method(), Method::Head); assert_eq!(req.method(), Method::Head);
assert_eq!(res.body_string().await, Some(RESPONSE_STRING.into())); let body_bytes = res.body_mut().to_bytes().await.unwrap();
assert_eq!(body_bytes, RESPONSE_STRING.as_bytes());
res.set_sized_body(body_bytes.len(), Cursor::new(body_bytes));
}) })
})); }));
let client = Client::debug(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.head("/").dispatch(); let response = client.head("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert!(response.body().is_none()); assert!(response.into_string().unwrap_or_default().is_empty());
} }
#[test] #[test]
@ -67,13 +69,15 @@ mod fairing_before_head_strip {
.attach(AdHoc::on_response("Check GET", |req, res| { .attach(AdHoc::on_response("Check GET", |req, res| {
Box::pin(async move { Box::pin(async move {
assert_eq!(req.method(), Method::Get); assert_eq!(req.method(), Method::Get);
assert_eq!(res.body_string().await, Some(RESPONSE_STRING.into())); let body_bytes = res.body_mut().to_bytes().await.unwrap();
assert_eq!(body_bytes, RESPONSE_STRING.as_bytes());
res.set_sized_body(body_bytes.len(), Cursor::new(body_bytes));
}) })
})); }));
let client = Client::debug(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.head("/").dispatch(); let response = client.head("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert!(response.body().is_none()); assert!(response.into_string().unwrap_or_default().is_empty());
} }
} }

View File

@ -36,7 +36,7 @@ mod head_handling_tests {
let content_type: Vec<_> = response.headers().get("Content-Type").collect(); let content_type: Vec<_> = response.headers().get("Content-Type").collect();
assert_eq!(content_type, vec![ContentType::Plain.to_string()]); assert_eq!(content_type, vec![ContentType::Plain.to_string()]);
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body().unwrap().known_size(), Some(13)); assert_eq!(response.body().preset_size(), Some(13));
assert!(response.into_bytes().unwrap().is_empty()); assert!(response.into_bytes().unwrap().is_empty());
let response = client.head("/empty").dispatch(); let response = client.head("/empty").dispatch();
@ -52,7 +52,7 @@ mod head_handling_tests {
let content_type: Vec<_> = response.headers().get("Content-Type").collect(); let content_type: Vec<_> = response.headers().get("Content-Type").collect();
assert_eq!(content_type, vec![ContentType::JSON.to_string()]); assert_eq!(content_type, vec![ContentType::JSON.to_string()]);
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body().unwrap().known_size(), Some(17)); assert_eq!(response.body().preset_size(), Some(17));
assert!(response.into_bytes().unwrap().is_empty()); assert!(response.into_bytes().unwrap().is_empty());
} }
} }

View File

@ -86,14 +86,20 @@ These methods are typically used in combination with the `assert_eq!` or
# use std::io::Cursor; # use std::io::Cursor;
# use rocket::Response; # use rocket::Response;
# use rocket::http::Header; # use rocket::http::Header;
#
# #[derive(Responder)]
# #[response(content_type = "text")]
# struct Custom {
# body: &'static str,
# header: Header<'static>,
# }
#
# #[get("/")] # #[get("/")]
# fn hello() -> Response<'static> { # fn hello() -> Custom {
# Response::build() # Custom {
# .header(ContentType::Plain) # body: "Expected Body",
# .header(Header::new("X-Special", "")) # header: Header::new("X-Special", ""),
# .sized_body("Expected Body".len(), Cursor::new("Expected Body")) # }
# .finalize()
# } # }
# use rocket::local::blocking::Client; # use rocket::local::blocking::Client;
@ -106,7 +112,7 @@ let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain)); assert_eq!(response.content_type(), Some(ContentType::Plain));
assert!(response.headers().get_one("X-Special").is_some()); assert!(response.headers().get_one("X-Special").is_some());
assert_eq!(response.into_string(), Some("Expected Body".into())); assert_eq!(response.into_string().unwrap(), "Expected Body");
``` ```
## Testing "Hello, world!" ## Testing "Hello, world!"