Make 'Responder' trait sync; fix its lifetimes.

In summary, this commit modifies 'Responder' so that:

  * ..it is no longer 'async'. To accommodate, the 'sized_body' methods
    in 'Response' and 'ResponseBuilder' are no longer 'async' and accept
    an optional size directly. If none is supplied, Rocket will attempt
    to compute the size, by seeking, before writing out the response.
    The 'Body' type was also changed to differentiate between its sized
    'Seek' and chunked body variants.

  * ..'&Request' gains a lifetime: 'r, and the returned 'Response' is
    parameterized by a new 'o: 'r. This allows responders to return
    references from the request or those that live longer.
This commit is contained in:
Sergio Benitez 2020-06-19 06:01:10 -07:00
parent 12308b403f
commit 2465e2f136
35 changed files with 453 additions and 456 deletions

View File

@ -958,7 +958,7 @@ applications.
* **The [`Responder`] trait has changed.**
`Responder::respond(self)` was removed in favor of
`Responder::respond_to(self, &Request)`. Responders may dynamically adjust
`Responder::respond_to(self, &'r Request)`. Responders may dynamically adjust
their response based on the incoming request.
* **`Outcome::of(Responder)` was removed while `Outcome::from(&Request,

View File

@ -34,9 +34,9 @@ use super::CompressionUtils;
#[derive(Debug)]
pub struct Compress<R>(pub R);
impl<'r, R: Responder<'r>> Responder<'r> for Compress<R> {
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Compress<R> {
#[inline(always)]
fn respond_to(self, request: &Request<'_>) -> response::Result<'r> {
fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o> {
let mut response = Response::build()
.merge(self.0.respond_to(request)?)
.finalize();

View File

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

View File

@ -23,7 +23,6 @@ use rocket::outcome::Outcome::*;
use rocket::data::{Data, FromData, FromDataFuture, Transform::*, TransformFuture, Transformed};
use rocket::http::Status;
use rocket::response::{self, content, Responder};
use rocket::futures::future::BoxFuture;
use serde::Serialize;
use serde::de::Deserialize;
@ -157,17 +156,15 @@ impl<'a, T: Deserialize<'a>> FromData<'a> for MsgPack<T> {
/// Serializes the wrapped value into MessagePack. Returns a response with
/// Content-Type `MsgPack` and a fixed-size body with the serialization. If
/// serialization fails, an `Err` of `Status::InternalServerError` is returned.
impl<'r, T: Serialize> Responder<'r> for MsgPack<T> {
fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>>
where 'a: 'x, 'r: 'x, Self: 'x
{
match rmp_serde::to_vec(&self.0) {
Ok(buf) => content::MsgPack(buf).respond_to(req),
Err(e) => {
impl<'r, T: Serialize> Responder<'r, 'static> for MsgPack<T> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
let buf = rmp_serde::to_vec(&self.0)
.map_err(|e| {
error_!("MsgPack failed to serialize: {:?}", e);
Box::pin(async { Err(Status::InternalServerError) })
}
}
Status::InternalServerError
})?;
content::MsgPack(buf).respond_to(req)
}
}

View File

@ -278,19 +278,17 @@ impl Into<Vec<Route>> for StaticFiles {
impl Handler for StaticFiles {
fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> HandlerFuture<'r> {
fn handle_dir<'r>(opt: Options, r: &'r Request<'_>, d: Data, path: &Path) -> HandlerFuture<'r> {
fn handle_dir<'r>(opt: Options, r: &'r Request<'_>, d: Data, path: &Path) -> Outcome<'r> {
if opt.contains(Options::NormalizeDirs) && !r.uri().path().ends_with('/') {
let new_path = r.uri().map_path(|p| p.to_owned() + "/")
.expect("adding a trailing slash to a known good path results in a valid path")
.into_owned();
return Box::pin(async move {
Outcome::from_or_forward(r, d, Redirect::permanent(new_path))
});
return Outcome::from_or_forward(r, d, Redirect::permanent(new_path));
}
if !opt.contains(Options::Index) {
return Box::pin(async move { Outcome::forward(d) });
return Outcome::forward(d);
}
let file = NamedFile::open(path.join("index.html")).ok();
@ -302,7 +300,7 @@ impl Handler for StaticFiles {
let current_route = req.route().expect("route while handling");
let is_segments_route = current_route.uri.path().ends_with(">");
if !is_segments_route {
return handle_dir(self.options, req, data, &self.root);
return handle_dir(self.options, req, data, &self.root).pin();
}
// Otherwise, we're handling segments. Get the segments as a `PathBuf`,
@ -316,7 +314,7 @@ impl Handler for StaticFiles {
match &path {
Some(path) if path.is_dir() => handle_dir(self.options, req, data, path),
Some(path) => Outcome::from_or_forward(req, data, NamedFile::open(path).ok()),
None => Box::pin(async move { Outcome::forward(data) }),
}
None => Outcome::forward(data),
}.pin()
}
}

View File

@ -141,7 +141,7 @@ use serde_json::{Value, to_value};
use std::borrow::Cow;
use std::path::PathBuf;
use rocket::{Manifest, State};
use rocket::Manifest;
use rocket::request::Request;
use rocket::fairing::Fairing;
use rocket::response::{self, Content, Responder};
@ -389,20 +389,19 @@ impl Template {
/// Returns a response with the Content-Type derived from the template's
/// extension and a fixed-size body containing the rendered template. If
/// rendering fails, an `Err` of `Status::InternalServerError` is returned.
#[rocket::async_trait]
impl<'r> Responder<'r> for Template {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
impl<'r> Responder<'r, 'static> for Template {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
let (render, content_type) = {
let ctxt = req.guard::<State<'_, ContextManager>>().await.succeeded().ok_or_else(|| {
let ctxt = req.managed_state::<ContextManager>().ok_or_else(|| {
error_!("Uninitialized template context: missing fairing.");
info_!("To use templates, you must attach `Template::fairing()`.");
info_!("See the `Template` documentation for more information.");
Status::InternalServerError
})?.inner().context();
})?.context();
self.finalize(&ctxt)?
};
Content(content_type, render).respond_to(req).await
Content(content_type, render).respond_to(req)
}
}

View File

@ -87,7 +87,7 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
// let #catcher: #fn_sig = #user_catcher_fn_name;
let #catcher = #user_catcher_fn_name;
#responder_stmt
::rocket::response::Responder::respond_to(___responder, #req).await?
::rocket::response::Responder::respond_to(___responder, #req)?
});
// Generate the catcher, keeping the user's input around.

View File

@ -369,19 +369,14 @@ fn generate_respond_expr(route: &Route) -> TokenStream2 {
let parameter_names = route.inputs.iter()
.map(|(_, rocket_ident, _)| rocket_ident);
let responder_stmt = if route.function.sig.asyncness.is_some() {
quote_spanned! { ret_span =>
let ___responder = #user_handler_fn_name(#(#parameter_names),*).await;
}
} else {
quote_spanned! { ret_span =>
let ___responder = #user_handler_fn_name(#(#parameter_names),*);
}
let _await = route.function.sig.asyncness.map(|a| quote_spanned!(a.span().into() => .await));
let responder_stmt = quote_spanned! { ret_span =>
let ___responder = #user_handler_fn_name(#(#parameter_names),*) #_await;
};
quote_spanned! { ret_span =>
#responder_stmt
#handler::Outcome::from(#req, ___responder).await
#handler::Outcome::from(#req, ___responder)
}
}

View File

@ -17,7 +17,11 @@ struct FieldAttr {
}
pub fn derive_responder(input: TokenStream) -> TokenStream {
DeriveGenerator::build_for(input, quote!(impl<'__r> ::rocket::response::Responder<'__r>))
// NOTE: Due to a bug in devise, we can't do the more correct:
// quote!(impl<'__r, '__o: '__r> ::rocket::response::Responder<'__r, '__o>))
// replace_generic(1, 0)
// A bugfix (on devise master) fixes this so the above works. Hack.
DeriveGenerator::build_for(input, quote!(impl<'__r> ::rocket::response::Responder<'__r, '__r>))
.generic_support(GenericSupport::Lifetime)
.data_support(DataSupport::Struct | DataSupport::Enum)
.replace_generic(0, 0)
@ -30,17 +34,15 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
false => Ok(())
})
.function(|_, inner| quote! {
fn respond_to<'__i, '__x>(
fn respond_to(
self,
__req: &'__r ::rocket::request::Request<'__i>
) -> ::rocket::futures::future::BoxFuture<'__x, ::rocket::response::Result<'__r>>
where '__i: '__x, '__r: '__x, Self: '__x
{
__req: &'__r ::rocket::request::Request
) -> ::rocket::response::Result<'__r> {
#inner
}
})
.try_map_fields(|_, fields| {
define_vars_and_mods!(_Ok, _Box);
define_vars_and_mods!(_Ok);
fn set_header_tokens<T: ToTokens + Spanned>(item: T) -> TokenStream2 {
quote_spanned!(item.span().into() => __res.set_header(#item);)
}
@ -53,7 +55,7 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
quote_spanned! { f.span().into() =>
let mut __res = <#ty as ::rocket::response::Responder>::respond_to(
#accessor, __req
).await?;
)?;
}
}).expect("have at least one field");
@ -73,13 +75,11 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
});
Ok(quote! {
#_Box::pin(async move {
#responder
#(#headers)*
#content_type
#status
#_Ok(__res)
})
#responder
#(#headers)*
#content_type
#status
#_Ok(__res)
})
})
.to_tokens()

View File

@ -28,7 +28,7 @@ async fn responder_foo() {
let req = local_req.inner();
let mut response = Foo::First("hello".into())
.respond_to(req).await
.respond_to(req)
.expect("response okay");
assert_eq!(response.status(), Status::Ok);
@ -36,7 +36,7 @@ async fn responder_foo() {
assert_eq!(response.body_string().await, Some("hello".into()));
let mut response = Foo::Second("just a test".into())
.respond_to(req).await
.respond_to(req)
.expect("response okay");
assert_eq!(response.status(), Status::InternalServerError);
@ -44,7 +44,7 @@ async fn responder_foo() {
assert_eq!(response.body_string().await, Some("just a test".into()));
let mut response = Foo::Third { responder: "well, hi", ct: ContentType::JSON }
.respond_to(req).await
.respond_to(req)
.expect("response okay");
assert_eq!(response.status(), Status::NotFound);
@ -52,7 +52,7 @@ async fn responder_foo() {
assert_eq!(response.body_string().await, Some("well, hi".into()));
let mut response = Foo::Fourth { string: "goodbye", ct: ContentType::JSON }
.respond_to(req).await
.respond_to(req)
.expect("response okay");
assert_eq!(response.status(), Status::raw(105));
@ -81,7 +81,7 @@ async fn responder_bar() {
other: ContentType::HTML,
third: Cookie::new("cookie", "here!"),
_yet_another: "uh..hi?".into()
}.respond_to(req).await.expect("response okay");
}.respond_to(req).expect("response okay");
assert_eq!(response.status(), Status::InternalServerError);
assert_eq!(response.content_type(), Some(ContentType::Plain));
@ -102,7 +102,7 @@ async fn responder_baz() {
let req = local_req.inner();
let mut response = Baz { responder: "just a custom" }
.respond_to(req).await
.respond_to(req)
.expect("response okay");
assert_eq!(response.status(), Status::Ok);

View File

@ -16,6 +16,7 @@ fn rocket() -> rocket::Rocket {
rocket::custom(config.unwrap()).mount("/", routes![get, post])
}
#[allow(unused_must_use)]
mod benches {
extern crate test;
@ -24,30 +25,34 @@ mod benches {
use rocket::local::Client;
use rocket::http::{Accept, ContentType};
fn client(_rocket: rocket::Rocket) -> Option<Client> {
unimplemented!("waiting for sync-client");
}
#[bench]
fn accept_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = client(rocket()).unwrap();
let mut request = client.get("/").header(Accept::JSON);
b.iter(|| { request.mut_dispatch(); });
}
#[bench]
fn wrong_accept_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = client(rocket()).unwrap();
let mut request = client.get("/").header(Accept::HTML);
b.iter(|| { request.mut_dispatch(); });
}
#[bench]
fn content_type_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = client(rocket()).unwrap();
let mut request = client.post("/").header(ContentType::JSON);
b.iter(|| { request.mut_dispatch(); });
}
#[bench]
fn wrong_content_type_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = client(rocket()).unwrap();
let mut request = client.post("/").header(ContentType::Plain);
b.iter(|| { request.mut_dispatch(); });
}

View File

@ -30,6 +30,7 @@ fn rocket() -> rocket::Rocket {
.mount("/", routes![post, post2, post3])
}
#[allow(unused_must_use)]
mod benches {
extern crate test;
@ -38,9 +39,13 @@ mod benches {
use rocket::local::Client;
use rocket::http::{Accept, ContentType};
fn client(_rocket: rocket::Rocket) -> Option<Client> {
unimplemented!("waiting for sync-client");
}
#[bench]
fn accept_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = client(rocket()).unwrap();
let mut requests = vec![];
requests.push(client.get("/").header(Accept::JSON));
requests.push(client.get("/").header(Accept::HTML));
@ -55,7 +60,7 @@ mod benches {
#[bench]
fn content_type_format(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = client(rocket()).unwrap();
let mut requests = vec![];
requests.push(client.post("/").header(ContentType::JSON));
requests.push(client.post("/").header(ContentType::HTML));

View File

@ -42,6 +42,7 @@ fn rocket() -> rocket::Rocket {
index_b, index_c, index_dyn_a])
}
#[allow(unused_must_use)]
mod benches {
extern crate test;
@ -49,9 +50,13 @@ mod benches {
use self::test::Bencher;
use rocket::local::Client;
fn client(_rocket: rocket::Rocket) -> Option<Client> {
unimplemented!("waiting for sync-client");
}
#[bench]
fn bench_hello_world(b: &mut Bencher) {
let client = Client::new(hello_world_rocket()).unwrap();
let client = client(hello_world_rocket()).unwrap();
let mut request = client.get("/");
b.iter(|| {
@ -61,7 +66,7 @@ mod benches {
#[bench]
fn bench_single_get_index(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = client(rocket()).unwrap();
let mut request = client.get("/");
b.iter(|| {
@ -71,7 +76,7 @@ mod benches {
#[bench]
fn bench_get_put_post_index(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = client(rocket()).unwrap();
// Hold all of the requests we're going to make during the benchmark.
let mut requests = vec![];
@ -88,7 +93,7 @@ mod benches {
#[bench]
fn bench_dynamic(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = client(rocket()).unwrap();
// Hold all of the requests we're going to make during the benchmark.
let mut requests = vec![];
@ -105,7 +110,7 @@ mod benches {
#[bench]
fn bench_simple_routing(b: &mut Bencher) {
let client = Client::new(rocket()).unwrap();
let client = client(rocket()).unwrap();
// Hold all of the requests we're going to make during the benchmark.
let mut requests = vec![];

View File

@ -153,10 +153,10 @@ macro_rules! default_catchers {
let mut map = HashMap::new();
$(
fn $fn_name<'r>(req: &'r Request<'_>) -> futures::future::BoxFuture<'r, response::Result<'r>> {
status::Custom(Status::from_code($code).unwrap(),
content::Html(error_page_template!($code, $name, $description))
).respond_to(req)
fn $fn_name<'r>(req: &'r Request<'_>) -> crate::handler::ErrorHandlerFuture<'r> {
let status = Status::from_code($code).unwrap();
let html = content::Html(error_page_template!($code, $name, $description));
Box::pin(async move { status::Custom(status, html).respond_to(req) })
}
map.insert($code, Catcher::new_default($code, $fn_name));
@ -172,7 +172,7 @@ pub mod defaults {
use std::collections::HashMap;
use crate::request::Request;
use crate::response::{self, content, status, Responder};
use crate::response::{content, status, Responder};
use crate::http::Status;
pub fn get() -> HashMap<u16, Catcher> {
@ -243,4 +243,3 @@ pub mod defaults {
}
}
}

View File

@ -44,16 +44,39 @@ pub struct AdHoc {
kind: AdHocKind,
}
// macro_rules! Async {
// ($kind:ident <$l:lifetime> ($($param:ty),*) -> $r:ty) => (
// dyn for<$l> $kind($($param),*) -> futures::future::BoxFuture<$l, $r>
// + Send + 'static
// );
// ($kind:ident ($($param:ty),*) -> $r:ty) => (
// dyn $kind($($param),*) -> futures::future::BoxFuture<'static, $r>
// + Send + Sync + 'static
// );
// ($kind:ident <$l:lifetime> ($($param:ty),*)) => (
// Async!($kind <$l> ($($param),*) -> ())
// );
// ($kind:ident ($($param:ty),*)) => (
// Async!($kind ($($param),*) -> ())
// );
// }
enum AdHocKind {
/// An ad-hoc **attach** fairing. Called when the fairing is attached.
Attach(Mutex<Option<Box<dyn FnOnce(Rocket) -> BoxFuture<'static, Result<Rocket, Rocket>> + Send + 'static>>>),
Attach(Mutex<Option<Box<dyn FnOnce(Rocket)
-> BoxFuture<'static, Result<Rocket, Rocket>> + Send + 'static>>>),
/// An ad-hoc **launch** fairing. Called just before Rocket launches.
Launch(Mutex<Option<Box<dyn FnOnce(&Manifest) + Send + 'static>>>),
/// An ad-hoc **request** fairing. Called when a request is received.
Request(Box<dyn for<'a> Fn(&'a mut Request<'_>, &'a Data) -> BoxFuture<'a, ()> + Send + Sync + 'static>),
Request(Box<dyn for<'a> Fn(&'a mut Request<'_>, &'a Data)
-> BoxFuture<'a, ()> + Send + Sync + 'static>),
/// An ad-hoc **response** fairing. Called when a response is ready to be
/// sent to a client.
Response(Box<dyn for<'a> Fn(&'a Request<'_>, &'a mut Response<'_>) -> BoxFuture<'a, ()> + Send + Sync + 'static>),
Response(Box<dyn for<'a> Fn(&'a Request<'_>, &'a mut Response<'_>)
-> BoxFuture<'a, ()> + Send + Sync + 'static>),
}
impl AdHoc {
@ -73,7 +96,10 @@ impl AdHoc {
F: FnOnce(Rocket) -> Fut + Send + 'static,
Fut: Future<Output=Result<Rocket, Rocket>> + Send + 'static,
{
AdHoc { name, kind: AdHocKind::Attach(Mutex::new(Some(Box::new(|rocket| Box::pin(f(rocket)))))) }
AdHoc {
name,
kind: AdHocKind::Attach(Mutex::new(Some(Box::new(|rocket| Box::pin(f(rocket))))))
}
}
/// Constructs an `AdHoc` launch fairing named `name`. The function `f` will
@ -117,6 +143,17 @@ impl AdHoc {
{
AdHoc { name, kind: AdHocKind::Request(Box::new(f)) }
}
// // FIXME: Can the generated future hold references to the request with this?
// pub fn on_request<F, Fut>(name: &'static str, f: F) -> AdHoc
// where
// F: for<'a> Fn(&'a mut Request<'_>, &'a Data) -> Fut + Send + Sync + 'static,
// Fut: Future<Output=()> + Send + 'static,
// {
// AdHoc {
// name,
// kind: AdHocKind::Request(Box::new(|req, data| Box::pin(f(req, data))))
// }
// }
/// Constructs an `AdHoc` response fairing named `name`. The function `f`
/// will be called and the returned `Future` will be `await`ed by Rocket

View File

@ -287,7 +287,7 @@ pub use self::info_kind::{Info, Kind};
/// let body = format!("Get: {}\nPost: {}", get_count, post_count);
/// res.set_status(Status::Ok);
/// res.set_header(ContentType::Plain);
/// res.set_sized_body(Cursor::new(body));
/// res.set_sized_body(body.len(), Cursor::new(body));
/// }
/// }
/// }

View File

@ -44,13 +44,13 @@ pub type HandlerFuture<'r> = BoxFuture<'r, Outcome<'r>>;
/// ```rust,no_run
/// # #[derive(Copy, Clone)] enum Kind { Simple, Intermediate, Complex, }
/// use rocket::{Request, Data, Route, http::Method};
/// use rocket::handler::{self, Handler, Outcome, HandlerFuture};
/// use rocket::handler::{self, Handler, Outcome};
///
/// #[derive(Clone)]
/// struct CustomHandler(Kind);
///
/// impl Handler for CustomHandler {
/// fn handle<'r>(&self, req: &'r Request, data: Data) -> HandlerFuture<'r> {
/// fn handle<'r>(&self, req: &'r Request, data: Data) -> Outcome<'r> {
/// match self.0 {
/// Kind::Simple => Outcome::from(req, "simple"),
/// Kind::Intermediate => Outcome::from(req, "intermediate"),
@ -184,7 +184,7 @@ pub type ErrorHandler = for<'r> fn(&'r Request<'_>) -> ErrorHandlerFuture<'r>;
/// Type type of `Future` returned by an error handler.
pub type ErrorHandlerFuture<'r> = BoxFuture<'r, response::Result<'r>>;
impl<'r> Outcome<'r> {
impl<'r, 'o: 'r> Outcome<'o> {
/// Return the `Outcome` of response to `req` from `responder`.
///
/// If the responder returns `Ok`, an outcome of `Success` is
@ -195,20 +195,18 @@ impl<'r> Outcome<'r> {
///
/// ```rust
/// use rocket::{Request, Data};
/// use rocket::handler::{Outcome, HandlerFuture};
/// use rocket::handler::Outcome;
///
/// fn str_responder<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> {
/// fn str_responder<'r>(req: &'r Request, _: Data) -> Outcome<'r> {
/// Outcome::from(req, "Hello, world!")
/// }
/// ```
#[inline]
pub fn from<T: Responder<'r> + Send + 'r>(req: &'r Request<'_>, responder: T) -> HandlerFuture<'r> {
Box::pin(async move {
match responder.respond_to(req).await {
Ok(response) => outcome::Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status)
}
})
pub fn from<R: Responder<'r, 'o>>(req: &'r Request<'_>, responder: R) -> Outcome<'o> {
match responder.respond_to(req) {
Ok(response) => outcome::Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status)
}
}
/// Return the `Outcome` of response to `req` from `responder`.
@ -221,23 +219,21 @@ impl<'r> Outcome<'r> {
///
/// ```rust
/// use rocket::{Request, Data};
/// use rocket::handler::{Outcome, HandlerFuture};
/// use rocket::handler::Outcome;
///
/// fn str_responder<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> {
/// fn str_responder<'r>(req: &'r Request, _: Data) -> Outcome<'r> {
/// Outcome::from(req, "Hello, world!")
/// }
/// ```
#[inline]
pub fn try_from<T, E>(req: &'r Request<'_>, result: Result<T, E>) -> HandlerFuture<'r>
where T: Responder<'r> + Send + 'r, E: std::fmt::Debug + Send + 'r
pub fn try_from<R, E>(req: &'r Request<'_>, result: Result<R, E>) -> Outcome<'o>
where R: Responder<'r, 'o>, E: std::fmt::Debug
{
Box::pin(async move {
let responder = result.map_err(crate::response::Debug);
match responder.respond_to(req).await {
Ok(response) => outcome::Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status)
}
})
let responder = result.map_err(crate::response::Debug);
match responder.respond_to(req) {
Ok(response) => outcome::Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status)
}
}
/// Return the `Outcome` of response to `req` from `responder`.
@ -250,22 +246,20 @@ impl<'r> Outcome<'r> {
///
/// ```rust
/// use rocket::{Request, Data};
/// use rocket::handler::{Outcome, HandlerFuture};
/// use rocket::handler::Outcome;
///
/// fn str_responder<'r>(req: &'r Request, data: Data) -> HandlerFuture<'r> {
/// fn str_responder<'r>(req: &'r Request, data: Data) -> Outcome<'r> {
/// Outcome::from_or_forward(req, data, "Hello, world!")
/// }
/// ```
#[inline]
pub fn from_or_forward<T: 'r>(req: &'r Request<'_>, data: Data, responder: T) -> HandlerFuture<'r>
where T: Responder<'r> + Send
pub fn from_or_forward<R>(req: &'r Request<'_>, data: Data, responder: R) -> Outcome<'o>
where R: Responder<'r, 'o>
{
Box::pin(async move {
match responder.respond_to(req).await {
Ok(response) => outcome::Outcome::Success(response),
Err(_) => outcome::Outcome::Forward(data)
}
})
match responder.respond_to(req) {
Ok(response) => outcome::Outcome::Success(response),
Err(_) => outcome::Outcome::Forward(data)
}
}
/// Return an `Outcome` of `Failure` with the status code `code`. This is
@ -278,13 +272,11 @@ impl<'r> Outcome<'r> {
///
/// ```rust
/// use rocket::{Request, Data};
/// use rocket::handler::{Outcome, HandlerFuture};
/// use rocket::handler::Outcome;
/// use rocket::http::Status;
///
/// fn bad_req_route<'r>(_: &'r Request, _: Data) -> HandlerFuture<'r> {
/// Box::pin(async move {
/// Outcome::failure(Status::BadRequest)
/// })
/// fn bad_req_route<'r>(_: &'r Request, _: Data) -> Outcome<'r> {
/// Outcome::failure(Status::BadRequest)
/// }
/// ```
#[inline(always)]
@ -302,12 +294,10 @@ impl<'r> Outcome<'r> {
///
/// ```rust
/// use rocket::{Request, Data};
/// use rocket::handler::{Outcome, HandlerFuture};
/// use rocket::handler::Outcome;
///
/// fn always_forward<'r>(_: &'r Request, data: Data) -> HandlerFuture<'r> {
/// Box::pin(async move {
/// Outcome::forward(data)
/// })
/// fn always_forward<'r>(_: &'r Request, data: Data) -> Outcome<'r> {
/// Outcome::forward(data)
/// }
/// ```
#[inline(always)]

View File

@ -601,6 +601,12 @@ impl<S, E, F> Outcome<S, E, F> {
}
}
}
impl<'a, S: Send + 'a, E: Send + 'a, F: Send + 'a> Outcome<S, E, F> {
#[inline]
pub fn pin(self) -> futures::future::BoxFuture<'a, Self> {
Box::pin(async move { self })
}
}
/// Unwraps an [`Outcome`] to its success value, otherwise propagating the
/// forward or failure.

View File

@ -294,7 +294,11 @@ impl<'r> Request<'r> {
match guard.take() {
Some(jar) => {
let mutex = &self.state.cookies;
Cookies::new(jar, self.state.config.secret_key(), move |jar| *mutex.lock().expect("cookies lock") = Some(jar))
let on_drop = move |jar| {
*mutex.lock().expect("cookies lock") = Some(jar);
};
Cookies::new(jar, self.state.config.secret_key(), on_drop)
}
None => {
error_!("Multiple `Cookies` instances are active at once.");
@ -537,6 +541,13 @@ impl<'r> Request<'r> {
T::from_request(self)
}
#[inline(always)]
pub fn managed_state<T>(&self) -> Option<&'r T>
where T: Send + Sync + 'static
{
self.state.managed.try_get::<T>()
}
/// Retrieves the cached value for type `T` from the request-local cached
/// state of `self`. If no such value has previously been cached for this
/// request, `f` is called to produce the value which is subsequently

View File

@ -46,12 +46,10 @@ pub struct Content<R>(pub ContentType, pub R);
/// Overrides the Content-Type of the response to the wrapped `ContentType` then
/// delegates the remainder of the response to the wrapped responder.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Content<R> {
#[inline(always)]
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Content<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
Response::build()
.merge(self.1.respond_to(req).await?)
.merge(self.1.respond_to(req)?)
.header(self.0)
.ok()
}
@ -59,8 +57,6 @@ impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Content<R> {
macro_rules! ctrs {
($($name:ident: $ct:ident, $name_str:expr, $ct_str:expr),+) => {
use futures::future::BoxFuture;
$(
#[doc="Override the `Content-Type` of the response to <b>"]
#[doc=$name_str]
@ -74,10 +70,8 @@ macro_rules! ctrs {
/// Sets the Content-Type of the response then delegates the
/// remainder of the response to the wrapped responder.
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for $name<R> {
fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>>
where 'a: 'x, 'r: 'x, Self: 'x
{
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for $name<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
Content(ContentType::$ct, self.0).respond_to(req)
}
}

View File

@ -63,9 +63,8 @@ impl<E> From<E> for Debug<E> {
}
}
#[crate::async_trait]
impl<'r, E: std::fmt::Debug + Send + 'r> Responder<'r> for Debug<E> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
impl<'r, E: std::fmt::Debug> Responder<'r, 'static> for Debug<E> {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
warn_!("Debug: {:?}", Paint::default(self.0));
warn_!("Debug always responds with {}.", Status::InternalServerError);
Response::build().status(Status::InternalServerError).ok()

View File

@ -99,7 +99,7 @@ pub struct Flash<R> {
/// [`msg()`]: Flash::msg()
pub type FlashMessage<'a, 'r> = crate::response::Flash<&'a Request<'r>>;
impl<'r, R: Responder<'r>> Flash<R> {
impl<R> Flash<R> {
/// Constructs a new `Flash` message with the given `name`, `msg`, and
/// underlying `responder`.
///
@ -192,12 +192,11 @@ impl<'r, R: Responder<'r>> Flash<R> {
/// response. In other words, simply sets a cookie and delegates the rest of the
/// response handling to the wrapped responder. As a result, the `Outcome` of
/// the response is the `Outcome` of the wrapped `Responder`.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Flash<R> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Flash<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
trace_!("Flash: setting message: {}:{}", self.name, self.message);
req.cookies().add(self.cookie());
self.inner.respond_to(req).await
self.inner.respond_to(req)
}
}

View File

@ -37,7 +37,8 @@ pub mod status;
#[doc(hidden)] pub use rocket_codegen::Responder;
pub use self::response::{Response, ResponseBuilder, Body, DEFAULT_CHUNK_SIZE};
pub use self::response::DEFAULT_CHUNK_SIZE;
pub use self::response::{Response, ResponseBody, ResponseBuilder, Body};
pub use self::responder::Responder;
pub use self::redirect::Redirect;
pub use self::flash::Flash;
@ -47,4 +48,4 @@ pub use self::debug::Debug;
#[doc(inline)] pub use self::content::Content;
/// Type alias for the `Result` of a [`Responder::respond_to()`] call.
pub type Result<'r> = std::result::Result<self::Response<'r>, crate::http::Status>;
pub type Result<'r> = std::result::Result<Response<'r>, crate::http::Status>;

View File

@ -78,10 +78,9 @@ impl NamedFile {
/// recognized. See [`ContentType::from_extension()`] for more information. If
/// you would like to stream a file with a different Content-Type than that
/// implied by its extension, use a [`File`] directly.
#[crate::async_trait]
impl<'r> Responder<'r> for NamedFile {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
let mut response = self.1.respond_to(req).await?;
impl<'r> Responder<'r, 'static> for NamedFile {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
let mut response = self.1.respond_to(req)?;
if let Some(ext) = self.0.extension() {
if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) {
response.set_header(ct);

View File

@ -147,9 +147,8 @@ impl Redirect {
/// the `Location` header field. The body of the response is empty. If the URI
/// value used to create the `Responder` is an invalid URI, an error of
/// `Status::InternalServerError` is returned.
#[crate::async_trait]
impl<'r> Responder<'r> for Redirect {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
impl<'r> Responder<'r, 'static> for Redirect {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
if let Some(uri) = self.1 {
Response::build()
.status(self.0)

View File

@ -1,11 +1,8 @@
use std::fs::File;
use std::io::Cursor;
use tokio::io::BufReader;
use futures::future::BoxFuture;
use crate::http::{Status, ContentType, StatusClass};
use crate::response::{self, Response, Body};
use crate::response::{self, Response};
use crate::request::Request;
/// Trait implemented by types that generate responses for clients.
@ -113,44 +110,15 @@ use crate::request::Request;
/// regardless of the incoming request. Thus, knowing the type is sufficient to
/// fully determine its functionality.
///
/// # Async Trait
/// ## Lifetimes
///
/// [`Responder`] is an _async_ trait. Implementations of `Responder` may be
/// decorated with an attribute of `#[rocket::async_trait]`, allowing the
/// `respond_to` method to be implemented as an `async fn`:
///
/// ```rust
/// use rocket::response::{self, Responder};
/// use rocket::request::Request;
/// # struct MyType;
///
/// #[rocket::async_trait]
/// impl<'r> Responder<'r> for MyType {
/// async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
/// /* .. */
/// # unimplemented!()
/// }
/// }
/// ```
///
/// In certain cases, including when implementing a _wrapping_ responder, it may
/// be desirable to perform work before entering an `async` section, or to
/// return a future directly. In this case, `Responder` can be implemented as:
///
/// ```rust
/// use rocket::response::{self, Responder};
/// use rocket::futures::future::BoxFuture;
/// use rocket::request::Request;
/// # struct MyType<R> { inner: R };
///
/// impl<'r, R: Responder<'r>> Responder<'r> for MyType<R> {
/// fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>>
/// where 'a: 'x, 'r: 'x, Self: 'x
/// {
/// self.inner.respond_to(req)
/// }
/// }
/// ```
/// `Responder` has two lifetimes: `Responder<'r, 'o: 'r>`. The first lifetime,
/// `'r`, refers to the reference to the `&'r Request`, while the second
/// lifetime refers to the returned `Response<'o>`. The bound `'o: 'r` allows
/// `'o` to be any lifetime that lives at least as long as the `Request`. In
/// particular, this includes borrows from the `Request` itself (where `'o` would
/// be `'r` as in `impl<'r> Responder<'r, 'r>`) as well as `'static` data (where
/// `'o` would be `'static` as in `impl<'r> Responder<'r, 'static>`).
///
/// # Example
///
@ -196,12 +164,11 @@ use crate::request::Request;
/// use rocket::response::{self, Response, Responder};
/// use rocket::http::ContentType;
///
/// #[rocket::async_trait]
/// impl<'r> Responder<'r> for Person {
/// async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
/// impl<'r> Responder<'r, 'static> for Person {
/// fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
/// let person_string = format!("{}:{}", self.name, self.age);
/// Response::build()
/// .sized_body(Cursor::new(person_string)).await
/// .sized_body(person_string.len(), Cursor::new(person_string))
/// .raw_header("X-Person-Name", self.name)
/// .raw_header("X-Person-Age", self.age.to_string())
/// .header(ContentType::new("application", "x-person"))
@ -213,8 +180,7 @@ use crate::request::Request;
/// # fn person() -> Person { Person { name: "a".to_string(), age: 20 } }
/// # fn main() { }
/// ```
#[crate::async_trait]
pub trait Responder<'r> {
pub trait Responder<'r, 'o: 'r> {
/// Returns `Ok` if a `Response` could be generated successfully. Otherwise,
/// returns an `Err` with a failing `Status`.
///
@ -226,97 +192,83 @@ pub trait Responder<'r> {
/// returned, the error catcher for the given status is retrieved and called
/// to generate a final error response, which is then written out to the
/// client.
async fn respond_to(self, request: &'r Request<'_>) -> response::Result<'r>;
fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o>;
}
/// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for &'r str {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
impl<'r, 'o: 'r> Responder<'r, 'o> for &'o str {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
Response::build()
.header(ContentType::Plain)
.sized_body(Cursor::new(self)).await
.sized_body(self.len(), Cursor::new(self))
.ok()
}
}
/// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for String {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
impl<'r> Responder<'r, 'static> for String {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
Response::build()
.header(ContentType::Plain)
.sized_body(Cursor::new(self)).await
.sized_body(self.len(), Cursor::new(self))
.ok()
}
}
/// Returns a response with Content-Type `application/octet-stream` and a
/// fixed-size body containing the data in `self`. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for &'r [u8] {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
impl<'r, 'o: 'r> Responder<'r, 'o> for &'o [u8] {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
Response::build()
.header(ContentType::Binary)
.sized_body(Cursor::new(self)).await
.sized_body(self.len(), Cursor::new(self))
.ok()
}
}
/// Returns a response with Content-Type `application/octet-stream` and a
/// fixed-size body containing the data in `self`. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for Vec<u8> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
impl<'r> Responder<'r, 'static> for Vec<u8> {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
Response::build()
.header(ContentType::Binary)
.sized_body(Cursor::new(self)).await
.sized_body(self.len(), Cursor::new(self))
.ok()
}
}
/// Returns a response with a sized body for the file. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for File {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
tokio::fs::File::from(self).respond_to(req).await
impl<'r> Responder<'r, 'static> for File {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
tokio::fs::File::from(self).respond_to(req)
}
}
/// Returns a response with a sized body for the file. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for tokio::fs::File {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
let metadata = self.metadata().await;
let stream = BufReader::new(self);
match metadata {
Ok(md) => Response::build().raw_body(Body::Sized(stream, md.len())).ok(),
Err(_) => Response::build().streamed_body(stream).ok()
}
impl<'r> Responder<'r, 'static> for tokio::fs::File {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
Response::build().sized_body(None, self).ok()
}
}
/// Returns an empty, default `Response`. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for () {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
impl<'r> Responder<'r, 'static> for () {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
Ok(Response::new())
}
}
/// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints
/// a warning message and returns an `Err` of `Status::NotFound`.
impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>>
where 'a: 'x, 'r: 'x, Self: 'x
{
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Option<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
match self {
Some(r) => r.respond_to(req),
None => {
warn_!("Response was `None`.");
Box::pin(async { Err(Status::NotFound) })
Err(Status::NotFound)
},
}
}
@ -324,10 +276,10 @@ impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
// Responds with the wrapped `Responder` in `self`, whether it is `Ok` or
/// `Err`.
impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result<R, E> {
fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>>
where 'a: 'x, 'r: 'x, Self: 'x
{
impl<'r, 'o: 'r, 't: 'o, 'e: 'o, T, E> Responder<'r, 'o> for Result<T, E>
where T: Responder<'r, 't>, E: Responder<'r, 'e>
{
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
match self {
Ok(responder) => responder.respond_to(req),
Err(responder) => responder.respond_to(req),
@ -349,9 +301,8 @@ impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result<R, E> {
/// `100` responds with any empty body and the given status code, and all other
/// status code emit an error message and forward to the `500` (internal server
/// error) catcher.
#[crate::async_trait]
impl<'r> Responder<'r> for Status {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
impl<'r> Responder<'r, 'static> for Status {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
match self.class() {
StatusClass::ClientError | StatusClass::ServerError => Err(self),
StatusClass::Success if self.code < 206 => {

View File

@ -8,40 +8,33 @@ use crate::response::{self, Responder};
use crate::http::{Header, HeaderMap, Status, ContentType, Cookie};
/// The default size, in bytes, of a chunk for streamed responses.
pub const DEFAULT_CHUNK_SIZE: u64 = 4096;
pub const DEFAULT_CHUNK_SIZE: usize = 4096;
#[derive(PartialEq, Clone, Hash)]
/// The body of a response: can be sized or streamed/chunked.
pub enum Body<T> {
pub enum Body<A, B> {
/// A fixed-size body.
Sized(T, u64),
Sized(A, Option<usize>),
/// A streamed/chunked body, akin to `Transfer-Encoding: chunked`.
Chunked(T, u64)
Chunked(B, usize)
}
impl<T> Body<T> {
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 T> {
pub fn as_mut(&mut self) -> Body<&mut A, &mut B> {
match *self {
Body::Sized(ref mut b, n) => Body::Sized(b, n),
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 type as a parameter to `f` and
/// constructs a new body with the size of `self` and the return value of
/// the call to `f`.
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Body<U> {
/// 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(b, n) => Body::Sized(f(b), n),
Body::Chunked(b, n) => Body::Chunked(f(b), n)
}
}
/// Consumes `self` and returns the inner body.
pub fn into_inner(self) -> T {
match self {
Body::Sized(b, _) | Body::Chunked(b, _) => b
Body::Sized(a, n) => Body::Sized(f1(a), n),
Body::Chunked(b, n) => Body::Chunked(f2(b), n)
}
}
@ -62,13 +55,50 @@ impl<T> Body<T> {
}
}
impl<T: AsyncRead + Unpin> Body<T> {
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
{
/// 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(self) -> Option<Vec<u8>> {
pub async fn into_bytes(mut self) -> Option<Vec<u8>> {
let mut vec = Vec::new();
let mut body = self.into_inner();
if let Err(e) = body.read_to_end(&mut vec).await {
if let Err(e) = self.as_reader().read_to_end(&mut vec).await {
error_!("Error reading body: {:?}", e);
return None;
}
@ -89,10 +119,10 @@ impl<T: AsyncRead + Unpin> Body<T> {
}
}
impl<T> fmt::Debug for Body<T> {
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::Sized(_, n) => writeln!(f, "Sized Body [{:?} bytes]", n),
Body::Chunked(_, n) => writeln!(f, "Chunked Body [{} bytes]", n),
}
}
@ -147,16 +177,15 @@ impl<T> fmt::Debug for Body<T> {
/// use rocket::response::Response;
/// use rocket::http::{Status, ContentType};
///
/// # rocket::async_test(async {
/// let body = "Brewing the best coffee!";
/// let response = Response::build()
/// .status(Status::ImATeapot)
/// .header(ContentType::Plain)
/// .raw_header("X-Teapot-Make", "Rocket")
/// .raw_header("X-Teapot-Model", "Utopia")
/// .raw_header_adjoin("X-Teapot-Model", "Series 1")
/// .sized_body(Cursor::new("Brewing the best coffee!")).await
/// .sized_body(body.len(), Cursor::new(body))
/// .finalize();
/// # });
/// ```
pub struct ResponseBuilder<'r> {
response: Response<'r>,
@ -332,7 +361,10 @@ impl<'r> ResponseBuilder<'r> {
self
}
/// Sets the body of the `Response` to be the fixed-sized `body`.
/// 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.
///
/// # Example
///
@ -340,16 +372,16 @@ impl<'r> ResponseBuilder<'r> {
/// use std::io::Cursor;
/// use rocket::Response;
///
/// # rocket::async_test(async {
/// let body = "Hello, world!";
/// let response = Response::build()
/// .sized_body(Cursor::new("Hello, world!")).await
/// .sized_body(body.len(), Cursor::new(body))
/// .finalize();
/// # })
/// ```
pub async fn sized_body<B>(&mut self, 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 ResponseBuilder<'r>
where B: AsyncRead + AsyncSeek + Send + Unpin + 'r,
S: Into<Option<usize>>
{
self.response.set_sized_body(body).await;
self.response.set_sized_body(size, body);
self
}
@ -391,7 +423,7 @@ impl<'r> ResponseBuilder<'r> {
/// # }
/// ```
#[inline(always)]
pub fn chunked_body<B>(&mut self, body: B, chunk_size: u64) -> &mut ResponseBuilder<'r>
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);
@ -408,15 +440,16 @@ impl<'r> ResponseBuilder<'r> {
/// use std::io::Cursor;
/// use rocket::response::{Response, Body};
///
/// # rocket::async_test(async {
/// let s = "Hello!";
/// let body = Body::Sized(Cursor::new(s), Some(s.len()));
/// let response = Response::build()
/// .raw_body(Body::Sized(Cursor::new("Hello!"), 6))
/// .raw_body::<Cursor<&'static str>, Cursor<&'static str>>(body)
/// .finalize();
/// # })
/// ```
#[inline(always)]
pub fn raw_body<T>(&mut self, body: Body<T>) -> &mut ResponseBuilder<'r>
where T: AsyncRead + Send + Unpin + 'r
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);
self
@ -513,13 +546,12 @@ impl<'r> ResponseBuilder<'r> {
/// use rocket::Response;
/// use rocket::http::Status;
///
/// # rocket::async_test(async {
/// let body = "Brewing the best coffee!";
/// let response = Response::build()
/// .status(Status::ImATeapot)
/// .sized_body(Cursor::new("Brewing the best coffee!")).await
/// .sized_body(body.len(), Cursor::new(body))
/// .raw_header("X-Custom", "value 2")
/// .finalize();
/// # })
/// ```
pub fn finalize(&mut self) -> Response<'r> {
std::mem::replace(&mut self.response, Response::new())
@ -545,12 +577,20 @@ 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`].
#[derive(Default)]
pub struct Response<'r> {
status: Option<Status>,
headers: HeaderMap<'r>,
body: Option<Body<Pin<Box<dyn AsyncRead + Send + 'r>>>>,
body: Option<ResponseBody<'r>>,
}
impl<'r> Response<'r> {
@ -877,20 +917,14 @@ impl<'r> Response<'r> {
/// let mut response = Response::new();
/// assert!(response.body().is_none());
///
/// response.set_sized_body(Cursor::new("Hello, world!")).await;
/// let string = "Hello, world!";
/// response.set_sized_body(string.len(), Cursor::new(string));
/// assert_eq!(response.body_string().await, Some("Hello, world!".to_string()));
/// # })
/// ```
#[inline(always)]
pub fn body(&mut self) -> Option<Body<&mut (dyn AsyncRead + Unpin + Send)>> {
// Looks crazy, right? Needed so Rust infers lifetime correctly. Weird.
match self.body.as_mut() {
Some(body) => Some(match body.as_mut() {
Body::Sized(b, size) => Body::Sized(b, size),
Body::Chunked(b, chunk_size) => Body::Chunked(b, chunk_size),
}),
None => None
}
pub fn body(&mut self) -> Option<&mut ResponseBody<'r>> {
self.body.as_mut()
}
/// Consumes `self's` body and reads it into a string. If `self` doesn't
@ -908,7 +942,8 @@ impl<'r> Response<'r> {
/// let mut response = Response::new();
/// assert!(response.body().is_none());
///
/// response.set_sized_body(Cursor::new("Hello, world!")).await;
/// 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());
/// # })
@ -935,7 +970,8 @@ impl<'r> Response<'r> {
/// let mut response = Response::new();
/// assert!(response.body().is_none());
///
/// response.set_sized_body(Cursor::new("hi!")).await;
/// 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());
/// # })
@ -961,7 +997,8 @@ impl<'r> Response<'r> {
/// let mut response = Response::new();
/// assert!(response.body().is_none());
///
/// response.set_sized_body(Cursor::new("Hello, world!")).await;
/// let string = "Hello, world!";
/// response.set_sized_body(string.len(), Cursor::new(string));
/// assert!(response.body().is_some());
///
/// let body = response.take_body();
@ -974,7 +1011,7 @@ impl<'r> Response<'r> {
/// # })
/// ```
#[inline(always)]
pub fn take_body(&mut self) -> Option<Body<Pin<Box<dyn AsyncRead + Send + 'r>>>> {
pub fn take_body(&mut self) -> Option<ResponseBody<'r>> {
self.body.take()
}
@ -990,16 +1027,10 @@ impl<'r> Response<'r> {
}
}
/// Sets the body of `self` to be the fixed-sized `body`. The size of the
/// body is obtained by `seek`ing to the end and then `seek`ing back to the
/// start. Since this is an asynchronous operation, it returns a future
/// and should be `await`-ed on.
///
/// # Panics
///
/// If either seek fails, this method panics. If you believe it is possible
/// for `seek` to panic for `B`, use [set_raw_body](#method.set_raw_body)
/// instead.
/// Sets the body of `self` 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.
///
/// # Example
///
@ -1008,19 +1039,18 @@ impl<'r> Response<'r> {
/// use rocket::Response;
///
/// # rocket::async_test(async {
/// let string = "Hello, world!";
///
/// let mut response = Response::new();
/// response.set_sized_body(Cursor::new("Hello, world!")).await;
/// response.set_sized_body(string.len(), Cursor::new(string));
/// assert_eq!(response.body_string().await, Some("Hello, world!".to_string()));
/// # })
/// ```
pub async fn set_sized_body<B>(&mut self, mut body: B)
where B: AsyncRead + AsyncSeek + Send + Unpin + 'r
pub fn set_sized_body<B, S>(&mut self, size: S, body: B)
where B: AsyncRead + AsyncSeek + Send + Unpin + 'r,
S: Into<Option<usize>>
{
let size = body.seek(io::SeekFrom::End(0)).await
.expect("Attempted to retrieve size by seeking, but failed.");
body.seek(io::SeekFrom::Start(0)).await
.expect("Attempted to reset body by seeking after getting size.");
self.body = Some(Body::Sized(Box::pin(body.take(size)), size));
self.body = Some(Body::Sized(Box::pin(body), size.into()));
}
/// Sets the body of `self` to be `body`, which will be streamed. The chunk
@ -1061,7 +1091,7 @@ impl<'r> Response<'r> {
/// # })
/// ```
#[inline(always)]
pub fn set_chunked_body<B>(&mut self, body: B, chunk_size: u64)
pub fn set_chunked_body<B>(&mut self, body: B, chunk_size: usize)
where B: AsyncRead + Send + 'r
{
self.body = Some(Body::Chunked(Box::pin(body), chunk_size));
@ -1078,19 +1108,22 @@ impl<'r> Response<'r> {
/// use rocket::response::{Response, Body};
///
/// # rocket::async_test(async {
/// let body = Body::Sized(Cursor::new("Hello!"), 6);
/// let string = "Hello!";
///
/// let mut response = Response::new();
/// response.set_raw_body(body);
/// 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, Some("Hello!".to_string()));
/// # })
/// ```
#[inline(always)]
pub fn set_raw_body<T>(&mut self, body: Body<T>)
where T: AsyncRead + Send + Unpin + 'r {
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(b, n) => Body::Sized(Box::pin(b.take(n)), n),
Body::Sized(a, n) => Body::Sized(Box::pin(a), n),
Body::Chunked(b, n) => Body::Chunked(Box::pin(b), n),
});
}
@ -1203,10 +1236,9 @@ impl fmt::Debug for Response<'_> {
use crate::request::Request;
#[crate::async_trait]
impl<'r> Responder<'r> for Response<'r> {
impl<'r, 'o: 'r> Responder<'r, 'o> for Response<'o> {
/// This is the identity implementation. It simply returns `Ok(self)`.
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
Ok(self)
}
}

View File

@ -97,9 +97,7 @@ impl<'r, R> Created<R> {
/// assert_eq!(etag, None);
/// # });
/// ```
pub fn body(mut self, responder: R) -> Self
where R: Responder<'r>
{
pub fn body(mut self, responder: R) -> Self {
self.1 = Some(responder);
self
}
@ -135,9 +133,7 @@ impl<'r, R> Created<R> {
/// assert_eq!(etag, Some(r#""13046220615156895040""#));
/// # });
/// ```
pub fn tagged_body(mut self, responder: R) -> Self
where R: Responder<'r> + Hash
{
pub fn tagged_body(mut self, responder: R) -> Self where R: Hash {
let mut hasher = &mut DefaultHasher::default();
responder.hash(&mut hasher);
let hash = hasher.finish();
@ -160,12 +156,11 @@ impl<'r, R> Created<R> {
/// the response with the `Responder`, the `ETag` header is set conditionally if
/// a hashable `Responder` is provided via [`Created::tagged_body()`]. The `ETag`
/// header is set to a hash value of the responder.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Created<R> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Created<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
let mut response = Response::build();
if let Some(responder) = self.1 {
response.merge(responder.respond_to(req).await?);
response.merge(responder.respond_to(req)?);
}
if let Some(hash) = self.2 {
@ -207,12 +202,11 @@ pub struct Accepted<R>(pub Option<R>);
/// Sets the status code of the response to 202 Accepted. If the responder is
/// `Some`, it is used to finalize the response.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Accepted<R> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Accepted<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond_to(req).await?);
build.merge(responder.respond_to(req)?);
}
build.status(Status::Accepted).ok()
@ -237,10 +231,9 @@ impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Accepted<R> {
pub struct NoContent;
/// Sets the status code of the response to 204 No Content.
impl<'r> Responder<'r> for NoContent {
fn respond_to(self, _: &Request<'_>) -> Result<Response<'r>, Status> {
let mut build = Response::build();
build.status(Status::NoContent).ok()
impl<'r> Responder<'r, 'static> for NoContent {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
Response::build().status(Status::NoContent).ok()
}
}
@ -273,12 +266,11 @@ pub struct BadRequest<R>(pub Option<R>);
/// Sets the status code of the response to 400 Bad Request. If the responder is
/// `Some`, it is used to finalize the response.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for BadRequest<R> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for BadRequest<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond_to(req).await?);
build.merge(responder.respond_to(req)?);
}
build.status(Status::BadRequest).ok()
@ -314,8 +306,8 @@ pub struct Unauthorized<R>(pub Option<R>);
/// Sets the status code of the response to 401 Unauthorized. If the responder is
/// `Some`, it is used to finalize the response.
impl<'r, R: Responder<'r>> Responder<'r> for Unauthorized<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> {
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Unauthorized<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond_to(req)?);
@ -354,8 +346,8 @@ pub struct Forbidden<R>(pub Option<R>);
/// Sets the status code of the response to 403 Forbidden. If the responder is
/// `Some`, it is used to finalize the response.
impl<'r, R: Responder<'r>> Responder<'r> for Forbidden<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> {
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Forbidden<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond_to(req)?);
@ -381,10 +373,9 @@ impl<'r, R: Responder<'r>> Responder<'r> for Forbidden<R> {
pub struct NotFound<R>(pub R);
/// Sets the status code of the response to 404 Not Found.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for NotFound<R> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
Response::build_from(self.0.respond_to(req).await?)
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for NotFound<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
Response::build_from(self.0.respond_to(req)?)
.status(Status::NotFound)
.ok()
}
@ -420,8 +411,8 @@ pub struct Conflict<R>(pub Option<R>);
/// Sets the status code of the response to 409 Conflict. If the responder is
/// `Some`, it is used to finalize the response.
impl<'r, R: Responder<'r>> Responder<'r> for Conflict<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> {
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Conflict<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond_to(req)?);
@ -447,10 +438,9 @@ pub struct Custom<R>(pub Status, pub R);
/// Sets the status code of the response and then delegates the remainder of the
/// response to the wrapped responder.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Custom<R> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
Response::build_from(self.1.respond_to(req).await?)
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Custom<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
Response::build_from(self.1.respond_to(req)?)
.status(self.0)
.ok()
}

View File

@ -11,7 +11,7 @@ use crate::response::{self, Response, Responder, DEFAULT_CHUNK_SIZE};
/// 4KiB. This means that at most 4KiB are stored in memory while the response
/// is being sent. This type should be used when sending responses that are
/// arbitrarily large in size, such as when streaming from a local socket.
pub struct Stream<T: AsyncRead>(T, u64);
pub struct Stream<T: AsyncRead>(T, usize);
impl<T: AsyncRead> Stream<T> {
/// Create a new stream from the given `reader` and sets the chunk size for
@ -28,7 +28,7 @@ impl<T: AsyncRead> Stream<T> {
/// # #[allow(unused_variables)]
/// let response = Stream::chunked(tokio::io::stdin(), 10);
/// ```
pub fn chunked(reader: T, chunk_size: u64) -> Stream<T> {
pub fn chunked(reader: T, chunk_size: usize) -> Stream<T> {
Stream(reader, chunk_size)
}
}
@ -66,9 +66,8 @@ impl<T: AsyncRead> From<T> for Stream<T> {
/// If reading from the input stream fails at any point during the response, the
/// response is abandoned, and the response ends abruptly. An error is printed
/// to the console with an indication of what went wrong.
#[crate::async_trait]
impl<'r, T: AsyncRead + Send + 'r> Responder<'r> for Stream<T> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
impl<'r, 'o: 'r, T: AsyncRead + Send + 'o> Responder<'r, 'o> for Stream<T> {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
Response::build().chunked_body(self.0, self.1).ok()
}
}

View File

@ -1,12 +1,10 @@
use std::collections::HashMap;
use std::convert::{From, TryInto};
use std::{io, mem};
use std::cmp::min;
use std::io;
use std::mem;
use std::sync::Arc;
use std::collections::HashMap;
use futures::future::{Future, FutureExt, BoxFuture};
use futures::stream::StreamExt;
use futures::future::{Future, FutureExt, BoxFuture};
use tokio::sync::{mpsc, oneshot};
use yansi::Paint;
@ -143,8 +141,13 @@ impl Manifest {
}
let send_response = move |hyp_res: hyper::ResponseBuilder, body| -> io::Result<()> {
let response = hyp_res.body(body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
tx.send(response).map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "Client disconnected before the response was started"))
let response = hyp_res.body(body)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
tx.send(response).map_err(|_| {
let msg = "Client disconnected before the response was started";
io::Error::new(io::ErrorKind::BrokenPipe, msg)
})
};
match response.body() {
@ -153,23 +156,22 @@ impl Manifest {
send_response(hyp_res, hyper::Body::empty())?;
}
Some(body) => {
let (body, chunk_size) = match body {
Body::Chunked(body, chunk_size) => {
(body, chunk_size.try_into().expect("u64 -> usize overflow"))
}
Body::Sized(body, size) => {
hyp_res = hyp_res.header(header::CONTENT_LENGTH, size.to_string());
(body, 4096usize)
}
if let Some(s) = body.size().await {
hyp_res = hyp_res.header(header::CONTENT_LENGTH, s.to_string());
}
let chunk_size = match *body {
Body::Chunked(_, chunk_size) => chunk_size as usize,
Body::Sized(_, _) => crate::response::DEFAULT_CHUNK_SIZE,
};
let (mut sender, hyp_body) = hyper::Body::channel();
send_response(hyp_res, hyp_body)?;
let mut stream = body.into_bytes_stream(chunk_size);
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))?;
sender.send_data(next?).await
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
}
}
};

View File

@ -17,7 +17,7 @@ type Selector = Method;
pub(crate) fn dummy_handler<'r>(
r: &'r Request<'_>, _: crate::Data
) -> BoxFuture<'r, crate::handler::Outcome<'r>> {
crate::Outcome::from(r, ())
crate::Outcome::from(r, ()).pin()
}
#[derive(Default)]

View File

@ -22,27 +22,24 @@ fn other() -> content::Json<&'static str> {
mod head_handling_tests {
use super::*;
use tokio::io::{AsyncRead, AsyncReadExt};
use tokio::io::AsyncReadExt;
use rocket::Route;
use rocket::local::Client;
use rocket::http::{Status, ContentType};
use rocket::response::Body;
use rocket::response::ResponseBody;
fn routes() -> Vec<Route> {
routes![index, empty, other]
}
async fn assert_empty_sized_body<T: AsyncRead + Unpin>(body: Body<T>, expected_size: u64) {
match body {
Body::Sized(mut body, size) => {
let mut buffer = vec![];
body.read_to_end(&mut buffer).await.unwrap();
assert_eq!(size, expected_size);
assert_eq!(buffer.len(), 0);
}
_ => panic!("Expected a sized body.")
}
async fn assert_empty_sized_body(body: &mut ResponseBody<'_>, expected_size: usize) {
let size = body.size().await.expect("sized body");
assert_eq!(size, expected_size);
let mut buffer = vec![];
body.as_reader().read_to_end(&mut buffer).await.unwrap();
assert_eq!(buffer.len(), 0);
}
#[rocket::async_test]

View File

@ -4,7 +4,6 @@
#[macro_use] extern crate rocket;
use rocket::{Request, State};
use rocket::futures::future::BoxFuture;
use rocket::response::{Responder, Result};
struct SomeState;
@ -14,11 +13,9 @@ pub struct CustomResponder<'r, R> {
state: &'r SomeState,
}
impl<'r, R: Responder<'r>> Responder<'r> for CustomResponder<'r, R> {
fn respond_to<'a, 'x>(self, _: &'r Request<'a>) -> BoxFuture<'x, Result<'r>>
where 'a: 'x, 'r: 'x, Self: 'x
{
unimplemented!()
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for CustomResponder<'r, R> {
fn respond_to(self, req: &'r Request<'_>) -> Result<'o> {
self.responder.respond_to(req)
}
}

View File

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

View File

@ -7,11 +7,10 @@ use std::env;
use rocket::{Request, Handler, Route, Data, Catcher, try_outcome};
use rocket::http::{Status, RawStr};
use rocket::response::{self, Responder, status::Custom};
use rocket::handler::{Outcome, HandlerFuture};
use rocket::response::{Responder, status::Custom};
use rocket::handler::{Outcome, HandlerFuture, ErrorHandlerFuture};
use rocket::outcome::IntoOutcome;
use rocket::http::Method::*;
use rocket::futures::future::BoxFuture;
use rocket::tokio::fs::File;
fn forward<'r>(_req: &'r Request, data: Data) -> HandlerFuture<'r> {
@ -19,26 +18,25 @@ fn forward<'r>(_req: &'r Request, data: Data) -> HandlerFuture<'r> {
}
fn hi<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> {
Box::pin(async move { Outcome::from(req, "Hello!").await })
Outcome::from(req, "Hello!").pin()
}
fn name<'a>(req: &'a Request, _: Data) -> HandlerFuture<'a> {
Box::pin(async move {
let param = req.get_param::<&'a RawStr>(0)
.and_then(|res| res.ok())
.unwrap_or("unnamed".into());
let param = req.get_param::<&'a RawStr>(0)
.and_then(|res| res.ok())
.unwrap_or("unnamed".into());
Outcome::from(req, param.as_str()).await
})
Outcome::from(req, param.as_str()).pin()
}
fn echo_url<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> {
let param_outcome = req.get_param::<&RawStr>(1)
.and_then(|res| res.ok())
.into_outcome(Status::BadRequest);
Box::pin(async move {
let param_outcome = req.get_param::<&RawStr>(1)
.and_then(|res| res.ok())
.into_outcome(Status::BadRequest);
let param = try_outcome!(param_outcome);
Outcome::try_from(req, RawStr::from_str(param).url_decode()).await
Outcome::try_from(req, RawStr::from_str(param).url_decode())
})
}
@ -52,7 +50,7 @@ fn upload<'r>(req: &'r Request, data: Data) -> HandlerFuture<'r> {
let file = File::create(env::temp_dir().join("upload.txt")).await;
if let Ok(file) = file {
if let Ok(n) = data.stream_to(file).await {
return Outcome::from(req, format!("OK: {} bytes uploaded.", n)).await;
return Outcome::from(req, format!("OK: {} bytes uploaded.", n));
}
println!(" => Failed copying.");
@ -65,12 +63,12 @@ fn upload<'r>(req: &'r Request, data: Data) -> HandlerFuture<'r> {
}
fn get_upload<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> {
Outcome::from(req, std::fs::File::open(env::temp_dir().join("upload.txt")).ok())
Outcome::from(req, std::fs::File::open(env::temp_dir().join("upload.txt")).ok()).pin()
}
fn not_found_handler<'r>(req: &'r Request) -> BoxFuture<'r, response::Result<'r>> {
fn not_found_handler<'r>(req: &'r Request) -> ErrorHandlerFuture<'r> {
let res = Custom(Status::NotFound, format!("Couldn't find: {}", req.uri()));
res.respond_to(req)
Box::pin(async move { res.respond_to(req) })
}
#[derive(Clone)]
@ -87,13 +85,13 @@ impl CustomHandler {
impl Handler for CustomHandler {
fn handle<'r>(&self, req: &'r Request, data: Data) -> HandlerFuture<'r> {
let self_data = self.data;
Box::pin(async move {
let id_outcome = req.get_param::<&RawStr>(0)
.and_then(|res| res.ok())
.or_forward(data);
let id_outcome = req.get_param::<&RawStr>(0)
.and_then(|res| res.ok())
.or_forward(data);
Box::pin(async move {
let id = try_outcome!(id_outcome);
Outcome::from(req, format!("{} - {}", self_data, id)).await
Outcome::from(req, format!("{} - {}", self_data, id))
})
}
}