Trait trillium::Handler[][src]

pub trait Handler: Send + Sync + 'static {
    fn run<'life0, 'async_trait>(
        &'life0 self,
        conn: Conn
    ) -> Pin<Box<dyn Future<Output = Conn> + Send + 'async_trait>>
    where
        'life0: 'async_trait,
        Self: 'async_trait
; fn init<'life0, 'life1, 'async_trait>(
        &'life0 mut self,
        _info: &'life1 mut Info
    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
    where
        'life0: 'async_trait,
        'life1: 'async_trait,
        Self: 'async_trait
, { ... }
fn before_send<'life0, 'async_trait>(
        &'life0 self,
        conn: Conn
    ) -> Pin<Box<dyn Future<Output = Conn> + Send + 'async_trait>>
    where
        'life0: 'async_trait,
        Self: 'async_trait
, { ... }
fn has_upgrade(&self, _upgrade: &Upgrade) -> bool { ... }
fn upgrade<'life0, 'async_trait>(
        &'life0 self,
        _upgrade: Upgrade
    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
    where
        'life0: 'async_trait,
        Self: 'async_trait
, { ... }
fn name(&self) -> Cow<'static, str> { ... } }
Expand description

The building block for Trillium applications.

Concept

Many other frameworks have a notion of middleware and endpoints, in which the model is that a request passes through a router and then any number of middlewares, then a single endpoint that returns a response, and then passes a response back through the middleware stack.

Because a Trillium Conn represents both a request and response, there is no distinction between middleware and endpoints, as all of these can be modeled as Fn(Conn) -> Future<Output = Conn>.

Implementing Handler

The simplest handler is an async closure or async fn that receives a Conn and returns a Conn, and Handler has a blanket implementation for any such Fn.

// as a closure
let handler = |conn: trillium::Conn| async move { conn.ok("trillium!") };

use trillium_testing::prelude::*;
assert_ok!(get("/").on(&handler), "trillium!");
// as an async function
async fn handler(conn: trillium::Conn) -> trillium::Conn {
    conn.ok("trillium!")
}
use trillium_testing::prelude::*;
assert_ok!(get("/").on(&handler), "trillium!");

The simplest implementation of Handler for a named type looks like this:

pub struct MyHandler;
#[trillium::async_trait]
impl trillium::Handler for MyHandler {
    async fn run(&self, conn: trillium::Conn) -> trillium::Conn {
        conn
    }
}

use trillium_testing::prelude::*;
assert_not_handled!(get("/").on(&MyHandler)); // we did not halt or set a body status

Temporary Note: Until rust has true async traits, implementing handler requires the use of the async_trait macro, which is reexported as trillium::async_trait.

Full trait specification

Unfortunately, the async_trait macro results in the difficult-to-read documentation at the top of the page, so here is how the trait is actually defined in trillium code:

#[trillium::async_trait]
pub trait Handler: Send + Sync + 'static {
    async fn run(&self, conn: Conn) -> Conn;
    async fn init(&mut self, info: &mut Info); // optional
    async fn before_send(&self, conn: Conn); // optional
    fn has_upgrade(&self, _upgrade: &Upgrade) -> bool; // optional
    async fn upgrade(&self, _upgrade: Upgrade); // mandatory only if has_upgrade returns true
    fn name(&self) -> Cow<'static, str>; // optional
}

See each of the function definitions below for advanced implementation.

For most application code and even trillium-packaged framework code, run is the only trait function that needs to be implemented.

Required methods

Executes this handler, performing any modifications to the Conn that are desired.

Provided methods

Performs one-time async set up on a mutable borrow of the Handler before the server starts accepting requests. This allows a Handler to be defined in synchronous code but perform async setup such as establishing a database connection or fetching some state from an external source. This is optional, and chances are high that you do not need this.

It also receives a mutable borrow of the Info that represents the current connection.

stability note: This may go away at some point. Please open an **issue if you have a use case which requires it.

Performs any final modifications to this conn after all handlers have been run. Although this is a slight deviation from the simple conn->conn->conn chain represented by most Handlers, it provides an easy way for libraries to effectively inject a second handler into a response chain. This is useful for loggers that need to record information both before and after other handlers have run, as well as database transaction handlers and similar library code.

❗IMPORTANT NOTE FOR LIBRARY AUTHORS: Please note that this will run whether or not the conn has was halted before Handler::run was called on a given conn. This means that if you want to make your before_send callback conditional on whether run was called, you need to put a unit type into the conn’s state and check for that.

stability note: I don’t love this for the exact reason that it breaks the simplicity of the conn->conn->model, but it is currently the best compromise between that simplicity and convenience for the application author, who should not have to add two Handlers to achieve an “around” effect.

predicate function answering the question of whether this Handler would like to take ownership of the negotiated Upgrade. If this returns true, you must implement Handler::upgrade. The first handler that responds true to this will receive ownership of the trillium::Upgrade in a subsequent call to Handler::upgrade

This will only be called if the handler reponds true to Handler::has_upgrade and will only be called once for this upgrade. There is no return value, and this function takes exclusive ownership of the underlying transport once this is called. You can downcast the transport to whatever the source transport type is and perform any non-http protocol communication that has been negotiated. You probably don’t want this unless you’re implementing something like websockets. Please note that for many transports such as TcpStreams, dropping the transport (and therefore the Upgrade) will hang up / disconnect.

Customize the name of your handler. This is used in Debug implementations. The default is the type name of this handler.

Implementations on Foreign Types

Implementors