Module context
Expand description
Context passed to and between services as input.
§State
rama
supports two kinds of states:
- type-safe state: this is the
S
generic parameter inContext
and is to be used as much as possible, given its existence and type properties can be validated at compile time - dynamic state: these can be injected as
Extensions
s using methods such asContext::insert
As a rule of thumb one should use the type-safe state (1) in case:
- the state is always expected to exist at the point the middleware/service is called
- the state is specific to the app or middleware
- and the state can be constructed in a default/empty state
The latter is important given the state is often created (or at least reserved) prior to it is actually being populated by the relevant middleware. This is not the case for app-specific state such as Database pools which are created since the start and shared among many different tasks.
The rule could be be simplified to “if you need to .unwrap()
you probably want type-safe state instead”.
It’s however just a guideline and not a hard rule. As maintainers of rama
we’ll do our best to respect it though,
and we recommend you to do the same.
Any state that is optional, and especially optional state injected by middleware, can be inserted using extensions.
It is however important to try as much as possible to then also consume this state in an approach that deals
gracefully with its absence. Good examples of this are header-related inputs. Headers might be set or not,
and so absence of Extensions
s that might be created as a result of these might reasonably not exist.
It might of course still mean the app returns an error response when it is absent, but it should not unwrap/panic.
§Examples
use rama_core::Context;
#[derive(Debug)]
struct ServiceState {
value: i32,
}
let state = ServiceState{ value: 5 };
let ctx = Context::with_state(state);
§Example: Extensions
The Context
can be extended with additional data using the Extensions
type.
use rama_core::Context;
let mut ctx = Context::default();
ctx.insert(5i32);
assert_eq!(ctx.get::<i32>(), Some(&5i32));
§State Wraps
rama
was built from the ground up to operate on and between different layers of the network stack.
This has also an impact on state. Because sure, typed state is nice, but state leakage is not. What do I mean with that?
When creating a TcpListener
with state the state will be owned by that TcpListener
. By default
it will clone the state and pass a clone to each incoming tcp
connection. You can however also
inject your own state provider to customise that behaviour. Pretty much the same goes for an HttpServer
,
where it will do the same for each incoming http request. This is great for stuff that is okay to share, but it is not desired
for state that you wish to have a narrower scope. Examples are state that are tied to a single tcp connection and thus
you do not wish to keep a global cache for this, as it would either be shared or get overly complicated to ensure
you keep things separate and clean.
One solution is to wrap your state.
See for reference: /examples/http_conn_state.rs
In that example we make use of:
MapStateLayer
: this generic layer allows you to map the state from one type to another, which is great in cases like this where you want the Application layer (http) to have a different type compared to the network layer (tpc).- the
derive_more
third-party crate is used as an example how one can use such crates to make services or layers which do not depend on a specific state type, but instead only require a reference (mutable or not) to specific properties they need, which can be useful in case that service is used in multiple branches, each with their own concrete state type.
Structs§
- Context passed to and between services as input.
- A type map of protocol extensions.