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.** * **The [`Responder`] trait has changed.**
`Responder::respond(self)` was removed in favor of `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. their response based on the incoming request.
* **`Outcome::of(Responder)` was removed while `Outcome::from(&Request, * **`Outcome::of(Responder)` was removed while `Outcome::from(&Request,

View File

@ -34,9 +34,9 @@ use super::CompressionUtils;
#[derive(Debug)] #[derive(Debug)]
pub struct Compress<R>(pub R); 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)] #[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() let mut response = Response::build()
.merge(self.0.respond_to(request)?) .merge(self.0.respond_to(request)?)
.finalize(); .finalize();

View File

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

View File

@ -23,7 +23,6 @@ use rocket::outcome::Outcome::*;
use rocket::data::{Data, FromData, FromDataFuture, Transform::*, TransformFuture, Transformed}; use rocket::data::{Data, FromData, FromDataFuture, Transform::*, TransformFuture, Transformed};
use rocket::http::Status; use rocket::http::Status;
use rocket::response::{self, content, Responder}; use rocket::response::{self, content, Responder};
use rocket::futures::future::BoxFuture;
use serde::Serialize; use serde::Serialize;
use serde::de::Deserialize; 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 /// Serializes the wrapped value into MessagePack. Returns a response with
/// Content-Type `MsgPack` and a fixed-size body with the serialization. If /// Content-Type `MsgPack` and a fixed-size body with the serialization. If
/// serialization fails, an `Err` of `Status::InternalServerError` is returned. /// serialization fails, an `Err` of `Status::InternalServerError` is returned.
impl<'r, T: Serialize> Responder<'r> for MsgPack<T> { impl<'r, T: Serialize> Responder<'r, 'static> for MsgPack<T> {
fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>> fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
where 'a: 'x, 'r: 'x, Self: 'x let buf = rmp_serde::to_vec(&self.0)
{ .map_err(|e| {
match rmp_serde::to_vec(&self.0) {
Ok(buf) => content::MsgPack(buf).respond_to(req),
Err(e) => {
error_!("MsgPack failed to serialize: {:?}", 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 { impl Handler for StaticFiles {
fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> HandlerFuture<'r> { 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('/') { if opt.contains(Options::NormalizeDirs) && !r.uri().path().ends_with('/') {
let new_path = r.uri().map_path(|p| p.to_owned() + "/") 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") .expect("adding a trailing slash to a known good path results in a valid path")
.into_owned(); .into_owned();
return Box::pin(async move { return Outcome::from_or_forward(r, d, Redirect::permanent(new_path));
Outcome::from_or_forward(r, d, Redirect::permanent(new_path))
});
} }
if !opt.contains(Options::Index) { 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(); 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 current_route = req.route().expect("route while handling");
let is_segments_route = current_route.uri.path().ends_with(">"); let is_segments_route = current_route.uri.path().ends_with(">");
if !is_segments_route { 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`, // Otherwise, we're handling segments. Get the segments as a `PathBuf`,
@ -316,7 +314,7 @@ impl Handler for StaticFiles {
match &path { match &path {
Some(path) if path.is_dir() => handle_dir(self.options, req, data, 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()), 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::borrow::Cow;
use std::path::PathBuf; use std::path::PathBuf;
use rocket::{Manifest, State}; use rocket::Manifest;
use rocket::request::Request; use rocket::request::Request;
use rocket::fairing::Fairing; use rocket::fairing::Fairing;
use rocket::response::{self, Content, Responder}; use rocket::response::{self, Content, Responder};
@ -389,20 +389,19 @@ impl Template {
/// Returns a response with the Content-Type derived from the template's /// Returns a response with the Content-Type derived from the template's
/// extension and a fixed-size body containing the rendered template. If /// extension and a fixed-size body containing the rendered template. If
/// rendering fails, an `Err` of `Status::InternalServerError` is returned. /// rendering fails, an `Err` of `Status::InternalServerError` is returned.
#[rocket::async_trait] impl<'r> Responder<'r, 'static> for Template {
impl<'r> Responder<'r> for Template { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
let (render, content_type) = { 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."); error_!("Uninitialized template context: missing fairing.");
info_!("To use templates, you must attach `Template::fairing()`."); info_!("To use templates, you must attach `Template::fairing()`.");
info_!("See the `Template` documentation for more information."); info_!("See the `Template` documentation for more information.");
Status::InternalServerError Status::InternalServerError
})?.inner().context(); })?.context();
self.finalize(&ctxt)? 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: #fn_sig = #user_catcher_fn_name;
let #catcher = #user_catcher_fn_name; let #catcher = #user_catcher_fn_name;
#responder_stmt #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. // 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() let parameter_names = route.inputs.iter()
.map(|(_, rocket_ident, _)| rocket_ident); .map(|(_, rocket_ident, _)| rocket_ident);
let responder_stmt = if route.function.sig.asyncness.is_some() { let _await = route.function.sig.asyncness.map(|a| quote_spanned!(a.span().into() => .await));
quote_spanned! { ret_span => let responder_stmt = quote_spanned! { ret_span =>
let ___responder = #user_handler_fn_name(#(#parameter_names),*).await; let ___responder = #user_handler_fn_name(#(#parameter_names),*) #_await;
}
} else {
quote_spanned! { ret_span =>
let ___responder = #user_handler_fn_name(#(#parameter_names),*);
}
}; };
quote_spanned! { ret_span => quote_spanned! { ret_span =>
#responder_stmt #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 { 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) .generic_support(GenericSupport::Lifetime)
.data_support(DataSupport::Struct | DataSupport::Enum) .data_support(DataSupport::Struct | DataSupport::Enum)
.replace_generic(0, 0) .replace_generic(0, 0)
@ -30,17 +34,15 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
false => Ok(()) false => Ok(())
}) })
.function(|_, inner| quote! { .function(|_, inner| quote! {
fn respond_to<'__i, '__x>( fn respond_to(
self, self,
__req: &'__r ::rocket::request::Request<'__i> __req: &'__r ::rocket::request::Request
) -> ::rocket::futures::future::BoxFuture<'__x, ::rocket::response::Result<'__r>> ) -> ::rocket::response::Result<'__r> {
where '__i: '__x, '__r: '__x, Self: '__x
{
#inner #inner
} }
}) })
.try_map_fields(|_, fields| { .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 { fn set_header_tokens<T: ToTokens + Spanned>(item: T) -> TokenStream2 {
quote_spanned!(item.span().into() => __res.set_header(#item);) 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() => quote_spanned! { f.span().into() =>
let mut __res = <#ty as ::rocket::response::Responder>::respond_to( let mut __res = <#ty as ::rocket::response::Responder>::respond_to(
#accessor, __req #accessor, __req
).await?; )?;
} }
}).expect("have at least one field"); }).expect("have at least one field");
@ -73,13 +75,11 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
}); });
Ok(quote! { Ok(quote! {
#_Box::pin(async move { #responder
#responder #(#headers)*
#(#headers)* #content_type
#content_type #status
#status #_Ok(__res)
#_Ok(__res)
})
}) })
}) })
.to_tokens() .to_tokens()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,16 +44,39 @@ pub struct AdHoc {
kind: AdHocKind, 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 { enum AdHocKind {
/// An ad-hoc **attach** fairing. Called when the fairing is attached. /// 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. /// An ad-hoc **launch** fairing. Called just before Rocket launches.
Launch(Mutex<Option<Box<dyn FnOnce(&Manifest) + Send + 'static>>>), Launch(Mutex<Option<Box<dyn FnOnce(&Manifest) + Send + 'static>>>),
/// An ad-hoc **request** fairing. Called when a request is received. /// 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 /// An ad-hoc **response** fairing. Called when a response is ready to be
/// sent to a client. /// 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 { impl AdHoc {
@ -73,7 +96,10 @@ impl AdHoc {
F: FnOnce(Rocket) -> Fut + Send + 'static, F: FnOnce(Rocket) -> Fut + Send + 'static,
Fut: Future<Output=Result<Rocket, Rocket>> + 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 /// 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)) } 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` /// Constructs an `AdHoc` response fairing named `name`. The function `f`
/// will be called and the returned `Future` will be `await`ed by Rocket /// 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); /// let body = format!("Get: {}\nPost: {}", get_count, post_count);
/// res.set_status(Status::Ok); /// res.set_status(Status::Ok);
/// res.set_header(ContentType::Plain); /// 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 /// ```rust,no_run
/// # #[derive(Copy, Clone)] enum Kind { Simple, Intermediate, Complex, } /// # #[derive(Copy, Clone)] enum Kind { Simple, Intermediate, Complex, }
/// use rocket::{Request, Data, Route, http::Method}; /// use rocket::{Request, Data, Route, http::Method};
/// use rocket::handler::{self, Handler, Outcome, HandlerFuture}; /// use rocket::handler::{self, Handler, Outcome};
/// ///
/// #[derive(Clone)] /// #[derive(Clone)]
/// struct CustomHandler(Kind); /// struct CustomHandler(Kind);
/// ///
/// impl Handler for CustomHandler { /// 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 { /// match self.0 {
/// Kind::Simple => Outcome::from(req, "simple"), /// Kind::Simple => Outcome::from(req, "simple"),
/// Kind::Intermediate => Outcome::from(req, "intermediate"), /// 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. /// Type type of `Future` returned by an error handler.
pub type ErrorHandlerFuture<'r> = BoxFuture<'r, response::Result<'r>>; 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`. /// Return the `Outcome` of response to `req` from `responder`.
/// ///
/// If the responder returns `Ok`, an outcome of `Success` is /// If the responder returns `Ok`, an outcome of `Success` is
@ -195,20 +195,18 @@ impl<'r> Outcome<'r> {
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Data}; /// 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!") /// Outcome::from(req, "Hello, world!")
/// } /// }
/// ``` /// ```
#[inline] #[inline]
pub fn from<T: Responder<'r> + Send + 'r>(req: &'r Request<'_>, responder: T) -> HandlerFuture<'r> { pub fn from<R: Responder<'r, 'o>>(req: &'r Request<'_>, responder: R) -> Outcome<'o> {
Box::pin(async move { match responder.respond_to(req) {
match responder.respond_to(req).await { Ok(response) => outcome::Outcome::Success(response),
Ok(response) => outcome::Outcome::Success(response), Err(status) => outcome::Outcome::Failure(status)
Err(status) => outcome::Outcome::Failure(status) }
}
})
} }
/// Return the `Outcome` of response to `req` from `responder`. /// Return the `Outcome` of response to `req` from `responder`.
@ -221,23 +219,21 @@ impl<'r> Outcome<'r> {
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Data}; /// 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!") /// Outcome::from(req, "Hello, world!")
/// } /// }
/// ``` /// ```
#[inline] #[inline]
pub fn try_from<T, E>(req: &'r Request<'_>, result: Result<T, E>) -> HandlerFuture<'r> pub fn try_from<R, E>(req: &'r Request<'_>, result: Result<R, E>) -> Outcome<'o>
where T: Responder<'r> + Send + 'r, E: std::fmt::Debug + Send + 'r where R: Responder<'r, 'o>, E: std::fmt::Debug
{ {
Box::pin(async move { let responder = result.map_err(crate::response::Debug);
let responder = result.map_err(crate::response::Debug); match responder.respond_to(req) {
match responder.respond_to(req).await { Ok(response) => outcome::Outcome::Success(response),
Ok(response) => outcome::Outcome::Success(response), Err(status) => outcome::Outcome::Failure(status)
Err(status) => outcome::Outcome::Failure(status) }
}
})
} }
/// Return the `Outcome` of response to `req` from `responder`. /// Return the `Outcome` of response to `req` from `responder`.
@ -250,22 +246,20 @@ impl<'r> Outcome<'r> {
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Data}; /// 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!") /// Outcome::from_or_forward(req, data, "Hello, world!")
/// } /// }
/// ``` /// ```
#[inline] #[inline]
pub fn from_or_forward<T: 'r>(req: &'r Request<'_>, data: Data, responder: T) -> HandlerFuture<'r> pub fn from_or_forward<R>(req: &'r Request<'_>, data: Data, responder: R) -> Outcome<'o>
where T: Responder<'r> + Send where R: Responder<'r, 'o>
{ {
Box::pin(async move { match responder.respond_to(req) {
match responder.respond_to(req).await { Ok(response) => outcome::Outcome::Success(response),
Ok(response) => outcome::Outcome::Success(response), Err(_) => outcome::Outcome::Forward(data)
Err(_) => outcome::Outcome::Forward(data) }
}
})
} }
/// Return an `Outcome` of `Failure` with the status code `code`. This is /// Return an `Outcome` of `Failure` with the status code `code`. This is
@ -278,13 +272,11 @@ impl<'r> Outcome<'r> {
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Data}; /// use rocket::{Request, Data};
/// use rocket::handler::{Outcome, HandlerFuture}; /// use rocket::handler::Outcome;
/// use rocket::http::Status; /// use rocket::http::Status;
/// ///
/// fn bad_req_route<'r>(_: &'r Request, _: Data) -> HandlerFuture<'r> { /// fn bad_req_route<'r>(_: &'r Request, _: Data) -> Outcome<'r> {
/// Box::pin(async move { /// Outcome::failure(Status::BadRequest)
/// Outcome::failure(Status::BadRequest)
/// })
/// } /// }
/// ``` /// ```
#[inline(always)] #[inline(always)]
@ -302,12 +294,10 @@ impl<'r> Outcome<'r> {
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Data}; /// use rocket::{Request, Data};
/// use rocket::handler::{Outcome, HandlerFuture}; /// use rocket::handler::Outcome;
/// ///
/// fn always_forward<'r>(_: &'r Request, data: Data) -> HandlerFuture<'r> { /// fn always_forward<'r>(_: &'r Request, data: Data) -> Outcome<'r> {
/// Box::pin(async move { /// Outcome::forward(data)
/// Outcome::forward(data)
/// })
/// } /// }
/// ``` /// ```
#[inline(always)] #[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 /// Unwraps an [`Outcome`] to its success value, otherwise propagating the
/// forward or failure. /// forward or failure.

View File

@ -294,7 +294,11 @@ impl<'r> Request<'r> {
match guard.take() { match guard.take() {
Some(jar) => { Some(jar) => {
let mutex = &self.state.cookies; 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 => { None => {
error_!("Multiple `Cookies` instances are active at once."); error_!("Multiple `Cookies` instances are active at once.");
@ -537,6 +541,13 @@ impl<'r> Request<'r> {
T::from_request(self) 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 /// 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 /// state of `self`. If no such value has previously been cached for this
/// request, `f` is called to produce the value which is subsequently /// 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 /// Overrides the Content-Type of the response to the wrapped `ContentType` then
/// delegates the remainder of the response to the wrapped responder. /// delegates the remainder of the response to the wrapped responder.
#[crate::async_trait] impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Content<R> {
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Content<R> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
#[inline(always)]
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
Response::build() Response::build()
.merge(self.1.respond_to(req).await?) .merge(self.1.respond_to(req)?)
.header(self.0) .header(self.0)
.ok() .ok()
} }
@ -59,8 +57,6 @@ impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Content<R> {
macro_rules! ctrs { macro_rules! ctrs {
($($name:ident: $ct:ident, $name_str:expr, $ct_str:expr),+) => { ($($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="Override the `Content-Type` of the response to <b>"]
#[doc=$name_str] #[doc=$name_str]
@ -74,10 +70,8 @@ macro_rules! ctrs {
/// Sets the Content-Type of the response then delegates the /// Sets the Content-Type of the response then delegates the
/// remainder of the response to the wrapped responder. /// remainder of the response to the wrapped responder.
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for $name<R> { impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for $name<R> {
fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>> fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
where 'a: 'x, 'r: 'x, Self: 'x
{
Content(ContentType::$ct, self.0).respond_to(req) 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> Responder<'r, 'static> for Debug<E> {
impl<'r, E: std::fmt::Debug + Send + 'r> Responder<'r> for Debug<E> { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
warn_!("Debug: {:?}", Paint::default(self.0)); warn_!("Debug: {:?}", Paint::default(self.0));
warn_!("Debug always responds with {}.", Status::InternalServerError); warn_!("Debug always responds with {}.", Status::InternalServerError);
Response::build().status(Status::InternalServerError).ok() Response::build().status(Status::InternalServerError).ok()

View File

@ -99,7 +99,7 @@ pub struct Flash<R> {
/// [`msg()`]: Flash::msg() /// [`msg()`]: Flash::msg()
pub type FlashMessage<'a, 'r> = crate::response::Flash<&'a Request<'r>>; 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 /// Constructs a new `Flash` message with the given `name`, `msg`, and
/// underlying `responder`. /// 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. 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 /// response handling to the wrapped responder. As a result, the `Outcome` of
/// the response is the `Outcome` of the wrapped `Responder`. /// the response is the `Outcome` of the wrapped `Responder`.
#[crate::async_trait] impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Flash<R> {
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Flash<R> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
trace_!("Flash: setting message: {}:{}", self.name, self.message); trace_!("Flash: setting message: {}:{}", self.name, self.message);
req.cookies().add(self.cookie()); 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; #[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::responder::Responder;
pub use self::redirect::Redirect; pub use self::redirect::Redirect;
pub use self::flash::Flash; pub use self::flash::Flash;
@ -47,4 +48,4 @@ pub use self::debug::Debug;
#[doc(inline)] pub use self::content::Content; #[doc(inline)] pub use self::content::Content;
/// Type alias for the `Result` of a [`Responder::respond_to()`] call. /// 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 /// recognized. See [`ContentType::from_extension()`] for more information. If
/// you would like to stream a file with a different Content-Type than that /// you would like to stream a file with a different Content-Type than that
/// implied by its extension, use a [`File`] directly. /// implied by its extension, use a [`File`] directly.
#[crate::async_trait] impl<'r> Responder<'r, 'static> for NamedFile {
impl<'r> Responder<'r> for NamedFile { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { let mut response = self.1.respond_to(req)?;
let mut response = self.1.respond_to(req).await?;
if let Some(ext) = self.0.extension() { if let Some(ext) = self.0.extension() {
if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) { if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) {
response.set_header(ct); 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 /// 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 /// value used to create the `Responder` is an invalid URI, an error of
/// `Status::InternalServerError` is returned. /// `Status::InternalServerError` is returned.
#[crate::async_trait] impl<'r> Responder<'r, 'static> for Redirect {
impl<'r> Responder<'r> for Redirect { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
if let Some(uri) = self.1 { if let Some(uri) = self.1 {
Response::build() Response::build()
.status(self.0) .status(self.0)

View File

@ -1,11 +1,8 @@
use std::fs::File; use std::fs::File;
use std::io::Cursor; use std::io::Cursor;
use tokio::io::BufReader;
use futures::future::BoxFuture;
use crate::http::{Status, ContentType, StatusClass}; use crate::http::{Status, ContentType, StatusClass};
use crate::response::{self, Response, Body}; use crate::response::{self, Response};
use crate::request::Request; use crate::request::Request;
/// Trait implemented by types that generate responses for clients. /// 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 /// regardless of the incoming request. Thus, knowing the type is sufficient to
/// fully determine its functionality. /// fully determine its functionality.
/// ///
/// # Async Trait /// ## Lifetimes
/// ///
/// [`Responder`] is an _async_ trait. Implementations of `Responder` may be /// `Responder` has two lifetimes: `Responder<'r, 'o: 'r>`. The first lifetime,
/// decorated with an attribute of `#[rocket::async_trait]`, allowing the /// `'r`, refers to the reference to the `&'r Request`, while the second
/// `respond_to` method to be implemented as an `async fn`: /// 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
/// ```rust /// particular, this includes borrows from the `Request` itself (where `'o` would
/// use rocket::response::{self, Responder}; /// be `'r` as in `impl<'r> Responder<'r, 'r>`) as well as `'static` data (where
/// use rocket::request::Request; /// `'o` would be `'static` as in `impl<'r> Responder<'r, 'static>`).
/// # 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)
/// }
/// }
/// ```
/// ///
/// # Example /// # Example
/// ///
@ -196,12 +164,11 @@ use crate::request::Request;
/// use rocket::response::{self, Response, Responder}; /// use rocket::response::{self, Response, Responder};
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// ///
/// #[rocket::async_trait] /// impl<'r> Responder<'r, 'static> for Person {
/// impl<'r> Responder<'r> for Person { /// fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
/// async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
/// let person_string = format!("{}:{}", self.name, self.age); /// let person_string = format!("{}:{}", self.name, self.age);
/// Response::build() /// 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-Name", self.name)
/// .raw_header("X-Person-Age", self.age.to_string()) /// .raw_header("X-Person-Age", self.age.to_string())
/// .header(ContentType::new("application", "x-person")) /// .header(ContentType::new("application", "x-person"))
@ -213,8 +180,7 @@ use crate::request::Request;
/// # fn person() -> Person { Person { name: "a".to_string(), age: 20 } } /// # fn person() -> Person { Person { name: "a".to_string(), age: 20 } }
/// # fn main() { } /// # fn main() { }
/// ``` /// ```
#[crate::async_trait] pub trait Responder<'r, 'o: 'r> {
pub trait Responder<'r> {
/// Returns `Ok` if a `Response` could be generated successfully. Otherwise, /// Returns `Ok` if a `Response` could be generated successfully. Otherwise,
/// returns an `Err` with a failing `Status`. /// 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 /// 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 /// to generate a final error response, which is then written out to the
/// client. /// 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 /// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`. /// containing the string `self`. Always returns `Ok`.
#[crate::async_trait] impl<'r, 'o: 'r> Responder<'r, 'o> for &'o str {
impl<'r> Responder<'r> for &'r str { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Response::build() Response::build()
.header(ContentType::Plain) .header(ContentType::Plain)
.sized_body(Cursor::new(self)).await .sized_body(self.len(), Cursor::new(self))
.ok() .ok()
} }
} }
/// Returns a response with Content-Type `text/plain` and a fixed-size body /// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`. /// containing the string `self`. Always returns `Ok`.
#[crate::async_trait] impl<'r> Responder<'r, 'static> for String {
impl<'r> Responder<'r> for String { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Response::build() Response::build()
.header(ContentType::Plain) .header(ContentType::Plain)
.sized_body(Cursor::new(self)).await .sized_body(self.len(), Cursor::new(self))
.ok() .ok()
} }
} }
/// Returns a response with Content-Type `application/octet-stream` and a /// Returns a response with Content-Type `application/octet-stream` and a
/// fixed-size body containing the data in `self`. Always returns `Ok`. /// fixed-size body containing the data in `self`. Always returns `Ok`.
#[crate::async_trait] impl<'r, 'o: 'r> Responder<'r, 'o> for &'o [u8] {
impl<'r> Responder<'r> for &'r [u8] { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Response::build() Response::build()
.header(ContentType::Binary) .header(ContentType::Binary)
.sized_body(Cursor::new(self)).await .sized_body(self.len(), Cursor::new(self))
.ok() .ok()
} }
} }
/// Returns a response with Content-Type `application/octet-stream` and a /// Returns a response with Content-Type `application/octet-stream` and a
/// fixed-size body containing the data in `self`. Always returns `Ok`. /// fixed-size body containing the data in `self`. Always returns `Ok`.
#[crate::async_trait] impl<'r> Responder<'r, 'static> for Vec<u8> {
impl<'r> Responder<'r> for Vec<u8> { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Response::build() Response::build()
.header(ContentType::Binary) .header(ContentType::Binary)
.sized_body(Cursor::new(self)).await .sized_body(self.len(), Cursor::new(self))
.ok() .ok()
} }
} }
/// Returns a response with a sized body for the file. Always returns `Ok`. /// Returns a response with a sized body for the file. Always returns `Ok`.
#[crate::async_trait] impl<'r> Responder<'r, 'static> for File {
impl<'r> Responder<'r> for File { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { tokio::fs::File::from(self).respond_to(req)
tokio::fs::File::from(self).respond_to(req).await
} }
} }
/// Returns a response with a sized body for the file. Always returns `Ok`. /// Returns a response with a sized body for the file. Always returns `Ok`.
#[crate::async_trait] impl<'r> Responder<'r, 'static> for tokio::fs::File {
impl<'r> Responder<'r> for tokio::fs::File { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { Response::build().sized_body(None, self).ok()
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()
}
} }
} }
/// Returns an empty, default `Response`. Always returns `Ok`. /// Returns an empty, default `Response`. Always returns `Ok`.
#[crate::async_trait] impl<'r> Responder<'r, 'static> for () {
impl<'r> Responder<'r> for () { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Ok(Response::new()) Ok(Response::new())
} }
} }
/// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints /// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints
/// a warning message and returns an `Err` of `Status::NotFound`. /// a warning message and returns an `Err` of `Status::NotFound`.
impl<'r, R: Responder<'r>> Responder<'r> for Option<R> { impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Option<R> {
fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>> fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
where 'a: 'x, 'r: 'x, Self: 'x
{
match self { match self {
Some(r) => r.respond_to(req), Some(r) => r.respond_to(req),
None => { None => {
warn_!("Response was `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 // Responds with the wrapped `Responder` in `self`, whether it is `Ok` or
/// `Err`. /// `Err`.
impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result<R, E> { impl<'r, 'o: 'r, 't: 'o, 'e: 'o, T, E> Responder<'r, 'o> for Result<T, E>
fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>> where T: Responder<'r, 't>, E: Responder<'r, 'e>
where 'a: 'x, 'r: 'x, Self: 'x {
{ fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
match self { match self {
Ok(responder) => responder.respond_to(req), Ok(responder) => responder.respond_to(req),
Err(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 /// `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 /// status code emit an error message and forward to the `500` (internal server
/// error) catcher. /// error) catcher.
#[crate::async_trait] impl<'r> Responder<'r, 'static> for Status {
impl<'r> Responder<'r> for Status { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
match self.class() { match self.class() {
StatusClass::ClientError | StatusClass::ServerError => Err(self), StatusClass::ClientError | StatusClass::ServerError => Err(self),
StatusClass::Success if self.code < 206 => { StatusClass::Success if self.code < 206 => {

View File

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

View File

@ -97,9 +97,7 @@ impl<'r, R> Created<R> {
/// assert_eq!(etag, None); /// assert_eq!(etag, None);
/// # }); /// # });
/// ``` /// ```
pub fn body(mut self, responder: R) -> Self pub fn body(mut self, responder: R) -> Self {
where R: Responder<'r>
{
self.1 = Some(responder); self.1 = Some(responder);
self self
} }
@ -135,9 +133,7 @@ impl<'r, R> Created<R> {
/// assert_eq!(etag, Some(r#""13046220615156895040""#)); /// assert_eq!(etag, Some(r#""13046220615156895040""#));
/// # }); /// # });
/// ``` /// ```
pub fn tagged_body(mut self, responder: R) -> Self pub fn tagged_body(mut self, responder: R) -> Self where R: Hash {
where R: Responder<'r> + Hash
{
let mut hasher = &mut DefaultHasher::default(); let mut hasher = &mut DefaultHasher::default();
responder.hash(&mut hasher); responder.hash(&mut hasher);
let hash = hasher.finish(); 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 /// the response with the `Responder`, the `ETag` header is set conditionally if
/// a hashable `Responder` is provided via [`Created::tagged_body()`]. The `ETag` /// a hashable `Responder` is provided via [`Created::tagged_body()`]. The `ETag`
/// header is set to a hash value of the responder. /// header is set to a hash value of the responder.
#[crate::async_trait] impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Created<R> {
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Created<R> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
let mut response = Response::build(); let mut response = Response::build();
if let Some(responder) = self.1 { 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 { 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 /// Sets the status code of the response to 202 Accepted. If the responder is
/// `Some`, it is used to finalize the response. /// `Some`, it is used to finalize the response.
#[crate::async_trait] impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Accepted<R> {
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Accepted<R> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
let mut build = Response::build(); let mut build = Response::build();
if let Some(responder) = self.0 { if let Some(responder) = self.0 {
build.merge(responder.respond_to(req).await?); build.merge(responder.respond_to(req)?);
} }
build.status(Status::Accepted).ok() build.status(Status::Accepted).ok()
@ -237,10 +231,9 @@ impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Accepted<R> {
pub struct NoContent; pub struct NoContent;
/// Sets the status code of the response to 204 No Content. /// Sets the status code of the response to 204 No Content.
impl<'r> Responder<'r> for NoContent { impl<'r> Responder<'r, 'static> for NoContent {
fn respond_to(self, _: &Request<'_>) -> Result<Response<'r>, Status> { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
let mut build = Response::build(); Response::build().status(Status::NoContent).ok()
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 /// Sets the status code of the response to 400 Bad Request. If the responder is
/// `Some`, it is used to finalize the response. /// `Some`, it is used to finalize the response.
#[crate::async_trait] impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for BadRequest<R> {
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for BadRequest<R> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
let mut build = Response::build(); let mut build = Response::build();
if let Some(responder) = self.0 { if let Some(responder) = self.0 {
build.merge(responder.respond_to(req).await?); build.merge(responder.respond_to(req)?);
} }
build.status(Status::BadRequest).ok() 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 /// Sets the status code of the response to 401 Unauthorized. If the responder is
/// `Some`, it is used to finalize the response. /// `Some`, it is used to finalize the response.
impl<'r, R: Responder<'r>> Responder<'r> for Unauthorized<R> { impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Unauthorized<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
let mut build = Response::build(); let mut build = Response::build();
if let Some(responder) = self.0 { if let Some(responder) = self.0 {
build.merge(responder.respond_to(req)?); 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 /// Sets the status code of the response to 403 Forbidden. If the responder is
/// `Some`, it is used to finalize the response. /// `Some`, it is used to finalize the response.
impl<'r, R: Responder<'r>> Responder<'r> for Forbidden<R> { impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Forbidden<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
let mut build = Response::build(); let mut build = Response::build();
if let Some(responder) = self.0 { if let Some(responder) = self.0 {
build.merge(responder.respond_to(req)?); 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); pub struct NotFound<R>(pub R);
/// Sets the status code of the response to 404 Not Found. /// Sets the status code of the response to 404 Not Found.
#[crate::async_trait] impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for NotFound<R> {
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for NotFound<R> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { Response::build_from(self.0.respond_to(req)?)
Response::build_from(self.0.respond_to(req).await?)
.status(Status::NotFound) .status(Status::NotFound)
.ok() .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 /// Sets the status code of the response to 409 Conflict. If the responder is
/// `Some`, it is used to finalize the response. /// `Some`, it is used to finalize the response.
impl<'r, R: Responder<'r>> Responder<'r> for Conflict<R> { impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Conflict<R> {
fn respond_to(self, req: &Request<'_>) -> Result<Response<'r>, Status> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
let mut build = Response::build(); let mut build = Response::build();
if let Some(responder) = self.0 { if let Some(responder) = self.0 {
build.merge(responder.respond_to(req)?); 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 /// Sets the status code of the response and then delegates the remainder of the
/// response to the wrapped responder. /// response to the wrapped responder.
#[crate::async_trait] impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Custom<R> {
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Custom<R> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { Response::build_from(self.1.respond_to(req)?)
Response::build_from(self.1.respond_to(req).await?)
.status(self.0) .status(self.0)
.ok() .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 /// 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 /// 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. /// 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> { impl<T: AsyncRead> Stream<T> {
/// Create a new stream from the given `reader` and sets the chunk size for /// 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)] /// # #[allow(unused_variables)]
/// let response = Stream::chunked(tokio::io::stdin(), 10); /// 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) 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 /// 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 /// response is abandoned, and the response ends abruptly. An error is printed
/// to the console with an indication of what went wrong. /// to the console with an indication of what went wrong.
#[crate::async_trait] impl<'r, 'o: 'r, T: AsyncRead + Send + 'o> Responder<'r, 'o> for Stream<T> {
impl<'r, T: AsyncRead + Send + 'r> Responder<'r> for Stream<T> { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Response::build().chunked_body(self.0, self.1).ok() Response::build().chunked_body(self.0, self.1).ok()
} }
} }

View File

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

View File

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

View File

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

View File

@ -48,7 +48,7 @@ impl Fairing for Counter {
let body = format!("Get: {}\nPost: {}", get_count, post_count); let body = format!("Get: {}\nPost: {}", get_count, post_count);
res.set_status(Status::Ok); res.set_status(Status::Ok);
res.set_header(ContentType::Plain); 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 { Box::pin(async move {
if req.uri().path() == "/" { if req.uri().path() == "/" {
println!(" => Rewriting response body."); println!(" => Rewriting response body.");
res.set_sized_body(Cursor::new("Hello, fairings!")).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::{Request, Handler, Route, Data, Catcher, try_outcome};
use rocket::http::{Status, RawStr}; use rocket::http::{Status, RawStr};
use rocket::response::{self, Responder, status::Custom}; use rocket::response::{Responder, status::Custom};
use rocket::handler::{Outcome, HandlerFuture}; use rocket::handler::{Outcome, HandlerFuture, ErrorHandlerFuture};
use rocket::outcome::IntoOutcome; use rocket::outcome::IntoOutcome;
use rocket::http::Method::*; use rocket::http::Method::*;
use rocket::futures::future::BoxFuture;
use rocket::tokio::fs::File; use rocket::tokio::fs::File;
fn forward<'r>(_req: &'r Request, data: Data) -> HandlerFuture<'r> { 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> { 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> { fn name<'a>(req: &'a Request, _: Data) -> HandlerFuture<'a> {
Box::pin(async move { let param = req.get_param::<&'a RawStr>(0)
let param = req.get_param::<&'a RawStr>(0) .and_then(|res| res.ok())
.and_then(|res| res.ok()) .unwrap_or("unnamed".into());
.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> { 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 { 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); 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; let file = File::create(env::temp_dir().join("upload.txt")).await;
if let Ok(file) = file { if let Ok(file) = file {
if let Ok(n) = data.stream_to(file).await { 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."); 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> { 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())); 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)] #[derive(Clone)]
@ -87,13 +85,13 @@ impl CustomHandler {
impl Handler for CustomHandler { impl Handler for CustomHandler {
fn handle<'r>(&self, req: &'r Request, data: Data) -> HandlerFuture<'r> { fn handle<'r>(&self, req: &'r Request, data: Data) -> HandlerFuture<'r> {
let self_data = self.data; let self_data = self.data;
Box::pin(async move { let id_outcome = req.get_param::<&RawStr>(0)
let id_outcome = req.get_param::<&RawStr>(0) .and_then(|res| res.ok())
.and_then(|res| res.ok()) .or_forward(data);
.or_forward(data);
Box::pin(async move {
let id = try_outcome!(id_outcome); let id = try_outcome!(id_outcome);
Outcome::from(req, format!("{} - {}", self_data, id)).await Outcome::from(req, format!("{} - {}", self_data, id))
}) })
} }
} }