How can I pass context between request layers and response layers in Tower

I get Tower pretty well... but I canNOT for the life of me figure out how to share request specific state created in a layer before the application service, with layers that transform the response!

I want something like this

  • First layer parses the request and stores bits of information in a struct
  • the "application service" (over which I have little control except that it accepts an http::Request and returns an http::Response) then consumes the request and constructs the response
  • Second layer uses the struct created by the first layer from the request to modify the response.

Note that I don't have control over the application service. That gets passed to me as a tower::Service to which I can attach layers etc.

I cannot figure out how to get that context information to the final layer. The main technique that I tried was to put the context in the Request extensions before returning from the first layer ... but how to swap it over to the Response extensions before the second layer gets the response? No amount of closure efforts worked because that meant moving the incoming application Service into the closure which made the closure FnOnce, not FnMut.

Any help would be appreciated. Thx

1 Like

http has request extensions for this: Passing state from middleware to handlers. And tower-http has a middleware layer for it.

I don't know if that's quite what you had in mind. But if you are using http (e.g., through axum) this should do the trick. I believe the response has access to the same set of extensions applied to the request. (Or maybe this is not the case? Re-reading your message, it sounds like the extensions do not propagate from request to response.)

edit: Right, the creator of the Response needs to copy extensions from the request explicitly. It isn't like a WSGI interface where responses reference the original request. But the service itself has knowledge of the original request anyway. Your layer calls the next layer by calling Next::run() [1], which returns the response from the handler. But you still have access to the request and any state you need when you get the response from the handler. Can you not update the response in the same layer that updates the request? Perhaps passing it to an outer layer as an extension on the response...


  1. Next is a thing in axum anyway... other routers with middleware should have something similar. ↩ī¸Ž

Well on the surface, no I've tried that! BUT you have given me inspiration! What if, instead of trying to attach my code to the incoming Service and stor may context in the extensions, instead I used the tower_http::add-extension clone technique to clone the incoming SERVICE and put THAT in the request extensions.

My pre-handler stuff can then extract that from the extension and run it, rather than pulling it directly from the closure environment!

Thanks for the inspiration!

Ok, THAT didn't work (and it took me three days of the most irritating mixture of Send/Sync mismatches and foreign opaque type defining call errors to finally give it up), BUT one that will work, I'm certain, AND will be a great addition to the middlewares in the tower-http crate, will be 'PropagateExtension' ... it will be almost exactly the same as PropagateHeader, but taking an extension from the Request to insert into the Response instead of cloning a header!

One would use AddExtension at the beginning of their layers (or insert an extension into the request in some other way), put PropagteExtension just before the service that consumes the Request and produces the Response, and the state can be shared between pre- and post- layers!!!

Initial code/PR next week!

I wonder why you have 2 separate layers here. Normally a layer handles both requests and responses that passes this layer. It takes a request from the upper layer, do its transformation, pass its result to the lower layer (or service) to get its response, do its transformation and pass it up. There's no restrictions to keep your context between 2 transformations. If you're using async fn/block the context would literally be some variables.

2 Likes

In short, composabiility, and the ability to not have to manually create service or layer implementations for each 'unit of functionality', instead using closures that are attached in the desired order either via the Service builder, or the ServiceExt trait methods.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.