You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There are certain pieces of metadata that are commonly associated with requests but aren't part of HTTP itself, e.g.:
Peer IP address
TLS session details (certificates, ciphers, etc)
HTTP URL router match results (original pattern, parameters, etc)
This kind of metadata can be inserted into requests as headers, but this has historically been a source of security and name collision issues and I hope we can offer a better alternative.
A few options to consider:
Add methods for the most common bits of metadata directly to the request type
Add a generic "metadata"/"extensions" map to the request type (like hyper)
Add a separate "metadata"/"context" argument to handle
This is probably a good idea to return to now that 0.2 is done. Just sketching some thoughts based on a discussion with @lann today:
I think there are two cases:
Common metadata that everyone will want, so it would be valuable to standardize in WASI HTTP
Middleware- or vendor-defined metadata that we want to be able to "tack on" to a request or response
Case (1) is the easier case to sketch in WIT and I'm thinking looks roughly like:
packagewasi:http;interfacetypes {
...
resourcerequest {
...
/// request-metadata is a child resource and follows the same child rules as headers and bodymetadata:func() ->request-metadata
}
resourcerequest-metadata {
peer-IP-address:func() ->option<...>
set-peer-IP-address:func(...)
...
}
}
and similarly for response.
For case (2), the middleware/vendor can leverage the Component Model's parametric polymorphism (and WIT's use) to, effectively, extend request-metadata:
If we wanted to make this syntactically nicer so that getUser showed up as a method on the prototype chain of request.metadata, we could implement CM/#296 (which incidentally mentions exactly this use case) which gives the bindings generator the info it needs.
For reference, a component using my-auth-middleware could target a world looking like:
Note that outgoing-handler/incoming-handler (whose handle functions take and return WASI HTTP requests and responses and don't know anything about my-auth-middleware) don't have to change nor is there any dynamic cast needed anywhere. And if two components targeting my-auth-world are linked together (linking one's exported handler to the other's imported handler, assuming host magic or 0.3 when we can collapse the two handler interfaces into wasi:http/handler), it also just works (still without a downcasts). (Parametric polymorphism FTW!)
An alternative approach to Case (2) is to extend WASI HTTP with some totally dynamic fields/values:
I expect this is what some HTTP standard libraries do and maybe there are some good use cases for it (it's better than smuggling the info through headers), so maybe we should do it too, but it seems worse from a performance and declarative-interfaces perspective.
As discussed, I really like the interface that this parametric polymorphism provides. My main concern is with the implementation of composition. For example, if I want to implement auth "middleware" by wrapping a wasi-http handler component in an auth component, I believe this design either requires host magic or it requires the auth component to implement "donut wrapping" of its parent's wasi-http interface(s)/types. The former (host magic) would probably be fine for certain "standard-ish" metadata but it isn't clear to me how it would be extensible. The latter (donut wrapping) seems plausible in principle but I'm not sure that the details of its implementation have been explored enough to be comfortable with depending on it for 0.3.
Yeah, that's a fair point, virtualization is definitely important. For this particular scenario, I think we might be able to get away with something that isn't optimal (it creates 1 more component instance than if we had full donut-wrapping producer toolchain support), but is at least is expressible in WAC/WIT today (I think?).
So the overall composed component could look like:
import types: wasi:http/types;
import outgoing: wasi:http/handler;
let auth-imports = new my-auth-middleware:import-adapter { types, outgoing };
let guest = new my:guest { auth-imports, outgoing }
let auth-exports = new my-auth-middleware:export-adapter { auth-imports, guest };
export auth-exports as wasi:http/handler;
and my:guest targeted the auth-guest world above. The private-methods interface gives the export-adapter component the ability to potentially call trusted exports on the import-adapter component that you wouldn't want the guest to be able to do (to maintain whatever auth invariants).
It's not optimal, but perhaps we could use this use case to push forward producer tooling to support donut wrapping directly so that import-adapter and export-adapter can be fused into one component with one memory, allowing the shared-nothing call through private-methods to be a shared-everything call through core wasm.
Going back to #825 (comment) "case (1)", I wonder if it could/should be unified with (2) by defining "first-party" metadata in that same form as "third-party" metadata, e.g.
This would produce less-nice bindings (absent new bindgen features) for these particular functions but it would be consistent with - and establish the pattern for - "third-party" metadata. It could also dovetail nicely with optional imports to statically express whether an implementer provides the given metadata.
Edit: somewhat of an aside, peer-IP-address is a perfect candidate for "middleware" virtualization: it is very common for a service to get the "real" peer IP from a trusted header like X-Forwarded-For.
@dicej pointed out that we'll run into a current WIT limitation when trying to write that private-methods interface, since we have a single function that needs to refer to both the imported wasi:http/types.request and the exported wasi:http/types.request. In particular, what we need to write is:
Normally, this would fail validation because host-http and virtual-http are not interface names defined in this package (intentionally). What the WIT extension would allow us to do is satisfy them via with in the world targeted by the import adapter:
The above with { foo = import bar } syntax was discussed as part of CM/#308 originally in the spirit of overriding the default resolution, but here there's nothing to override; if we didn't have the with, the export private-methods; would fail to validate.
I realized there's another approach in which import-adapter doesn't export wasi:http/types but instead whatever made up name it wants, using WAC to do the renaming:
and the WAC would be updated to do the renaming from virtual-http-types to wasi:http/types:
import types: wasi:http/types;
import outgoing: wasi:http/handler;
let auth-imports = new my-auth-middleware:import-adapter { types, outgoing };
let guest = new my:guest { wasi:http/types = auth-imports[my-auth-middleware:private-api/virtual-http-types], outgoing }
let auth-exports = new my-auth-middleware:export-adapter { auth-imports, guest };
export auth-exports as wasi:http/handler;
which is allowed because ultimately "wasi:http/types" is just an import name and the types match. It does feel silly to have to copy-paste wasi:http/types into virtual-http-types, but I think this could be avoided by (yet another) WIT feature we should add: allow include inside interface { ... }, so that we could write interface virtual-http-types { include wasi:http/types; }.
So, altogether, I think we have multiple options for how to express this.
The wasi:http champions discussed this today and we have no updates at this time. This work may be unblocked in the future by component model features like optional imports.
Activity
[-]Request metadata[/-][+][v0.3] Request metadata[/+]lukewagner commentedon Apr 30, 2024
This is probably a good idea to return to now that 0.2 is done. Just sketching some thoughts based on a discussion with @lann today:
I think there are two cases:
requestorresponseCase (1) is the easier case to sketch in WIT and I'm thinking looks roughly like:
and similarly for
response.For case (2), the middleware/vendor can leverage the Component Model's parametric polymorphism (and WIT's
use) to, effectively, extendrequest-metadata:Thus, from, let's say JS, you could write:
If we wanted to make this syntactically nicer so that
getUsershowed up as a method on the prototype chain ofrequest.metadata, we could implement CM/#296 (which incidentally mentions exactly this use case) which gives the bindings generator the info it needs.For reference, a component using
my-auth-middlewarecould target a world looking like:Note that
outgoing-handler/incoming-handler(whosehandlefunctions take and return WASI HTTPrequests andresponses and don't know anything aboutmy-auth-middleware) don't have to change nor is there any dynamic cast needed anywhere. And if two components targetingmy-auth-worldare linked together (linking one's exported handler to the other's imported handler, assuming host magic or 0.3 when we can collapse the two handler interfaces intowasi:http/handler), it also just works (still without a downcasts). (Parametric polymorphism FTW!)An alternative approach to Case (2) is to extend WASI HTTP with some totally dynamic fields/values:
I expect this is what some HTTP standard libraries do and maybe there are some good use cases for it (it's better than smuggling the info through headers), so maybe we should do it too, but it seems worse from a performance and declarative-interfaces perspective.
lann commentedon May 2, 2024
As discussed, I really like the interface that this parametric polymorphism provides. My main concern is with the implementation of composition. For example, if I want to implement auth "middleware" by wrapping a wasi-http handler component in an auth component, I believe this design either requires host magic or it requires the auth component to implement "donut wrapping" of its parent's wasi-http interface(s)/types. The former (host magic) would probably be fine for certain "standard-ish" metadata but it isn't clear to me how it would be extensible. The latter (donut wrapping) seems plausible in principle but I'm not sure that the details of its implementation have been explored enough to be comfortable with depending on it for 0.3.
lukewagner commentedon May 2, 2024
Yeah, that's a fair point, virtualization is definitely important. For this particular scenario, I think we might be able to get away with something that isn't optimal (it creates 1 more component instance than if we had full donut-wrapping producer toolchain support), but is at least is expressible in WAC/WIT today (I think?).
So the overall composed component could look like:
where
my-auth-middleware:import-adapterhad type:and
my-auth-middleware:export-adapterhad type:and
my:guesttargeted theauth-guestworld above. Theprivate-methodsinterface gives theexport-adaptercomponent the ability to potentially call trusted exports on theimport-adaptercomponent that you wouldn't want theguestto be able to do (to maintain whatever auth invariants).It's not optimal, but perhaps we could use this use case to push forward producer tooling to support donut wrapping directly so that
import-adapterandexport-adaptercan be fused into one component with one memory, allowing the shared-nothing call throughprivate-methodsto be a shared-everything call through core wasm.WDYT?
lann commentedon May 3, 2024
I'll have to let others chime in on the composition implementation but the approach looks straightforward enough to me in the near term (0.3). Thanks!
lann commentedon May 3, 2024
Going back to #825 (comment) "case (1)", I wonder if it could/should be unified with (2) by defining "first-party" metadata in that same form as "third-party" metadata, e.g.
This would produce less-nice bindings (absent new bindgen features) for these particular functions but it would be consistent with - and establish the pattern for - "third-party" metadata. It could also dovetail nicely with optional imports to statically express whether an implementer provides the given metadata.
Edit: somewhat of an aside,
peer-IP-addressis a perfect candidate for "middleware" virtualization: it is very common for a service to get the "real" peer IP from a trusted header likeX-Forwarded-For.lukewagner commentedon May 3, 2024
Ah, good point! It also replaces dynamic
option<...>result type in my original sample with more static "this is what I actually need" declaration.lukewagner commentedon May 7, 2024
@dicej pointed out that we'll run into a current WIT limitation when trying to write that
private-methodsinterface, since we have a single function that needs to refer to both the importedwasi:http/types.requestand the exportedwasi:http/types.request. In particular, what we need to write is:Normally, this would fail validation because
host-httpandvirtual-httpare not interface names defined in this package (intentionally). What the WIT extension would allow us to do is satisfy them viawithin theworldtargeted by the import adapter:The above
with { foo = import bar }syntax was discussed as part of CM/#308 originally in the spirit of overriding the default resolution, but here there's nothing to override; if we didn't have thewith, theexport private-methods;would fail to validate.I realized there's another approach in which
import-adapterdoesn't exportwasi:http/typesbut instead whatever made up name it wants, using WAC to do the renaming:and the WAC would be updated to do the renaming from
virtual-http-typestowasi:http/types:which is allowed because ultimately
"wasi:http/types"is just an import name and the types match. It does feel silly to have to copy-pastewasi:http/typesintovirtual-http-types, but I think this could be avoided by (yet another) WIT feature we should add: allowincludeinsideinterface { ... }, so that we could writeinterface virtual-http-types { include wasi:http/types; }.So, altogether, I think we have multiple options for how to express this.
lann commentedon Oct 8, 2026
The
wasi:httpchampions discussed this today and we have no updates at this time. This work may be unblocked in the future by component model features like optional imports.