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 Plugins.
§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 Plugins
// 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§
- Filter
ByOperation - Filters the application of an inner
Pluginusing a predicate over theServiceShape::Operations. - Http
Plugins - A wrapper struct for composing HTTP plugins.
- Identity
Plugin - A
Pluginthat maps a service to itself. - Layer
Plugin - A
Pluginwhich acts as a [Layer]L. - Model
Plugins - A wrapper struct for composing model plugins.
It operates identically to
HttpPlugins; see its documentation. - Operation
Fn - An adapter to convert a
Fn(ShapeId, T) -> Serviceclosure into aPlugin. Seeplugin_from_operation_fnfor more details. - Plugin
Layer - A [
Layer] which acts as aPluginPlfor specific protocolPand operationOp. - Plugin
Stack - A wrapper struct which composes an
Innerand anOuterPlugin. - Scoped
- A
Pluginwhich scopes the application of an innerPlugin.
Enums§
Traits§
- Http
Marker - 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.
- Model
Marker - 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.
- Plugin
- A mapping from one
Serviceto another. This should be viewed as aLayerparameterized by the protocol and operation.
Functions§
- filter_
by_ operation - Filters the application of an inner
Pluginusing a predicate over theServiceShape::Operations. - plugin_
from_ operation_ fn - Constructs a
Pluginusing a closure over the [ServiceShape::]F: Fn(ShapeId, T) -> Service`.