mirror of https://github.com/rwf2/Rocket.git
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:
parent
e7b28f18a9
commit
63e6845386
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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<String> {
|
||||
self.response.body_string().await
|
||||
pub(crate) async fn _into_string(mut self) -> io::Result<String> {
|
||||
self.response.body_mut().to_string().await
|
||||
}
|
||||
|
||||
pub(crate) async fn _into_bytes(mut self) -> Option<Vec<u8>> {
|
||||
self.response.body_bytes().await
|
||||
pub(crate) async fn _into_bytes(mut self) -> io::Result<Vec<u8>> {
|
||||
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<io::Result<()>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String> {
|
||||
fn _into_string(self) -> io::Result<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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String> {
|
||||
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<Vec<u8>> {
|
||||
self._into_bytes() $(.$suffix)?
|
||||
if self._response().body().is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self._into_bytes() $(.$suffix)? .ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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, 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.
|
||||
/// 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<A, B> fmt::Debug for Body<A, B> {
|
|||
/// .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<Header<'h>>
|
||||
{
|
||||
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<Header<'h>>
|
||||
{
|
||||
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<Cow<'a, str>>, V: Into<Cow<'b, str>>, '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<Cow<'a, str>>, V: Into<Cow<'b, str>>, '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<B, S>(&mut self, size: S, body: B) -> &mut ResponseBuilder<'r>
|
||||
where B: AsyncRead + AsyncSeek + Send + Unpin + 'r,
|
||||
pub fn sized_body<B, S>(&mut self, size: S, body: B) -> &mut Builder<'r>
|
||||
where B: AsyncRead + AsyncSeek + Send + 'r,
|
||||
S: Into<Option<usize>>
|
||||
{
|
||||
self.response.set_sized_body(size, body);
|
||||
|
@ -405,60 +275,31 @@ impl<'r> ResponseBuilder<'r> {
|
|||
/// .finalize();
|
||||
/// ```
|
||||
#[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
|
||||
{
|
||||
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<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`.
|
||||
/// 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>, Cursor<&'static str>>(body)
|
||||
/// .streamed_body(Cursor::new("Hello, world!"))
|
||||
/// .max_chunk_size(3072)
|
||||
/// .finalize();
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn raw_body<S, C>(&mut self, body: Body<S, C>) -> &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<T: AsyncRead + AsyncSeek> AsyncReadSeek for T { }
|
||||
|
||||
pub type ResponseBody<'r> = Body<
|
||||
Pin<Box<dyn AsyncReadSeek + Send + 'r>>,
|
||||
Pin<Box<dyn AsyncRead + Send + 'r>>
|
||||
>;
|
||||
|
||||
/// 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<Status>,
|
||||
headers: HeaderMap<'r>,
|
||||
body: Option<ResponseBody<'r>>,
|
||||
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<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.
|
||||
// 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<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>>
|
||||
{
|
||||
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<B>(&mut self, body: B) where B: AsyncRead + Send + 'r {
|
||||
self.set_chunked_body(body, DEFAULT_CHUNK_SIZE);
|
||||
pub fn set_streamed_body<B>(&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<B>(&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>, 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),
|
||||
});
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,31 +147,20 @@ impl Rocket<Orbit> {
|
|||
})
|
||||
};
|
||||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!"
|
||||
|
|
Loading…
Reference in New Issue