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§

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.
  • A mapping from one Service to another. This should be viewed as a Layer parameterized by the protocol and operation.

Functions§