1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-11-26 19:41:12 +00:00

expose Handler trait

This commit is contained in:
Rob Ede 2021-12-25 04:43:59 +00:00
parent adf9935841
commit 5860fe5381
No known key found for this signature in database
GPG key ID: 97C636207D3EF933
5 changed files with 91 additions and 27 deletions

View file

@ -8,26 +8,88 @@ use crate::{
BoxError, FromRequest, HttpResponse, Responder, BoxError, FromRequest, HttpResponse, Responder,
}; };
/// A request handler is an async function that accepts zero or more parameters that can be /// The interface for request handlers.
/// extracted from a request (i.e., [`impl FromRequest`]) and returns a type that can be converted
/// into an [`HttpResponse`] (that is, it impls the [`Responder`] trait).
/// ///
/// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not /// # What Is A Request Handler
/// a valid handler. See <https://actix.rs/docs/handlers> for more information. /// A request handler has three requirements:
/// 1. It is an async function (or a function/closure that returns an appropriate future);
/// 1. The function accepts zero or more parameters that implement [`FromRequest`];
/// 1. The async function (or future) resolves to a type that can be converted into an
/// [`HttpResponse`] (i.e., it implements the [`Responder`] trait).
/// ///
/// [`impl FromRequest`]: crate::FromRequest /// # Compiler Errors
/// If you get the error `the trait Handler<_, _, _> is not implemented`, then your handler does not
/// fulfill one or more of the above requirements.
///
/// Unfortunately we cannot provide a better compile error message (while keeping the trait's
/// flexibility) unless a stable alternative to [`#[rustc_on_unimplemented]`][on_unimpl] is added
/// to Rust.
///
/// # How Do Handlers Receive Variable Numbers Of Arguments
/// Rest assured there is no macro magic here; it's just traits.
///
/// The first thing to note is that [`FromRequest`] is implemented for tuples (up to 12 in length).
///
/// Secondly, the `Handler` trait is implemented for functions (up to an [arity] of 12) in a way
/// that aligns their parameter positions with a corresponding tuple of types (becoming the `T` type
/// parameter in this trait's implementation).
///
/// Thanks to Rust's type system, Actix Web can infer the function parameter types. During the
/// extraction step, the parameter types are described as a tuple type, [`from_request`] is run on
/// that tuple, and the `Handler::call` implementation for that particular function arity
/// destructures the tuple into it's component types and calls your handler function with them.
///
/// In pseudo-code the process looks something like this:
/// ```ignore
/// async fn my_handler(body: String, state: web::Data<MyState>) -> impl Responder {
/// ...
/// }
///
/// // the function params above described as a tuple, names do not matter, only position
/// type InferredMyHandlerArgs = (String, web::Data<MyState>);
///
/// // create tuple of arguments to be passed to handler
/// let args = InferredMyHandlerArgs::from_request(&request, &payload).await;
///
/// // call handler with argument tuple
/// let response = Handler::call(&my_handler, args).await;
///
/// // which is effectively...
///
/// let (body, state) = args;
/// let response = my_handler(body, state).await;
/// ```
///
/// This is the source code for the 2-parameter implementation of `Handler` to help illustrate the
/// bounds of the handler call after argument extraction:
/// ```ignore
/// impl<Func, Arg1, Arg2, R> Handler<(Arg1, Arg2), R> for Func
/// where
/// Func: Fn(Arg1, Arg2) -> R + Clone + 'static,
/// R: Future,
/// R::Output: Responder,
/// {
/// fn call(&self, (arg1, arg2): (Arg1, Arg2)) -> R {
/// (self)(arg1, arg2)
/// }
/// }
/// ```
///
/// [arity]: https://en.wikipedia.org/wiki/Arity
/// [`from_request`]: FromRequest::from_request
/// [on_unimpl]: https://github.com/rust-lang/rust/issues/29628
pub trait Handler<T, R>: Clone + 'static pub trait Handler<T, R>: Clone + 'static
where where
R: Future, R: Future,
R::Output: Responder, R::Output: Responder,
{ {
fn call(&self, param: T) -> R; fn call(&self, args: T) -> R;
} }
pub(crate) fn handler_service<F, T, R>(handler: F) -> BoxedHttpServiceFactory pub(crate) fn handler_service<F, Args, R>(handler: F) -> BoxedHttpServiceFactory
where where
F: Handler<T, R>, F: Handler<Args, R>,
T: FromRequest, Args: FromRequest,
R: Future, R: Future,
R::Output: Responder, R::Output: Responder,
<R::Output as Responder>::Body: MessageBody, <R::Output as Responder>::Body: MessageBody,
@ -39,7 +101,7 @@ where
async move { async move {
let (req, mut payload) = req.into_parts(); let (req, mut payload) = req.into_parts();
let res = match T::from_request(&req, &mut payload).await { let res = match Args::from_request(&req, &mut payload).await {
Err(err) => HttpResponse::from_error(err), Err(err) => HttpResponse::from_error(err),
Ok(data) => handler Ok(data) => handler
@ -59,17 +121,18 @@ where
/// ///
/// # Examples /// # Examples
/// ```ignore /// ```ignore
/// factory_tuple! {} // implements Handler for types: fn() -> Res /// factory_tuple! {} // implements Handler for types: fn() -> R
/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> Res /// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> R
/// ``` /// ```
macro_rules! factory_tuple ({ $($param:ident)* } => { macro_rules! factory_tuple ({ $($param:ident)* } => {
impl<Func, $($param,)* Res> Handler<($($param,)*), Res> for Func impl<Func, $($param,)* R> Handler<($($param,)*), R> for Func
where Func: Fn($($param),*) -> Res + Clone + 'static, where Func: Fn($($param),*) -> R + Clone + 'static,
Res: Future, R: Future,
Res::Output: Responder, R::Output: Responder,
{ {
#[inline]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn call(&self, ($($param,)*): ($($param,)*)) -> Res { fn call(&self, ($($param,)*): ($($param,)*)) -> R {
(self)($($param,)*) (self)($($param,)*)
} }
} }

View file

@ -106,6 +106,7 @@ pub use cookie;
pub use crate::app::App; pub use crate::app::App;
pub use crate::error::{Error, ResponseError, Result}; pub use crate::error::{Error, ResponseError, Result};
pub use crate::extract::FromRequest; pub use crate::extract::FromRequest;
pub use crate::handler::Handler;
pub use crate::request::HttpRequest; pub use crate::request::HttpRequest;
pub use crate::resource::Resource; pub use crate::resource::Resource;
pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder}; pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder};

View file

@ -232,10 +232,10 @@ where
/// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().service(web::resource("/").route(web::route().to(index))); /// App::new().service(web::resource("/").route(web::route().to(index)));
/// ``` /// ```
pub fn to<F, I, R>(mut self, handler: F) -> Self pub fn to<F, Args, R>(mut self, handler: F) -> Self
where where
F: Handler<I, R>, F: Handler<Args, R>,
I: FromRequest + 'static, Args: FromRequest + 'static,
R: Future + 'static, R: Future + 'static,
R::Output: Responder + 'static, R::Output: Responder + 'static,
<R::Output as Responder>::Body: MessageBody, <R::Output as Responder>::Body: MessageBody,

View file

@ -176,10 +176,10 @@ impl Route {
/// ); /// );
/// } /// }
/// ``` /// ```
pub fn to<F, T, R>(mut self, handler: F) -> Self pub fn to<F, Args, R>(mut self, handler: F) -> Self
where where
F: Handler<T, R>, F: Handler<Args, R>,
T: FromRequest + 'static, Args: FromRequest + 'static,
R: Future + 'static, R: Future + 'static,
R::Output: Responder + 'static, R::Output: Responder + 'static,
<R::Output as Responder>::Body: MessageBody, <R::Output as Responder>::Body: MessageBody,

View file

@ -146,10 +146,10 @@ pub fn method(method: Method) -> Route {
/// web::to(index)) /// web::to(index))
/// ); /// );
/// ``` /// ```
pub fn to<F, I, R>(handler: F) -> Route pub fn to<F, Args, R>(handler: F) -> Route
where where
F: Handler<I, R>, F: Handler<Args, R>,
I: FromRequest + 'static, Args: FromRequest + 'static,
R: Future + 'static, R: Future + 'static,
R::Output: Responder + 'static, R::Output: Responder + 'static,
<R::Output as Responder>::Body: MessageBody + 'static, <R::Output as Responder>::Body: MessageBody + 'static,