Module aws_smithy_http_server::plugin
source · Expand description
The plugin system allows you to build middleware with an awareness of the operation it is applied to.
The system centers around the Plugin
, HttpMarker
, and ModelMarker
traits. In
addition, this module provides helpers for composing and combining Plugin
s.
§HTTP plugins vs model plugins
Plugins come in two flavors: HTTP plugins and model plugins. The key difference between them is when they run:
- A HTTP plugin acts on the HTTP request before it is deserialized, and acts on the HTTP response after it is serialized.
- A model plugin acts on the modeled operation input after it is deserialized, and acts on the modeled operation output or the modeled operation error before it is serialized.
See the relevant section in the book, which contains an illustrative diagram.
Both kinds of plugins implement the Plugin
trait, but only HTTP plugins implement the
HttpMarker
trait and only model plugins implement the ModelMarker
trait. There is no
difference in how an HTTP plugin or a model plugin is applied, so both the HttpMarker
trait
and the ModelMarker
trait are marker traits, they carry no behavior. Their only purpose
is to mark a plugin, at the type system leve, as allowed to run at a certain time. A plugin can be
both a HTTP plugin and a model plugin by implementing both traits; in this case, when the
plugin runs is decided by you when you register it in your application. IdentityPlugin
,
Scoped
, and LayerPlugin
are examples of plugins that implement both traits.
In practice, most plugins are HTTP plugins. Since HTTP plugins run before a request has been correctly deserialized, HTTP plugins should be fast and lightweight. Only use model plugins if you absolutely require your middleware to run after deserialization, or to act on particular fields of your deserialized operation’s input/output/errors.
§Filtered application of a HTTP Layer
// Create a `Plugin` from a HTTP `Layer`
let plugin = LayerPlugin(layer);
scope! {
struct OnlyGetPokemonSpecies {
includes: [GetPokemonSpecies],
excludes: [/* The rest of the operations go here */]
}
}
// Only apply the layer to operations with name "GetPokemonSpecies".
let filtered_plugin = Scoped::new::<OnlyGetPokemonSpecies>(&plugin);
// The same effect can be achieved at runtime.
let filtered_plugin = filter_by_operation(&plugin, |operation: Operation| operation == Operation::GetPokemonSpecies);
§Construct a Plugin
from a closure that takes as input the operation name
use aws_smithy_http_server::plugin::plugin_from_operation_fn;
use tower::layer::layer_fn;
struct FooService<S> {
info: String,
inner: S
}
fn map<S>(op: Operation, inner: S) -> FooService<S> {
match op {
Operation::CheckHealth => FooService { info: op.shape_id().name().to_string(), inner },
Operation::GetPokemonSpecies => FooService { info: "bar".to_string(), inner },
_ => todo!()
}
}
// This plugin applies the `FooService` middleware around every operation.
let plugin = plugin_from_operation_fn(map);
§Combine Plugin
s
// Combine `Plugin`s `a` and `b`. Both need to implement `HttpMarker`.
let plugin = HttpPlugins::new()
.push(a)
.push(b);
As noted in the HttpPlugins
documentation, the plugins’ runtime logic is executed in registration order,
meaning that a
is run before b
in the example above.
Similarly, you can use ModelPlugins
to combine model plugins.
§Example implementation of a Plugin
The following is an example implementation of a Plugin
that prints out the service’s name
and the name of the operation that was hit every time it runs. Since it doesn’t act on the HTTP
request nor the modeled operation input/output/errors, this plugin can be both an HTTP plugin
and a model plugin. In practice, however, you’d only want to register it once, as either an
HTTP plugin or a model plugin.
use aws_smithy_http_server::{
operation::OperationShape,
service::ServiceShape,
plugin::{Plugin, HttpMarker, HttpPlugins, ModelMarker},
shape_id::ShapeId,
};
/// A [`Service`] that adds a print log.
#[derive(Clone, Debug)]
pub struct PrintService<S> {
inner: S,
service_id: ShapeId,
operation_id: ShapeId
}
impl<R, S> Service<R> for PrintService<S>
where
S: Service<R>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: R) -> Self::Future {
println!("Hi {} in {}", self.operation_id.absolute(), self.service_id.absolute());
self.inner.call(req)
}
}
/// A [`Plugin`] for a service builder to add a [`PrintLayer`] over operations.
#[derive(Debug)]
pub struct PrintPlugin;
impl<Ser, Op, T> Plugin<Ser, Op, T> for PrintPlugin
where
Ser: ServiceShape,
Op: OperationShape,
{
type Output = PrintService<T>;
fn apply(&self, inner: T) -> Self::Output {
PrintService {
inner,
service_id: Op::ID,
operation_id: Ser::ID,
}
}
}
// This plugin could be registered as an HTTP plugin and a model plugin, so we implement both
// marker traits.
impl HttpMarker for PrintPlugin { }
impl ModelMarker for PrintPlugin { }
Structs§
- Filters the application of an inner
Plugin
using a predicate over theServiceShape::Operations
. - A wrapper struct for composing HTTP plugins.
- A
Plugin
that maps a service to itself. - A wrapper struct for composing model plugins. It operates identically to
HttpPlugins
; see its documentation. - An adapter to convert a
Fn(ShapeId, T) -> Service
closure into aPlugin
. Seeplugin_from_operation_fn
for more details.
Enums§
Traits§
- A HTTP plugin is a plugin that acts on the HTTP request before it is deserialized, and acts on the HTTP response after it is serialized.
- A model plugin is a plugin that acts on the modeled operation input after it is deserialized, and acts on the modeled operation output or the modeled operation error before it is serialized.
Functions§
- Filters the application of an inner
Plugin
using a predicate over theServiceShape::Operations
. - Constructs a
Plugin
using a closure over the [ServiceShape::]
F: Fn(ShapeId, T) -> Service`.