From 63e6845386d638c15a9db5ce2436f3ddeef7a6ba Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Wed, 28 Apr 2021 01:51:51 -0700 Subject: [PATCH] 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. --- core/codegen/tests/responder.rs | 50 +- core/lib/src/local/asynchronous/response.rs | 17 +- core/lib/src/local/blocking/response.rs | 6 +- core/lib/src/local/response.rs | 30 +- core/lib/src/response/body.rs | 423 +++++++++++++++ core/lib/src/response/mod.rs | 10 +- core/lib/src/response/response.rs | 488 ++++-------------- core/lib/src/server.rs | 35 +- .../conditionally-set-server-header-996.rs | 10 +- .../fairing_before_head_strip-issue-546.rs | 20 +- core/lib/tests/head_handling.rs | 4 +- site/guide/8-testing.md | 22 +- 12 files changed, 629 insertions(+), 486 deletions(-) create mode 100644 core/lib/src/response/body.rs diff --git a/core/codegen/tests/responder.rs b/core/codegen/tests/responder.rs index 069b8141..e4aa84d2 100644 --- a/core/codegen/tests/responder.rs +++ b/core/codegen/tests/responder.rs @@ -25,37 +25,37 @@ async fn responder_foo() { let local_req = client.get("/"); let req = local_req.inner(); - let mut response = Foo::First("hello".into()) + let mut r = Foo::First("hello".into()) .respond_to(req) .expect("response okay"); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::Plain)); - assert_eq!(response.body_string().await, Some("hello".into())); + assert_eq!(r.status(), Status::Ok); + assert_eq!(r.content_type(), Some(ContentType::Plain)); + 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) .expect("response okay"); - assert_eq!(response.status(), Status::InternalServerError); - assert_eq!(response.content_type(), Some(ContentType::Binary)); - assert_eq!(response.body_string().await, Some("just a test".into())); + assert_eq!(r.status(), Status::InternalServerError); + assert_eq!(r.content_type(), Some(ContentType::Binary)); + 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) .expect("response okay"); - assert_eq!(response.status(), Status::NotFound); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - assert_eq!(response.body_string().await, Some("well, hi".into())); + assert_eq!(r.status(), Status::NotFound); + assert_eq!(r.content_type(), Some(ContentType::HTML)); + 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) .expect("response okay"); - assert_eq!(response.status(), Status::raw(105)); - assert_eq!(response.content_type(), Some(ContentType::JSON)); - assert_eq!(response.body_string().await, Some("goodbye".into())); + assert_eq!(r.status(), Status::raw(105)); + assert_eq!(r.content_type(), Some(ContentType::JSON)); + assert_eq!(r.body_mut().to_string().await.unwrap(), "goodbye"); } #[derive(Responder)] @@ -74,17 +74,17 @@ async fn responder_bar() { let local_req = client.get("/"); let req = local_req.inner(); - let mut response = Bar { + let mut r = Bar { responder: Foo::Second("foo foo".into()), other: ContentType::HTML, third: Cookie::new("cookie", "here!"), _yet_another: "uh..hi?".into() }.respond_to(req).expect("response okay"); - assert_eq!(response.status(), Status::InternalServerError); - assert_eq!(response.content_type(), Some(ContentType::Plain)); - assert_eq!(response.body_string().await, Some("foo foo".into())); - assert_eq!(response.headers().get_one("Set-Cookie"), Some("cookie=here!")); + assert_eq!(r.status(), Status::InternalServerError); + assert_eq!(r.content_type(), Some(ContentType::Plain)); + assert_eq!(r.body_mut().to_string().await.unwrap(), "foo foo"); + assert_eq!(r.headers().get_one("Set-Cookie"), Some("cookie=here!")); } #[derive(Responder)] @@ -99,11 +99,11 @@ async fn responder_baz() { let local_req = client.get("/"); let req = local_req.inner(); - let mut response = Baz { responder: "just a custom" } + let mut r = Baz { responder: "just a custom" } .respond_to(req) .expect("response okay"); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::new("application", "x-custom"))); - assert_eq!(response.body_string().await, Some("just a custom".into())); + assert_eq!(r.status(), Status::Ok); + assert_eq!(r.content_type(), Some(ContentType::new("application", "x-custom"))); + assert_eq!(r.body_mut().to_string().await.unwrap(), "just a custom"); } diff --git a/core/lib/src/local/asynchronous/response.rs b/core/lib/src/local/asynchronous/response.rs index 887940be..cda66d7f 100644 --- a/core/lib/src/local/asynchronous/response.rs +++ b/core/lib/src/local/asynchronous/response.rs @@ -40,7 +40,7 @@ use crate::{Request, Response}; /// /// // Check metadata validity. /// 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()`. /// let mut buffer = [0; 10]; @@ -107,12 +107,12 @@ impl LocalResponse<'_> { &self.cookies } - pub(crate) async fn _into_string(mut self) -> Option { - self.response.body_string().await + pub(crate) async fn _into_string(mut self) -> io::Result { + self.response.body_mut().to_string().await } - pub(crate) async fn _into_bytes(mut self) -> Option> { - self.response.body_bytes().await + pub(crate) async fn _into_bytes(mut self) -> io::Result> { + self.response.body_mut().to_bytes().await } // Generates the public API methods, which call the private methods above. @@ -126,12 +126,7 @@ impl AsyncRead for LocalResponse<'_> { cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { - let body = match self.response.body_mut() { - Some(body) => body, - _ => return Poll::Ready(Ok(())) - }; - - Pin::new(body.as_reader()).poll_read(cx, buf) + Pin::new(self.response.body_mut()).poll_read(cx, buf) } } diff --git a/core/lib/src/local/blocking/response.rs b/core/lib/src/local/blocking/response.rs index 1b188d4f..a041b43f 100644 --- a/core/lib/src/local/blocking/response.rs +++ b/core/lib/src/local/blocking/response.rs @@ -37,7 +37,7 @@ use super::Client; /// /// // Check metadata validity. /// 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()`. /// let mut buffer = [0; 10]; @@ -63,11 +63,11 @@ impl LocalResponse<'_> { self.inner._cookies() } - fn _into_string(self) -> Option { + fn _into_string(self) -> io::Result { self.client.block_on(self.inner._into_string()) } - fn _into_bytes(self) -> Option> { + fn _into_bytes(self) -> io::Result> { self.client.block_on(self.inner._into_bytes()) } diff --git a/core/lib/src/local/response.rs b/core/lib/src/local/response.rs index 43cae16b..b005458b 100644 --- a/core/lib/src/local/response.rs +++ b/core/lib/src/local/response.rs @@ -55,11 +55,13 @@ macro_rules! pub_response_impl { } 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 - /// `self` doesn't have a body, reading fails, or string conversion (for - /// non-UTF-8 bodies) fails, returns `None`. + /// Consumes `self` and reads the entirety of its body into a string. + /// + /// 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 /// @@ -73,11 +75,19 @@ macro_rules! pub_response_impl { /// ``` #[inline(always)] pub $($prefix)? fn into_string(self) -> Option { - self._into_string() $(.$suffix)? + if self._response().body().is_none() { + return None; + } + + self._into_string() $(.$suffix)? .ok() } - /// Consumes `self` and reads the entirety of its body into a `Vec` of `u8` - /// 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 /// @@ -91,7 +101,11 @@ macro_rules! pub_response_impl { /// ``` #[inline(always)] pub $($prefix)? fn into_bytes(self) -> Option> { - self._into_bytes() $(.$suffix)? + if self._response().body().is_none() { + return None; + } + + self._into_bytes() $(.$suffix)? .ok() } #[cfg(test)] diff --git a/core/lib/src/response/body.rs b/core/lib/src/response/body.rs new file mode 100644 index 00000000..2c45bb1c --- /dev/null +++ b/core/lib/src/response/body.rs @@ -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, + /// 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 AsyncReadSeek for T { } + +/// A pinned `AsyncRead + AsyncSeek` body type. +type SizedBody<'r> = Pin>; + +/// A pinned `AsyncRead` (not `AsyncSeek`) body type. +type UnsizedBody<'r> = Pin>; + +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(body: T, preset_size: Option) -> 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(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 { + 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 { + 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> { + 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::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> { + 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), + } + } +} diff --git a/core/lib/src/response/mod.rs b/core/lib/src/response/mod.rs index 7524e55d..3e951248 100644 --- a/core/lib/src/response/mod.rs +++ b/core/lib/src/response/mod.rs @@ -26,24 +26,24 @@ mod responder; mod redirect; mod named_file; -mod stream; mod response; mod debug; +mod body; pub(crate) mod flash; pub mod content; 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, ResponseBody, ResponseBuilder, Body}; +pub use self::response::{Response, Builder}; +pub use self::body::Body; pub use self::responder::Responder; pub use self::redirect::Redirect; pub use self::flash::Flash; pub use self::named_file::NamedFile; -pub use self::stream::Stream; pub use self::debug::Debug; /// Type alias for the `Result` of a [`Responder::respond_to()`] call. diff --git a/core/lib/src/response/response.rs b/core/lib/src/response/response.rs index 0f46253f..e580973c 100644 --- a/core/lib/src/response/response.rs +++ b/core/lib/src/response/response.rs @@ -1,152 +1,23 @@ -use std::{io, fmt, str}; +use std::{fmt, str}; 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::response::Body; -/// The default size, in bytes, of a chunk for streamed responses. -pub const DEFAULT_CHUNK_SIZE: usize = 4096; - -/// The body of a response: can be sized or streamed/chunked. -pub enum Body { - /// A fixed-size body. - Sized(A, Option), - /// A streamed/chunked body, akin to `Transfer-Encoding: chunked`. - Chunked(B, usize) -} - -impl Body { - /// 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, F2: FnOnce(B) -> U>(self, f1: F1, f2: F2) -> Body { - 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 Body { - /// Consumes `self` and returns the inner body. - pub fn into_inner(self) -> T { - match self { - Body::Sized(b, _) | Body::Chunked(b, _) => b - } - } -} - -impl Body - where A: AsyncRead + AsyncSeek + Send + Unpin, - B: AsyncRead + Send + Unpin -{ - pub fn known_size(&self) -> Option { - 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 { - 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> { - 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 { - 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 fmt::Debug for Body { - 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. +/// Builder for the [`Response`] type. /// /// Building a [`Response`] can be a low-level ordeal; this structure presents a /// higher-level API that simplifies building `Response`s. /// /// # Usage /// -/// `ResponseBuilder` follows the builder pattern and is usually obtained by -/// calling [`Response::build()`] on `Response`. Almost all methods take the -/// current builder as a mutable reference and return the same mutable reference -/// with field(s) modified in the `Responder` being built. These method calls -/// can be chained: `build.a().b()`. +/// `Builder` follows the builder pattern and is usually obtained by calling +/// [`Response::build()`] on `Response`. Almost all methods take the current +/// builder as a mutable reference and return the same mutable reference with +/// field(s) modified in the `Response` being built. These method calls can be +/// chained: `build.a().b()`. /// /// To finish building and retrieve the built `Response`, use the /// [`finalize()`](#method.finalize) or [`ok()`](#method.ok) methods. @@ -194,25 +65,25 @@ impl fmt::Debug for Body { /// .sized_body(body.len(), Cursor::new(body)) /// .finalize(); /// ``` -pub struct ResponseBuilder<'r> { +pub struct Builder<'r> { response: Response<'r>, } -impl<'r> ResponseBuilder<'r> { - /// Creates a new `ResponseBuilder` that will build on top of the `base` +impl<'r> Builder<'r> { + /// Creates a new `Builder` that will build on top of the `base` /// `Response`. /// /// # Example /// /// ```rust - /// use rocket::response::{ResponseBuilder, Response}; + /// use rocket::response::{Builder, Response}; /// /// # #[allow(unused_variables)] - /// let builder = ResponseBuilder::new(Response::new()); + /// let builder = Builder::new(Response::new()); /// ``` #[inline(always)] - pub fn new(base: Response<'r>) -> ResponseBuilder<'r> { - ResponseBuilder { + pub fn new(base: Response<'r>) -> Builder<'r> { + Builder { response: base, } } @@ -230,7 +101,7 @@ impl<'r> ResponseBuilder<'r> { /// .finalize(); /// ``` #[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 } @@ -251,7 +122,7 @@ impl<'r> ResponseBuilder<'r> { /// assert_eq!(response.status(), Status::new(699, "Alien Encounter")); /// ``` #[inline(always)] - pub fn raw_status(&mut self, code: u16, reason: &'static str) -> &mut ResponseBuilder<'r> { + pub fn raw_status(&mut self, code: u16, reason: &'static str) -> &mut Builder<'r> { self.response.set_raw_status(code, reason); self } @@ -279,7 +150,7 @@ impl<'r> ResponseBuilder<'r> { /// assert_eq!(response.headers().get("Content-Type").count(), 1); /// ``` #[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> { self.response.set_header(header); @@ -310,7 +181,7 @@ impl<'r> ResponseBuilder<'r> { /// assert_eq!(response.headers().get("Accept").count(), 2); /// ``` #[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> { self.response.adjoin_header(header); @@ -335,7 +206,7 @@ impl<'r> ResponseBuilder<'r> { /// assert_eq!(response.headers().get("X-Custom").count(), 1); /// ``` #[inline(always)] - pub fn raw_header<'a, 'b, N, V>(&mut self, name: N, value: V) -> &mut ResponseBuilder<'r> + pub fn raw_header<'a, 'b, N, V>(&mut self, name: N, value: V) -> &mut Builder<'r> where N: Into>, V: Into>, 'a: 'r, 'b: 'r { self.response.set_raw_header(name, value); @@ -361,7 +232,7 @@ impl<'r> ResponseBuilder<'r> { /// assert_eq!(response.headers().get("X-Custom").count(), 2); /// ``` #[inline(always)] - pub fn raw_header_adjoin<'a, 'b, N, V>(&mut self, name: N, value: V) -> &mut ResponseBuilder<'r> + pub fn raw_header_adjoin<'a, 'b, N, V>(&mut self, name: N, value: V) -> &mut Builder<'r> where N: Into>, V: Into>, 'a: 'r, 'b: 'r { 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 /// `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 - /// response. + /// be computed with calls to `seek` when the response is written out. /// /// # Example /// @@ -384,8 +254,8 @@ impl<'r> ResponseBuilder<'r> { /// .sized_body(body.len(), Cursor::new(body)) /// .finalize(); /// ``` - pub fn sized_body(&mut self, size: S, body: B) -> &mut ResponseBuilder<'r> - where B: AsyncRead + AsyncSeek + Send + Unpin + 'r, + pub fn sized_body(&mut self, size: S, body: B) -> &mut Builder<'r> + where B: AsyncRead + AsyncSeek + Send + 'r, S: Into> { self.response.set_sized_body(size, body); @@ -405,60 +275,31 @@ impl<'r> ResponseBuilder<'r> { /// .finalize(); /// ``` #[inline(always)] - pub fn streamed_body(&mut self, body: B) -> &mut ResponseBuilder<'r> + pub fn streamed_body(&mut self, body: B) -> &mut Builder<'r> where B: AsyncRead + Send + 'r { self.response.set_streamed_body(body); self } - /// Sets the body of the `Response` to be the streamed `body` with a custom - /// chunk size, in bytes. + /// Sets the max chunk size of a body, if any, to `size`. /// - /// # Example - /// - /// ```rust - /// use rocket::Response; - /// use tokio::fs::File; - /// # use std::io; - /// - /// # async fn test<'r>() -> io::Result> { - /// let response = Response::build() - /// .chunked_body(File::open("body.txt").await?, 8096) - /// .ok(); - /// # response - /// # } - /// ``` - #[inline(always)] - pub fn chunked_body(&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`. + /// See [`Response::set_max_chunk_size()`] for notes. /// /// # Example /// /// ```rust /// 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() - /// .raw_body::, Cursor<&'static str>>(body) + /// .streamed_body(Cursor::new("Hello, world!")) + /// .max_chunk_size(3072) /// .finalize(); /// ``` #[inline(always)] - pub fn raw_body(&mut self, body: Body) -> &mut ResponseBuilder<'r> - where S: AsyncRead + AsyncSeek + Send + Unpin + 'r, - C: AsyncRead + Send + Unpin + 'r - { - self.response.set_raw_body(body); + pub fn max_chunk_size(&mut self, size: usize) -> &mut Builder<'r> { + self.response.set_max_chunk_size(size); self } @@ -496,7 +337,7 @@ impl<'r> ResponseBuilder<'r> { /// assert_eq!(custom_values, vec!["value 1"]); /// ``` #[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 } @@ -536,7 +377,7 @@ impl<'r> ResponseBuilder<'r> { /// assert_eq!(custom_values, vec!["value 2", "value 3", "value 1"]); /// ``` #[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 } @@ -584,20 +425,15 @@ impl<'r> ResponseBuilder<'r> { } } -pub trait AsyncReadSeek: AsyncRead + AsyncSeek { } -impl AsyncReadSeek for T { } - -pub type ResponseBody<'r> = Body< - Pin>, - Pin> ->; - -/// A response, as returned by types implementing [`Responder`]. +/// A response, as returned by types implementing +/// [`Responder`](crate::response::Responder). +/// +/// See [`Builder`] for docs on how a `Response` is typically created. #[derive(Default)] pub struct Response<'r> { status: Option, headers: HeaderMap<'r>, - body: Option>, + body: Body<'r>, } impl<'r> Response<'r> { @@ -620,14 +456,10 @@ impl<'r> Response<'r> { /// ``` #[inline(always)] pub fn new() -> Response<'r> { - Response { - status: None, - headers: HeaderMap::new(), - body: None, - } + Response::default() } - /// Returns a `ResponseBuilder` with a base of `Response::new()`. + /// Returns a `Builder` with a base of `Response::new()`. /// /// # Example /// @@ -638,11 +470,11 @@ impl<'r> Response<'r> { /// let builder = Response::build(); /// ``` #[inline(always)] - pub fn build() -> ResponseBuilder<'r> { + pub fn build() -> Builder<'r> { Response::build_from(Response::new()) } - /// Returns a `ResponseBuilder` with a base of `other`. + /// Returns a `Builder` with a base of `other`. /// /// # Example /// @@ -654,8 +486,8 @@ impl<'r> Response<'r> { /// let builder = Response::build_from(other); /// ``` #[inline(always)] - pub fn build_from(other: Response<'r>) -> ResponseBuilder<'r> { - ResponseBuilder::new(other) + pub fn build_from(other: Response<'r>) -> Builder<'r> { + Builder::new(other) } /// Returns the status of `self`. @@ -925,8 +757,8 @@ impl<'r> Response<'r> { /// # }) /// ``` #[inline(always)] - pub fn body(&self) -> Option<&ResponseBody<'r>> { - self.body.as_ref() + pub fn body(&self) -> &Body<'r> { + &self.body } /// 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!"; /// 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)] - pub fn body_mut(&mut self) -> Option<&mut ResponseBody<'r>> { - self.body.as_mut() + pub fn body_mut(&mut self) -> &mut Body<'r> { + &mut self.body } - /// Consumes `self's` body and reads it into a string. If `self` doesn't - /// have a body, reading fails, or string conversion (for non-UTF-8 bodies) - /// 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 { - 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> { - 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> { - 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. + // Makes the `AsyncRead`er in the body empty but leaves the size of the body + // if it exists. Meant to be used during HEAD handling. #[inline(always)] pub(crate) fn strip_body(&mut self) { - if let Some(body) = self.take_body() { - self.body = match body { - Body::Sized(_, n) => Some(Body::Sized(Box::pin(io::Cursor::new(&[])), n)), - Body::Chunked(..) => None - }; - } + self.body.strip(); } /// Sets the body of `self` to be the fixed-sized `body` with size @@ -1060,48 +800,64 @@ impl<'r> Response<'r> { /// # Example /// /// ```rust - /// use std::io::Cursor; + /// use std::io; /// use rocket::Response; /// - /// # rocket::async_test(async { + /// # let o: io::Result<()> = rocket::async_test(async { /// let string = "Hello, world!"; /// /// let mut response = Response::new(); - /// response.set_sized_body(string.len(), Cursor::new(string)); - /// assert_eq!(response.body_string().await.unwrap(), "Hello, world!"); - /// # }) + /// response.set_sized_body(string.len(), io::Cursor::new(string)); + /// assert_eq!(response.body_mut().to_string().await?, "Hello, world!"); + /// # Ok(()) + /// # }); + /// # assert!(o.is_ok()); /// ``` pub fn set_sized_body(&mut self, size: S, body: B) - where B: AsyncRead + AsyncSeek + Send + Unpin + 'r, + where B: AsyncRead + AsyncSeek + Send + 'r, S: Into> { - 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 - /// size of the stream is - /// [DEFAULT_CHUNK_SIZE](crate::response::DEFAULT_CHUNK_SIZE). Use - /// [set_chunked_body](#method.set_chunked_body) for custom chunk sizes. + /// Sets the body of `self` to `body`, which will be streamed. + /// + /// The max chunk size is configured via [`Response::set_max_chunk_size()`] + /// and defaults to [`Body::DEFAULT_MAX_CHUNK`]. /// /// # Example /// /// ```rust + /// # use std::io; /// use tokio::io::{repeat, AsyncReadExt}; /// use rocket::Response; /// - /// # rocket::async_test(async { + /// # let o: io::Result<()> = rocket::async_test(async { /// let mut response = Response::new(); /// 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)] - pub fn set_streamed_body(&mut self, body: B) where B: AsyncRead + Send + 'r { - self.set_chunked_body(body, DEFAULT_CHUNK_SIZE); + pub fn set_streamed_body(&mut self, body: B) + 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 - /// size `chunk_size`. + /// Sets the body's maximum chunk size to `size` bytes. + /// + /// 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 /// @@ -1109,48 +865,16 @@ impl<'r> Response<'r> { /// use tokio::io::{repeat, AsyncReadExt}; /// use rocket::Response; /// - /// # rocket::async_test(async { + /// # let o: Option<()> = rocket::async_test(async { /// let mut response = Response::new(); - /// response.set_chunked_body(repeat(97).take(5), 10); - /// assert_eq!(response.body_string().await.unwrap(), "aaaaa"); - /// # }) - /// ``` + /// response.set_streamed_body(repeat(97).take(5)); + /// response.set_max_chunk_size(3072); + /// # Some(()) + /// # }); + /// # assert!(o.is_some()); #[inline(always)] - pub fn set_chunked_body(&mut self, body: B, chunk_size: usize) - where B: AsyncRead + Send + 'r - { - 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>>(body); - /// - /// assert_eq!(response.body_string().await.unwrap(), "Hello!"); - /// # }) - /// ``` - #[inline(always)] - pub fn set_raw_body(&mut self, body: Body) - 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), - }); + pub fn set_max_chunk_size(&mut self, size: usize) { + self.body_mut().set_max_chunk_size(size); } /// 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); } - if let Some(body) = other.body { - self.body = Some(body); + if other.body().is_some() { + self.body = other.body; } for (name, values) in other.headers.into_iter_raw() { @@ -1252,18 +976,6 @@ impl fmt::Debug for Response<'_> { writeln!(f, "{}", header)?; } - match self.body { - 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) + self.body.fmt(f) } } diff --git a/core/lib/src/server.rs b/core/lib/src/server.rs index d7aba70c..b6092429 100644 --- a/core/lib/src/server.rs +++ b/core/lib/src/server.rs @@ -147,31 +147,20 @@ impl Rocket { }) }; - match response.body_mut() { - None => { - hyp_res = hyp_res.header(hyper::header::CONTENT_LENGTH, 0); - 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 body = response.body_mut(); + if let Some(n) = body.size().await { + hyp_res = hyp_res.header(hyper::header::CONTENT_LENGTH, n); + } - let chunk_size = match *body { - Body::Chunked(_, chunk_size) => chunk_size as usize, - Body::Sized(_, _) => crate::response::DEFAULT_CHUNK_SIZE, - }; + let max_chunk_size = body.max_chunk_size(); + let (mut sender, hyp_body) = hyper::Body::channel(); + send_response(hyp_res, hyp_body)?; - let (mut sender, hyp_body) = hyper::Body::channel(); - send_response(hyp_res, hyp_body)?; - - let mut stream = body.as_reader().into_bytes_stream(chunk_size); - while let Some(next) = stream.next().await { - sender.send_data(next?).await - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - } - } - }; + let mut stream = body.into_bytes_stream(max_chunk_size); + while let Some(next) = stream.next().await { + sender.send_data(next?).await + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + } Ok(()) } diff --git a/core/lib/tests/conditionally-set-server-header-996.rs b/core/lib/tests/conditionally-set-server-header-996.rs index 25fe565d..f146b036 100644 --- a/core/lib/tests/conditionally-set-server-header-996.rs +++ b/core/lib/tests/conditionally-set-server-header-996.rs @@ -1,13 +1,13 @@ #[macro_use] extern crate rocket; -use rocket::Response; use rocket::http::Header; +#[derive(Responder)] +struct HeaderOnly((), Header<'static>); + #[get("/do_not_overwrite")] -fn do_not_overwrite() -> Response<'static> { - Response::build() - .header(Header::new("Server", "Test")) - .finalize() +fn do_not_overwrite() -> HeaderOnly { + HeaderOnly((), Header::new("Server", "Test")) } #[get("/use_default")] diff --git a/core/lib/tests/fairing_before_head_strip-issue-546.rs b/core/lib/tests/fairing_before_head_strip-issue-546.rs index 47cd5599..368098b3 100644 --- a/core/lib/tests/fairing_before_head_strip-issue-546.rs +++ b/core/lib/tests/fairing_before_head_strip-issue-546.rs @@ -17,12 +17,12 @@ fn auto() -> &'static str { mod fairing_before_head_strip { use super::*; 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::fairing::AdHoc; + use rocket::local::blocking::Client; + use rocket::http::{Method, Status}; #[test] fn not_auto_handled() { @@ -36,14 +36,16 @@ mod fairing_before_head_strip { .attach(AdHoc::on_response("Check HEAD 2", |req, res| { Box::pin(async move { 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 response = client.head("/").dispatch(); assert_eq!(response.status(), Status::Ok); - assert!(response.body().is_none()); + assert!(response.into_string().unwrap_or_default().is_empty()); } #[test] @@ -67,13 +69,15 @@ mod fairing_before_head_strip { .attach(AdHoc::on_response("Check GET", |req, res| { Box::pin(async move { 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 response = client.head("/").dispatch(); assert_eq!(response.status(), Status::Ok); - assert!(response.body().is_none()); + assert!(response.into_string().unwrap_or_default().is_empty()); } } diff --git a/core/lib/tests/head_handling.rs b/core/lib/tests/head_handling.rs index d381dd1e..4832d269 100644 --- a/core/lib/tests/head_handling.rs +++ b/core/lib/tests/head_handling.rs @@ -36,7 +36,7 @@ mod head_handling_tests { let content_type: Vec<_> = response.headers().get("Content-Type").collect(); assert_eq!(content_type, vec![ContentType::Plain.to_string()]); 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()); let response = client.head("/empty").dispatch(); @@ -52,7 +52,7 @@ mod head_handling_tests { let content_type: Vec<_> = response.headers().get("Content-Type").collect(); assert_eq!(content_type, vec![ContentType::JSON.to_string()]); 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()); } } diff --git a/site/guide/8-testing.md b/site/guide/8-testing.md index 32878589..6399f01a 100644 --- a/site/guide/8-testing.md +++ b/site/guide/8-testing.md @@ -86,14 +86,20 @@ These methods are typically used in combination with the `assert_eq!` or # use std::io::Cursor; # use rocket::Response; # use rocket::http::Header; - +# +# #[derive(Responder)] +# #[response(content_type = "text")] +# struct Custom { +# body: &'static str, +# header: Header<'static>, +# } +# # #[get("/")] -# fn hello() -> Response<'static> { -# Response::build() -# .header(ContentType::Plain) -# .header(Header::new("X-Special", "")) -# .sized_body("Expected Body".len(), Cursor::new("Expected Body")) -# .finalize() +# fn hello() -> Custom { +# Custom { +# body: "Expected Body", +# header: Header::new("X-Special", ""), +# } # } # use rocket::local::blocking::Client; @@ -106,7 +112,7 @@ let mut response = client.get("/").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::Plain)); 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!"