-
Notifications
You must be signed in to change notification settings - Fork 290
Description
As part of WASI P3 work, we want to explore implementing HTTP middleware using component composition. The purpose is to allow application developers to use off-the-shelf or custom components in a pipeline to add or customise behaviour without needing to bake it into the core application component.
For example, the following site requires a CORS check and authentication, and enriches the request with geolocation information. None of these are core application concerns, and they could ideally be pulled off the shelf rather than custom coded.

Manifest
Spin cannot naturally do this in the current dependency model, because dependencies model only imports of the application component. (It can do one level of middleware unnaturally, by making the main component the middleware and having it depend on the application, but 1. that currently allows only one level and 2. that's unnatural, ew.)
So to support middleware we have to decide:
- Do we try to wedge it into the current dependencies section?
- If not, where do we want to put it?
The trouble with using the dependencies section is that it's a map, whereas the middleware pipeline is a sequence. All middleware components use the same interface, and the order of plugging them together is significant. I'm sure some creative soul has an idea for how to overcome this mismatch, but it defeated me. Ideas welcome!
If we can't use the dependencies section, where do we put it? The two options appear to be the trigger or the component:
# Trigger?
[[trigger.http]]
route = "/..."
component = "cat-videos"
pipeline = ["cors", "auth", "geo"]
# Or alternative trigger?
[[trigger.http]]
route = "/..."
component = ["cors", "auth", "geo", "cat-videos"]
# Or component?
[component.cat-videos]
source = "catvids.wasm"
pipeline = ["cors", "auth", "geo"]
I'm attracted to putting it on the trigger, because:
- It's HTTP specific - it can be part of the HTTP trigger schema rather than the common component schema
- It frames middleware as part of the serving pipeline rather than as inherent to the component
(okay mostly 1 I admit)
However, all our composition and Wasm loading infrastructure is component centric. During prototyping (https://github.com/itowlson/spin/tree/http-middleware) I found it really hard to see how the relevant subsystems could reach into trigger-specific config this way - for example how would spin registry push
know that this field on the HTTP trigger required binaries to be resolved and packaged?
So for prototyping I've been putting it on the component. But I'd value discussion and feedback on this.
Interface
Middleware needs to be able to pass a request on to the next entry in the chain. In principle this could be done by composing each component's wasi:http/handler
import onto the next one's export, then the middleware code calling handle
on the incoming request to pass it on. But many middleware items will want to do their own HTTP stuff, e.g. authenticating using OIDC or whatever.
WebAssembly/wasi-http#167 proposes adding an origin
interface to the WASI-HTTP proxy world. This would be an additional interface identical to handler
, but with the semantics of "pass it on along the chain" rather than "fling it across the network." My prototype called this next
and had it in a Spin-specific package, but the concept is the same. If origin
lands soon enough then we can use that, otherwise we use our own and adopt the spec one when it arrives.
Unlike dependencies, Spin (or the HTTP trigger, or whatever) needs special knowledge of the middleware origin/next interface, because it's not amenable to plug composition (and we don't want devs to have to specify the mapping on every pipeline entry!).
Permissions
Currently, dependencies get the choice of "main component permissions or nothing," governed by the dependencies_inherit_configuration
flag. We should not expect fine-grained permissions in the middleware timeframe, so middleware will likely inherit the same permissions behaviour, governed by the same flag. This fits nicely with "middleware is an attribute of the component" but not so nicely with it being on the trigger (because if the component needs to allow-list the OIDC provider then it is not middleware-agnostic after all).
This is a bit of a brain dump and I am sure other brains have thought about this a lot more than mine so please weigh in.