Use 'async_trait' for 'Responder'.

Also:

  * Remove 'response::ResultFuture'.
  * Re-export 'tokio' and 'futures' from the crate root.
  * Make 'ResponseBuilder::sized_body()' and 'async fn'.
  * Remove the 'Future' implementation for 'ResponseBuilder'.
  * Add 'ResponseBuilder::finalize()' for finalizing the builder.
This commit is contained in:
Sergio Benitez 2020-02-03 00:30:22 -08:00
parent 58f81d392e
commit c0c6c79a7f
24 changed files with 342 additions and 405 deletions

View File

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

View File

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

View File

@ -383,9 +383,9 @@ impl Template {
/// Returns a response with the Content-Type derived from the template's
/// extension and a fixed-size body containing the rendered template. If
/// rendering fails, an `Err` of `Status::InternalServerError` is returned.
#[rocket::async_trait]
impl<'r> Responder<'r> for Template {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
let (render, content_type) = {
let ctxt = req.guard::<State<'_, ContextManager>>().await.succeeded().ok_or_else(|| {
error_!("Uninitialized template context: missing fairing.");
@ -398,6 +398,5 @@ impl<'r> Responder<'r> for Template {
};
Content(content_type, render).respond_to(req).await
})
}
}

View File

@ -103,7 +103,6 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
.status(#status)
.merge(__response)
.ok()
.await
})
}

View File

@ -30,10 +30,12 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
false => Ok(())
})
.function(|_, inner| quote! {
fn respond_to(
fn respond_to<'__i, '__x>(
self,
__req: &'__r ::rocket::Request
) -> ::rocket::response::ResultFuture<'__r> {
__req: &'__r ::rocket::request::Request<'__i>
) -> ::rocket::futures::future::BoxFuture<'__x, ::rocket::response::Result<'__r>>
where '__i: '__x, '__r: '__x, Self: '__x
{
#inner
}
})

View File

@ -28,8 +28,7 @@ async fn responder_foo() {
let req = local_req.inner();
let mut response = Foo::First("hello".into())
.respond_to(req)
.await
.respond_to(req).await
.expect("response okay");
assert_eq!(response.status(), Status::Ok);
@ -37,8 +36,7 @@ async fn responder_foo() {
assert_eq!(response.body_string().await, Some("hello".into()));
let mut response = Foo::Second("just a test".into())
.respond_to(req)
.await
.respond_to(req).await
.expect("response okay");
assert_eq!(response.status(), Status::InternalServerError);
@ -46,8 +44,7 @@ async fn responder_foo() {
assert_eq!(response.body_string().await, Some("just a test".into()));
let mut response = Foo::Third { responder: "well, hi", ct: ContentType::JSON }
.respond_to(req)
.await
.respond_to(req).await
.expect("response okay");
assert_eq!(response.status(), Status::NotFound);
@ -55,8 +52,7 @@ async fn responder_foo() {
assert_eq!(response.body_string().await, Some("well, hi".into()));
let mut response = Foo::Fourth { string: "goodbye", ct: ContentType::JSON }
.respond_to(req)
.await
.respond_to(req).await
.expect("response okay");
assert_eq!(response.status(), Status::raw(105));
@ -106,8 +102,7 @@ async fn responder_baz() {
let req = local_req.inner();
let mut response = Baz { responder: "just a custom" }
.respond_to(req)
.await
.respond_to(req).await
.expect("response okay");
assert_eq!(response.status(), Status::Ok);

View File

@ -158,10 +158,7 @@ impl PartialEq for AcceptParams {
/// use rocket::http::Accept;
/// use rocket::response::Response;
///
/// # #[allow(unused_variables)]
/// # rocket::async_test(async {
/// let response = Response::build().header(Accept::JSON).await;
/// # })
/// let response = Response::build().header(Accept::JSON).finalize();
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct Accept(pub(crate) AcceptParams);

View File

@ -38,10 +38,7 @@ use crate::ext::IntoCollection;
/// use rocket::http::ContentType;
/// use rocket::response::Response;
///
/// # #[allow(unused_variables)]
/// # rocket::async_test(async {
/// let response = Response::build().header(ContentType::HTML).await;
/// # })
/// let response = Response::build().header(ContentType::HTML).finalize();
/// ```
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct ContentType(pub MediaType);

View File

@ -28,6 +28,7 @@ ctrl_c_shutdown = ["tokio/signal"]
rocket_codegen = { version = "0.5.0-dev", path = "../codegen" }
rocket_http = { version = "0.5.0-dev", path = "../http" }
futures-util = "0.3.0"
futures = "0.3.0"
tokio = { version = "0.2.9", features = ["fs", "io-std", "io-util", "rt-threaded", "sync"] }
yansi = "0.5"
log = { version = "0.4", features = ["std"] }

View File

@ -99,6 +99,9 @@ pub use async_trait::*;
#[macro_use] extern crate log;
#[macro_use] extern crate pear;
pub use futures;
pub use tokio;
#[doc(hidden)] #[macro_use] pub mod logger;
#[macro_use] pub mod outcome;
pub mod local;

View File

@ -23,7 +23,7 @@
//! ```
use crate::request::Request;
use crate::response::{Response, Responder, ResultFuture};
use crate::response::{self, Response, Responder};
use crate::http::ContentType;
/// Sets the Content-Type of a `Responder` to a chosen value.
@ -46,21 +46,21 @@ pub struct Content<R>(pub ContentType, pub R);
/// Overrides the Content-Type of the response to the wrapped `ContentType` then
/// delegates the remainder of the response to the wrapped responder.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Content<R> {
#[inline(always)]
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
Response::build()
.merge(self.1.respond_to(req).await?)
.header(self.0)
.ok()
.await
})
}
}
macro_rules! ctrs {
($($name:ident: $ct:ident, $name_str:expr, $ct_str:expr),+) => {
use futures_util::future::BoxFuture;
$(
#[doc="Override the `Content-Type` of the response to <b>"]
#[doc=$name_str]
@ -75,7 +75,9 @@ macro_rules! ctrs {
/// Sets the Content-Type of the response then delegates the
/// remainder of the response to the wrapped responder.
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for $name<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>>
where 'a: 'x, 'r: 'x, Self: 'x
{
Content(ContentType::$ct, self.0).respond_to(req)
}
}

View File

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

View File

@ -3,7 +3,7 @@ use std::convert::AsRef;
use time::Duration;
use crate::outcome::IntoOutcome;
use crate::response::{Responder, ResultFuture};
use crate::response::{self, Responder};
use crate::request::{self, Request, FromRequest};
use crate::http::{Status, Cookie};
use std::sync::atomic::{AtomicBool, Ordering};
@ -198,11 +198,12 @@ impl<'r, R: Responder<'r>> Flash<R> {
/// response. In other words, simply sets a cookie and delegates the rest of the
/// response handling to the wrapped responder. As a result, the `Outcome` of
/// the response is the `Outcome` of the wrapped `Responder`.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Flash<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
trace_!("Flash: setting message: {}:{}", self.name, self.message);
req.cookies().add(self.cookie());
self.inner.respond_to(req)
self.inner.respond_to(req).await
}
}

View File

@ -46,8 +46,5 @@ pub use self::stream::Stream;
pub use self::debug::Debug;
#[doc(inline)] pub use self::content::Content;
/// Type alias for the `Result` of a `Responder::respond` 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>;
/// Type alias for the `Future` returned by a `Responder::respond` call.
pub type ResultFuture<'r> = futures_util::future::BoxFuture<'r, Result<'r>>;

View File

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

View File

@ -1,7 +1,7 @@
use std::convert::TryInto;
use crate::request::Request;
use crate::response::{Response, Responder, ResultFuture};
use crate::response::{self, Response, Responder};
use crate::http::uri::Uri;
use crate::http::Status;
@ -147,19 +147,17 @@ impl Redirect {
/// the `Location` header field. The body of the response is empty. If the URI
/// value used to create the `Responder` is an invalid URI, an error of
/// `Status::InternalServerError` is returned.
#[crate::async_trait]
impl<'r> Responder<'r> for Redirect {
fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
if let Some(uri) = self.1 {
Response::build()
.status(self.0)
.raw_header("Location", uri.to_string())
.ok()
.await
} else {
error!("Invalid URI used for redirect.");
Err(Status::InternalServerError)
}
})
}
}

View File

@ -1,8 +1,8 @@
use std::fs::File;
use std::io::Cursor;
use futures_util::future::ready;
use tokio::io::BufReader;
use futures_util::future::BoxFuture;
use crate::http::{Status, ContentType, StatusClass};
use crate::response::{self, Response, Body};
@ -113,13 +113,50 @@ use crate::request::Request;
/// regardless of the incoming request. Thus, knowing the type is sufficient to
/// fully determine its functionality.
///
/// # Async Trait
///
/// [`Responder`] is an _async_ trait. Implementations of `Responder` may be
/// decorated with an attribute of `#[rocket::async_trait]`, allowing the
/// `respond_to` method to be implemented as an `async fn`:
///
/// ```rust
/// use rocket::response::{self, Responder};
/// use rocket::request::Request;
/// # struct MyType;
///
/// #[rocket::async_trait]
/// impl<'r> Responder<'r> for MyType {
/// async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
/// /* .. */
/// # unimplemented!()
/// }
/// }
/// ```
///
/// In certain cases, including when implementing a _wrapping_ responder, it may
/// be desirable to perform work before entering an `async` section, or to
/// return a future directly. In this case, `Responder` can be implemented as:
///
/// ```rust
/// use rocket::response::{self, Responder};
/// use rocket::futures::future::BoxFuture;
/// use rocket::request::Request;
/// # struct MyType<R> { inner: R };
///
/// impl<'r, R: Responder<'r>> Responder<'r> for MyType<R> {
/// fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>>
/// where 'a: 'x, 'r: 'x, Self: 'x
/// {
/// self.inner.respond_to(req)
/// }
/// }
/// ```
///
/// # Example
///
/// Say that you have a custom type, `Person`:
///
/// ```rust
///
/// # #[allow(dead_code)]
/// struct Person {
/// name: String,
/// age: u16
@ -129,11 +166,16 @@ use crate::request::Request;
/// You'd like to use `Person` as a `Responder` so that you can return a
/// `Person` directly from a handler:
///
/// ```rust,ignore
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # type Person = String;
/// #[get("/person/<id>")]
/// fn person(id: usize) -> Option<Person> {
/// # /*
/// Person::from_id(id)
/// # */ None
/// }
/// # fn main() {}
/// ```
///
/// You want the `Person` responder to set two header fields: `X-Person-Name`
@ -154,17 +196,16 @@ use crate::request::Request;
/// use rocket::response::{self, Response, Responder};
/// use rocket::http::ContentType;
///
/// #[rocket::async_trait]
/// impl<'r> Responder<'r> for Person {
/// fn respond_to(self, _: &'r Request) -> response::ResultFuture<'r> {
/// Box::pin(async move {
/// async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
/// let person_string = format!("{}:{}", self.name, self.age);
/// Response::build()
/// .sized_body(Cursor::new(format!("{}:{}", self.name, self.age)))
/// .sized_body(Cursor::new(person_string)).await
/// .raw_header("X-Person-Name", self.name)
/// .raw_header("X-Person-Age", self.age.to_string())
/// .header(ContentType::new("application", "x-person"))
/// .ok()
/// .await
/// })
/// }
/// }
/// #
@ -172,6 +213,7 @@ use crate::request::Request;
/// # fn person() -> Person { Person { name: "a".to_string(), age: 20 } }
/// # fn main() { }
/// ```
#[crate::async_trait]
pub trait Responder<'r> {
/// Returns `Ok` if a `Response` could be generated successfully. Otherwise,
/// returns an `Err` with a failing `Status`.
@ -184,113 +226,108 @@ pub trait Responder<'r> {
/// returned, the error catcher for the given status is retrieved and called
/// to generate a final error response, which is then written out to the
/// client.
fn respond_to(self, request: &'r Request<'_>) -> response::ResultFuture<'r>;
async fn respond_to(self, request: &'r Request<'_>) -> response::Result<'r>;
}
/// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for &'r str {
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Response::build()
.header(ContentType::Plain)
.sized_body(Cursor::new(self))
.sized_body(Cursor::new(self)).await
.ok()
.await
})
}
}
/// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for String {
fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Response::build()
.header(ContentType::Plain)
.sized_body(Cursor::new(self))
.sized_body(Cursor::new(self)).await
.ok()
.await
})
}
}
/// Returns a response with Content-Type `application/octet-stream` and a
/// fixed-size body containing the data in `self`. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for &'r [u8] {
fn respond_to(self, _: &Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Response::build()
.header(ContentType::Binary)
.sized_body(Cursor::new(self))
.sized_body(Cursor::new(self)).await
.ok()
.await
})
}
}
/// Returns a response with Content-Type `application/octet-stream` and a
/// fixed-size body containing the data in `self`. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for Vec<u8> {
fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Response::build()
.header(ContentType::Binary)
.sized_body(Cursor::new(self))
.sized_body(Cursor::new(self)).await
.ok()
.await
})
}
}
/// Returns a response with a sized body for the file. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for File {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
tokio::fs::File::from(self).respond_to(req)
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
tokio::fs::File::from(self).respond_to(req).await
}
}
/// Returns a response with a sized body for the file. Always returns `Ok`.
#[crate::async_trait]
impl<'r> Responder<'r> for tokio::fs::File {
fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
let metadata = self.metadata().await;
let stream = BufReader::new(self);
match metadata {
Ok(md) => Response::build().raw_body(Body::Sized(stream, md.len())).ok().await,
Err(_) => Response::build().streamed_body(stream).ok().await
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`.
#[crate::async_trait]
impl<'r> Responder<'r> for () {
fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Ok(Response::new())
})
}
}
/// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints
/// a warning message and returns an `Err` of `Status::NotFound`.
impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>>
where 'a: 'x, 'r: 'x, Self: 'x
{
match self {
Some(r) => r.respond_to(req),
None => {
warn_!("Response was `None`.");
Box::pin(ready(Err(Status::NotFound)))
Box::pin(async { Err(Status::NotFound) })
},
}
}
}
/// 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`.
impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result<R, E> {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
fn respond_to<'a, 'x>(self, req: &'r Request<'a>) -> BoxFuture<'x, response::Result<'r>>
where 'a: 'x, 'r: 'x, Self: 'x
{
match self {
Ok(responder) => responder.respond_to(req),
Err(responder) => responder.respond_to(req),
@ -312,16 +349,16 @@ impl<'r, R: Responder<'r>, E: Responder<'r>> Responder<'r> for Result<R, E> {
/// `100` responds with any empty body and the given status code, and all other
/// status code emit an error message and forward to the `500` (internal server
/// error) catcher.
#[crate::async_trait]
impl<'r> Responder<'r> for Status {
fn respond_to(self, _: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
match self.class() {
StatusClass::ClientError | StatusClass::ServerError => Err(self),
StatusClass::Success if self.code < 206 => {
Response::build().status(self).ok().await
Response::build().status(self).ok()
}
StatusClass::Informational if self.code == 100 => {
Response::build().status(self).ok().await
Response::build().status(self).ok()
}
_ => {
error_!("Invalid status used as responder: {}.", self);
@ -329,6 +366,5 @@ impl<'r> Responder<'r> for Status {
Err(Status::InternalServerError)
}
}
})
}
}

View File

@ -1,12 +1,10 @@
use std::{io, fmt, str};
use std::borrow::Cow;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt};
use crate::response::{Responder, ResultFuture};
use crate::response::{self, Responder};
use crate::http::{Header, HeaderMap, Status, ContentType, Cookie};
/// The default size, in bytes, of a chunk for streamed responses.
@ -100,12 +98,6 @@ impl<T> fmt::Debug for Body<T> {
}
}
/// Internal workaround for `Box<dyn AsyncRead + AsyncSeek>` not being allowed.
///
/// https://github.com/rust-lang/rfcs/issues/2035
trait AsyncReadAsyncSeek: AsyncRead + AsyncSeek + Unpin + Send {}
impl<T: AsyncRead + AsyncSeek + Unpin + Send> AsyncReadAsyncSeek for T {}
/// Type for easily building `Response`s.
///
/// Building a [`Response`] can be a low-level ordeal; this structure presents a
@ -119,9 +111,8 @@ impl<T: AsyncRead + AsyncSeek + Unpin + Send> AsyncReadAsyncSeek for T {}
/// with field(s) modified in the `Responder` being built. These method calls
/// can be chained: `build.a().b()`.
///
/// To finish building and retrieve the built `Response`, `.await` the builder.
/// The [`ok()` method](#method.ok) is also provided as a convenience
/// for `Responder` implementations.
/// To finish building and retrieve the built `Response`, use the
/// [`finalize()`](#method.finalize) or [`ok()`](#method.ok) methods.
///
/// ## Headers
///
@ -157,23 +148,18 @@ impl<T: AsyncRead + AsyncSeek + Unpin + Send> AsyncReadAsyncSeek for T {}
/// use rocket::http::{Status, ContentType};
///
/// # rocket::async_test(async {
///
/// # #[allow(unused_variables)]
/// let response = Response::build()
/// .status(Status::ImATeapot)
/// .header(ContentType::Plain)
/// .raw_header("X-Teapot-Make", "Rocket")
/// .raw_header("X-Teapot-Model", "Utopia")
/// .raw_header_adjoin("X-Teapot-Model", "Series 1")
/// .sized_body(Cursor::new("Brewing the best coffee!"))
/// .await;
///
/// # })
/// .sized_body(Cursor::new("Brewing the best coffee!")).await
/// .finalize();
/// # });
/// ```
pub struct ResponseBuilder<'r> {
response: Response<'r>,
pending_sized_body: Option<Box<dyn AsyncReadAsyncSeek + 'r>>,
fut: Option<Pin<Box<dyn Future<Output=Response<'r>> + Send + 'r>>>,
}
impl<'r> ResponseBuilder<'r> {
@ -192,8 +178,6 @@ impl<'r> ResponseBuilder<'r> {
pub fn new(base: Response<'r>) -> ResponseBuilder<'r> {
ResponseBuilder {
response: base,
pending_sized_body: None,
fut: None,
}
}
@ -205,14 +189,9 @@ impl<'r> ResponseBuilder<'r> {
/// use rocket::Response;
/// use rocket::http::Status;
///
/// # rocket::async_test(async {
///
/// # #[allow(unused_variables)]
/// let response = Response::build()
/// .status(Status::NotFound)
/// .await;
///
/// # })
/// .finalize();
/// ```
#[inline(always)]
pub fn status(&mut self, status: Status) -> &mut ResponseBuilder<'r> {
@ -227,14 +206,13 @@ impl<'r> ResponseBuilder<'r> {
///
/// ```rust
/// use rocket::Response;
/// use rocket::http::Status;
///
/// # rocket::async_test(async {
///
/// # #[allow(unused_variables)]
/// let response = Response::build()
/// .raw_status(699, "Alien Encounter")
/// .await;
/// # })
/// .finalize();
///
/// assert_eq!(response.status(), Status::new(699, "Alien Encounter"));
/// ```
#[inline(always)]
pub fn raw_status(&mut self, code: u16, reason: &'static str) -> &mut ResponseBuilder<'r> {
@ -257,16 +235,12 @@ impl<'r> ResponseBuilder<'r> {
/// use rocket::Response;
/// use rocket::http::ContentType;
///
/// # rocket::async_test(async {
///
/// let response = Response::build()
/// .header(ContentType::JSON)
/// .header(ContentType::HTML)
/// .await;
/// .finalize();
///
/// assert_eq!(response.headers().get("Content-Type").count(), 1);
///
/// # })
/// ```
#[inline(always)]
pub fn header<'h: 'r, H>(&mut self, header: H) -> &mut ResponseBuilder<'r>
@ -292,16 +266,12 @@ impl<'r> ResponseBuilder<'r> {
/// use rocket::http::Header;
/// use rocket::http::hyper::header::ACCEPT;
///
/// # rocket::async_test(async {
///
/// let response = Response::build()
/// .header_adjoin(Header::new(ACCEPT.as_str(), "application/json"))
/// .header_adjoin(Header::new(ACCEPT.as_str(), "text/plain"))
/// .await;
/// .finalize();
///
/// assert_eq!(response.headers().get("Accept").count(), 2);
///
/// # })
/// ```
#[inline(always)]
pub fn header_adjoin<'h: 'r, H>(&mut self, header: H) -> &mut ResponseBuilder<'r>
@ -321,16 +291,12 @@ impl<'r> ResponseBuilder<'r> {
/// ```rust
/// use rocket::Response;
///
/// # rocket::async_test(async {
///
/// let response = Response::build()
/// .raw_header("X-Custom", "first")
/// .raw_header("X-Custom", "second")
/// .await;
/// .finalize();
///
/// assert_eq!(response.headers().get("X-Custom").count(), 1);
///
/// # })
/// ```
#[inline(always)]
pub fn raw_header<'a, 'b, N, V>(&mut self, name: N, value: V) -> &mut ResponseBuilder<'r>
@ -351,16 +317,12 @@ impl<'r> ResponseBuilder<'r> {
/// ```rust
/// use rocket::Response;
///
/// # rocket::async_test(async {
///
/// let response = Response::build()
/// .raw_header_adjoin("X-Custom", "first")
/// .raw_header_adjoin("X-Custom", "second")
/// .await;
/// .finalize();
///
/// assert_eq!(response.headers().get("X-Custom").count(), 2);
///
/// # })
/// ```
#[inline(always)]
pub fn raw_header_adjoin<'a, 'b, N, V>(&mut self, name: N, value: V) -> &mut ResponseBuilder<'r>
@ -375,24 +337,19 @@ impl<'r> ResponseBuilder<'r> {
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::Response;
/// use tokio::fs::File;
/// # use std::io;
///
/// # #[allow(dead_code)]
/// # async fn test() -> io::Result<()> {
/// # #[allow(unused_variables)]
/// # rocket::async_test(async {
/// let response = Response::build()
/// .sized_body(File::open("body.txt").await?)
/// .await;
/// # Ok(())
/// # }
/// .sized_body(Cursor::new("Hello, world!")).await
/// .finalize();
/// # })
/// ```
#[inline(always)]
pub fn sized_body<B>(&mut self, body: B) -> &mut ResponseBuilder<'r>
pub async fn sized_body<B>(&mut self, body: B) -> &mut ResponseBuilder<'r>
where B: AsyncRead + AsyncSeek + Send + Unpin + 'r
{
self.pending_sized_body = Some(Box::new(body));
self.response.set_sized_body(body).await;
self
}
@ -401,25 +358,18 @@ impl<'r> ResponseBuilder<'r> {
/// # Example
///
/// ```rust
/// use std::io::Cursor;
/// use rocket::Response;
/// use tokio::fs::File;
/// # use std::io;
///
/// # #[allow(dead_code)]
/// # async fn test() -> io::Result<()> {
/// # #[allow(unused_variables)]
/// let response = Response::build()
/// .streamed_body(File::open("body.txt").await?)
/// .await;
/// # Ok(())
/// # }
/// .streamed_body(Cursor::new("Hello, world!"))
/// .finalize();
/// ```
#[inline(always)]
pub fn streamed_body<B>(&mut self, body: B) -> &mut ResponseBuilder<'r>
where B: AsyncRead + Send + 'r
{
self.response.set_streamed_body(body);
self.pending_sized_body = None;
self
}
@ -433,21 +383,18 @@ impl<'r> ResponseBuilder<'r> {
/// use tokio::fs::File;
/// # use std::io;
///
/// # #[allow(dead_code)]
/// # async fn test() -> io::Result<()> {
/// # #[allow(unused_variables)]
/// # async fn test<'r>() -> io::Result<Response<'r>> {
/// let response = Response::build()
/// .chunked_body(File::open("body.txt").await?, 8096)
/// .await;
/// # Ok(())
/// .ok();
/// # response
/// # }
/// ```
#[inline(always)]
pub fn chunked_body<B: AsyncRead + Send + 'r>(&mut self, body: B, chunk_size: u64)
-> &mut ResponseBuilder<'r>
pub fn chunked_body<B>(&mut self, body: B, chunk_size: u64) -> &mut ResponseBuilder<'r>
where B: AsyncRead + Send + 'r
{
self.response.set_chunked_body(body, chunk_size);
self.pending_sized_body = None;
self
}
@ -462,20 +409,16 @@ impl<'r> ResponseBuilder<'r> {
/// use rocket::response::{Response, Body};
///
/// # rocket::async_test(async {
///
/// # #[allow(unused_variables)]
/// let response = Response::build()
/// .raw_body(Body::Sized(Cursor::new("Hello!"), 6))
/// .await;
///
/// .finalize();
/// # })
/// ```
#[inline(always)]
pub fn raw_body<T: AsyncRead + Send + Unpin + 'r>(&mut self, body: Body<T>)
-> &mut ResponseBuilder<'r>
pub fn raw_body<T>(&mut self, body: Body<T>) -> &mut ResponseBuilder<'r>
where T: AsyncRead + Send + Unpin + 'r
{
self.response.set_raw_body(body);
self.pending_sized_body = None;
self
}
@ -491,40 +434,29 @@ impl<'r> ResponseBuilder<'r> {
/// use rocket::Response;
/// use rocket::http::{Status, ContentType};
///
/// # rocket::async_test(async {
///
/// let base = Response::build()
/// .status(Status::NotFound)
/// .header(ContentType::HTML)
/// .raw_header("X-Custom", "value 1")
/// .await;
/// .finalize();
///
/// let response = Response::build()
/// .status(Status::ImATeapot)
/// .raw_header("X-Custom", "value 2")
/// .raw_header_adjoin("X-Custom", "value 3")
/// .merge(base)
/// .await;
/// .finalize();
///
/// assert_eq!(response.status(), Status::NotFound);
///
/// # {
/// let ctype: Vec<_> = response.headers().get("Content-Type").collect();
/// assert_eq!(ctype, vec![ContentType::HTML.to_string()]);
/// # }
///
/// # {
/// let custom_values: Vec<_> = response.headers().get("X-Custom").collect();
/// assert_eq!(custom_values, vec!["value 1"]);
/// # }
///
/// # });
/// ```
#[inline(always)]
pub fn merge(&mut self, mut other: Response<'r>) -> &mut ResponseBuilder<'r> {
if other.body().is_some() {
self.pending_sized_body = None;
}
pub fn merge(&mut self, other: Response<'r>) -> &mut ResponseBuilder<'r> {
self.response.merge(other);
self
}
@ -542,34 +474,26 @@ impl<'r> ResponseBuilder<'r> {
/// use rocket::Response;
/// use rocket::http::{Status, ContentType};
///
/// # rocket::async_test(async {
///
/// let other = Response::build()
/// .status(Status::NotFound)
/// .header(ContentType::HTML)
/// .raw_header("X-Custom", "value 1")
/// .await;
/// .finalize();
///
/// let response = Response::build()
/// .status(Status::ImATeapot)
/// .raw_header("X-Custom", "value 2")
/// .raw_header_adjoin("X-Custom", "value 3")
/// .join(other)
/// .await;
/// .finalize();
///
/// assert_eq!(response.status(), Status::ImATeapot);
///
/// # {
/// let ctype: Vec<_> = response.headers().get("Content-Type").collect();
/// assert_eq!(ctype, vec![ContentType::HTML.to_string()]);
/// # }
///
/// # {
/// let custom_values: Vec<_> = response.headers().get("X-Custom").collect();
/// assert_eq!(custom_values, vec!["value 2", "value 3", "value 1"]);
/// # }
///
/// # })
/// ```
#[inline(always)]
pub fn join(&mut self, other: Response<'r>) -> &mut ResponseBuilder<'r> {
@ -577,48 +501,47 @@ impl<'r> ResponseBuilder<'r> {
self
}
/// Retrieve the built `Response` wrapped in `Ok`.
/// Return the `Response` structure that was being built by this builder.
/// After calling this method, `self` is cleared and must be rebuilt as if
/// from `new()`.
///
/// # Example
///
/// ```rust
/// use std::io::Cursor;
///
/// use rocket::Response;
/// use rocket::http::Status;
///
/// # rocket::async_test(async {
/// let response = Response::build()
/// .status(Status::ImATeapot)
/// .sized_body(Cursor::new("Brewing the best coffee!")).await
/// .raw_header("X-Custom", "value 2")
/// .finalize();
/// # })
/// ```
pub fn finalize(&mut self) -> Response<'r> {
std::mem::replace(&mut self.response, Response::new())
}
/// Retrieve the built `Response` wrapped in `Ok`. After calling this
/// method, `self` is cleared and must be rebuilt as if from `new()`.
///
/// # Example
///
/// ```rust
/// use rocket::Response;
///
/// # rocket::async_test(async {
///
/// let response: Result<Response, ()> = Response::build()
/// // build the response
/// .ok()
/// .await;
/// .ok();
///
/// assert!(response.is_ok());
///
/// # })
/// ```
#[inline(always)]
pub async fn ok<E>(&mut self) -> Result<Response<'r>, E> {
Ok(self.await)
}
}
impl<'r> Future for ResponseBuilder<'r> {
type Output = Response<'r>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
if this.fut.is_none() {
let mut response = std::mem::replace(&mut this.response, Response::new());
let pending_sized_body = this.pending_sized_body.take();
this.fut = Some(Box::pin(async {
if let Some(sb) = pending_sized_body {
// TODO: Avoid double boxing (Pin<Box<Take<Pin<Box<dyn AsyncReadAsyncSeek>>>>>)
response.set_sized_body(sb).await;
}
response
}));
}
this.fut.as_mut().expect("this.fut.is_none() checked and assigned Some").as_mut().poll(cx)
pub fn ok<E>(&mut self) -> Result<Response<'r>, E> {
Ok(self.finalize())
}
}
@ -1139,7 +1062,8 @@ impl<'r> Response<'r> {
/// ```
#[inline(always)]
pub fn set_chunked_body<B>(&mut self, body: B, chunk_size: u64)
where B: AsyncRead + Send + 'r {
where B: AsyncRead + Send + 'r
{
self.body = Some(Body::Chunked(Box::pin(body), chunk_size));
}
@ -1181,34 +1105,26 @@ impl<'r> Response<'r> {
/// use rocket::Response;
/// use rocket::http::{Status, ContentType};
///
/// # rocket::async_test(async {
///
/// let base = Response::build()
/// .status(Status::NotFound)
/// .header(ContentType::HTML)
/// .raw_header("X-Custom", "value 1")
/// .await;
/// .finalize();
///
/// let response = Response::build()
/// .status(Status::ImATeapot)
/// .raw_header("X-Custom", "value 2")
/// .raw_header_adjoin("X-Custom", "value 3")
/// .merge(base)
/// .await;
/// .finalize();
///
/// assert_eq!(response.status(), Status::NotFound);
///
/// # {
/// let ctype: Vec<_> = response.headers().get("Content-Type").collect();
/// assert_eq!(ctype, vec![ContentType::HTML.to_string()]);
/// # }
///
/// # {
/// let custom_values: Vec<_> = response.headers().get("X-Custom").collect();
/// assert_eq!(custom_values, vec!["value 1"]);
/// # }
///
/// # })
/// ```
pub fn merge(&mut self, other: Response<'r>) {
if let Some(status) = other.status {
@ -1234,34 +1150,26 @@ impl<'r> Response<'r> {
/// use rocket::Response;
/// use rocket::http::{Status, ContentType};
///
/// # rocket::async_test(async {
///
/// let other = Response::build()
/// .status(Status::NotFound)
/// .header(ContentType::HTML)
/// .raw_header("X-Custom", "value 1")
/// .await;
/// .finalize();
///
/// let response = Response::build()
/// .status(Status::ImATeapot)
/// .raw_header("X-Custom", "value 2")
/// .raw_header_adjoin("X-Custom", "value 3")
/// .join(other)
/// .await;
/// .finalize();
///
/// assert_eq!(response.status(), Status::ImATeapot);
///
/// # {
/// let ctype: Vec<_> = response.headers().get("Content-Type").collect();
/// assert_eq!(ctype, vec![ContentType::HTML.to_string()]);
/// # }
///
/// # {
/// let custom_values: Vec<_> = response.headers().get("X-Custom").collect();
/// assert_eq!(custom_values, vec!["value 2", "value 3", "value 1"]);
/// # }
///
/// # })
/// ```
pub fn join(&mut self, other: Response<'r>) {
if self.status.is_none() {
@ -1295,11 +1203,10 @@ impl fmt::Debug for Response<'_> {
use crate::request::Request;
#[crate::async_trait]
impl<'r> Responder<'r> for Response<'r> {
/// This is the identity implementation. It simply returns `Ok(self)`.
fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async {
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Ok(self)
})
}
}

View File

@ -12,7 +12,7 @@ use std::collections::hash_map::DefaultHasher;
use std::borrow::Cow;
use crate::request::Request;
use crate::response::{Responder, Response, ResultFuture};
use crate::response::{self, Responder, Response};
use crate::http::Status;
/// Sets the status of the response to 201 (Created).
@ -160,9 +160,9 @@ impl<'r, R> Created<R> {
/// the response with the `Responder`, the `ETag` header is set conditionally if
/// a hashable `Responder` is provided via [`Created::tagged_body()`]. The `ETag`
/// header is set to a hash value of the responder.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Created<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
let mut response = Response::build();
if let Some(responder) = self.1 {
response.merge(responder.respond_to(req).await?);
@ -175,8 +175,6 @@ impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Created<R> {
response.status(Status::Created)
.raw_header("Location", self.0)
.ok()
.await
})
}
}
@ -209,16 +207,15 @@ pub struct Accepted<R>(pub Option<R>);
/// Sets the status code of the response to 202 Accepted. If the responder is
/// `Some`, it is used to finalize the response.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Accepted<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond_to(req).await?);
}
build.status(Status::Accepted).ok().await
})
build.status(Status::Accepted).ok()
}
}
@ -276,16 +273,15 @@ pub struct BadRequest<R>(pub Option<R>);
/// Sets the status code of the response to 400 Bad Request. If the responder is
/// `Some`, it is used to finalize the response.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for BadRequest<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond_to(req).await?);
}
build.status(Status::BadRequest).ok().await
})
build.status(Status::BadRequest).ok()
}
}
@ -385,14 +381,12 @@ impl<'r, R: Responder<'r>> Responder<'r> for Forbidden<R> {
pub struct NotFound<R>(pub R);
/// Sets the status code of the response to 404 Not Found.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for NotFound<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
Response::build_from(self.0.respond_to(req).await?)
.status(Status::NotFound)
.ok()
.await
})
}
}
@ -453,14 +447,12 @@ pub struct Custom<R>(pub Status, pub R);
/// Sets the status code of the response and then delegates the remainder of the
/// response to the wrapped responder.
#[crate::async_trait]
impl<'r, R: Responder<'r> + Send + 'r> Responder<'r> for Custom<R> {
fn respond_to(self, req: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async move {
async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> {
Response::build_from(self.1.respond_to(req).await?)
.status(self.0)
.ok()
.await
})
}
}

View File

@ -3,7 +3,7 @@ use std::fmt::{self, Debug};
use tokio::io::AsyncRead;
use crate::request::Request;
use crate::response::{Response, Responder, ResultFuture, DEFAULT_CHUNK_SIZE};
use crate::response::{self, Response, Responder, DEFAULT_CHUNK_SIZE};
/// Streams a response to a client from an arbitrary `AsyncRead`er type.
///
@ -66,10 +66,9 @@ impl<T: AsyncRead> From<T> for Stream<T> {
/// If reading from the input stream fails at any point during the response, the
/// response is abandoned, and the response ends abruptly. An error is printed
/// to the console with an indication of what went wrong.
#[crate::async_trait]
impl<'r, T: AsyncRead + Send + 'r> Responder<'r> for Stream<T> {
fn respond_to(self, _: &'r Request<'_>) -> ResultFuture<'r> {
Box::pin(async {
Response::build().chunked_body(self.0, self.1).ok().await
})
async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
Response::build().chunked_body(self.0, self.1).ok()
}
}

View File

@ -14,7 +14,9 @@ use crate::http::Method;
type Selector = Method;
// A handler to use when one is needed temporarily.
pub(crate) fn dummy_handler<'r>(r: &'r Request<'_>, _: crate::Data) -> BoxFuture<'r, crate::handler::Outcome<'r>> {
pub(crate) fn dummy_handler<'r>(
r: &'r Request<'_>, _: crate::Data
) -> BoxFuture<'r, crate::handler::Outcome<'r>> {
crate::Outcome::from(r, ())
}

View File

@ -9,7 +9,7 @@ use rocket::http::Header;
async fn do_not_overwrite() -> Response<'static> {
Response::build()
.header(Header::new("Server", "Test"))
.await
.finalize()
}
#[get("/use_default")]

View File

@ -3,8 +3,9 @@
#[macro_use] extern crate rocket;
use rocket::State;
use rocket::response::{self, Responder};
use rocket::{Request, State};
use rocket::futures::future::BoxFuture;
use rocket::response::{Responder, Result};
struct SomeState;
@ -14,7 +15,9 @@ pub struct CustomResponder<'r, R> {
}
impl<'r, R: Responder<'r>> Responder<'r> for CustomResponder<'r, R> {
fn respond_to(self, _: &rocket::Request) -> response::ResultFuture<'r> {
fn respond_to<'a, 'x>(self, _: &'r Request<'a>) -> BoxFuture<'x, Result<'r>>
where 'a: 'x, 'r: 'x, Self: 'x
{
unimplemented!()
}
}

View File

@ -5,14 +5,14 @@ mod tests;
use std::env;
use tokio::fs::File;
use rocket::{Request, Handler, Route, Data, Catcher, try_outcome};
use rocket::http::{Status, RawStr};
use rocket::response::{self, Responder, status::Custom};
use rocket::handler::{Outcome, HandlerFuture};
use rocket::outcome::IntoOutcome;
use rocket::http::Method::*;
use rocket::futures::future::BoxFuture;
use rocket::tokio::fs::File;
fn forward<'r>(_req: &'r Request, data: Data) -> HandlerFuture<'r> {
Box::pin(async move { Outcome::forward(data) })
@ -68,7 +68,7 @@ fn get_upload<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> {
Outcome::from(req, std::fs::File::open(env::temp_dir().join("upload.txt")).ok())
}
fn not_found_handler<'r>(req: &'r Request) -> response::ResultFuture<'r> {
fn not_found_handler<'r>(req: &'r Request) -> BoxFuture<'r, response::Result<'r>> {
let res = Custom(Status::NotFound, format!("Couldn't find: {}", req.uri()));
res.respond_to(req)
}