-
SummaryHi, I looked through documentation and examples and didn't find anything that seemed obvious. I'm using Axum, tera, and htmx. When requests come in htmx is going to provide a bunch of headers that will be useful for rendering the view. My initial approach was to use an extractor to pass these values to route handlers, but this leaves me duplicating a lot of code for every handler (because every request may be either AJAX (I only need a partial), or the first request (I need to render the base template with partial included)). My next thought was that every handler will return some struct AppResponse {
/// path to a partial (it may be rendered as a partial if it was an AJAX request,
/// or a full page in the master template otherwise)
view_path: &'static str,
/// view context specific to the handled route
view_context: tera::Context,
// TODO: extra fields for error handling, etc.
} If this had an implementation of async fn view_renderer_middleware(request: Request, next: Next) -> Response {
// 1. extract header information from htmx
// (avoid doing this manually in every handler)
let common_context: tera::Context = ctx_from_request(&request);
// 2. run handlers which will return `AppResponse`
let handler_rsp: AppResponse /* or Response<AppResponse> ? */ = next.run(request).await;
// 3. combine generic context with route/view-specific context
let final_context: tera::Context = merge_contexts(common_context, handler_rsp.view_context);
// 4. return a rendered response
// (some trickery in the tera template will use htmx context to decide between partial/full render)
generate_final_response(handler_rsp.view_path, final_context)
} Is it possible to achieve something like this? I've had a look at I'm open to different approaches, but doing it manually in every single handler with an extractor seems inefficient. axum version0.7.5 |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments
-
And to extend this idea a little further, I might want to do something like this for authentication state. Rather than pass that from each route handler directly into a view, it would be nice if every single view could count on that information being present, with a common key, and without having to repeatedly add it. |
Beta Was this translation helpful? Give feedback.
-
Thinking I might be able to use |
Beta Was this translation helpful? Give feedback.
-
This feels kind of hacky, but it's what I have so far: #[derive(Clone)]
pub struct MiddlewarePayload {
some_value: String,
}
// a handler
async fn my_handler() -> impl IntoResponse {
let mut rsp = Response::<String>::new("never gets used".into());
rsp.extensions_mut().insert(MiddlewarePayload { some_value: "passing data to middleware".to_owned() });
rsp
}
// middleware function
pub async fn change_response_later(
request: Request,
next: Next,
) -> Response {
// TODO: extract generic details
// run inner handler (which will add extension)
let response = next.run(request).await;
// If the extension was present, we can use what it contains
if let Some(ext) = response.extensions().get::<MiddlewarePayload>() {
return Response::new(ext.some_value.clone().into());
}
// in case the extension was missing
return response;
}
// in router
let app = Router::new()
.route("/", get(my_handler))
.layer(axum::middleware::from_fn(change_response_later)); This would work, but I don't like the idea of passing through a dummy request when I want to defer the response.
|
Beta Was this translation helpful? Give feedback.
-
I think I've rubber-ducked this one.
✅ still works for values other than |
Beta Was this translation helpful? Give feedback.
I think I've rubber-ducked this one.
Response<AppResponse>
Request
, callNext::run()
, then take thatResponse
and copy the request header data into the response as an extensionimpl IntoResponse for Response<AppResponse>
; between values in extension data and the custom struct I should be able to do what I want and produce aResponse<Body>
.✅ still works for values other than
Response<AppResponse>
✅ don't need to create dummy responses