如何在Minijinja模板中全局获取Axum请求路径(含错误页)
问题
我正在用Axum开发Web应用,想给Minijinja模板里的活跃链接添加属性和样式(比如aria-current),这需要所有模板(包括错误页模板)都能获取到请求路径。
目前普通页面中,我通过OriginalUri提取器将请求路径传入模板上下文;但错误页的情况特殊——我给应用错误类型实现了IntoResponse,这个实现无法访问请求对象。我曾考虑在中间件里更新minijinja::Environment,但它是作为应用状态传入的,属于只读资源。
另外,我没法在IntoResponse里复用应用状态中的环境,所以错误响应是重新创建的环境,这应该不是正确做法。我的AppError定义如下:
enum AppError { UnexpectedError(anyhow::Error), } impl From<anyhow::Error> for AppError { fn from(e: anyhow::Error) -> Self { Self::UnexpectedError(e) } } impl IntoResponse for AppError { fn into_response(self) -> Response { match self { Self::UnexpectedError(_) => { let mut env = Environment::new(); env.set_loader(path_loader("templates")); match env .get_template("500.html.j2") .and_then(|tmpl| tmpl.render(context! {})) { Ok(content) => { (StatusCode::INTERNAL_SERVER_ERROR, Html(content)).into_response() } Err(_) => { (StatusCode::INTERNAL_SERVER_ERROR, "We're experiencing unexpected technical difficulties. Please try again later. Thank you for your patience.").into_response() } } } } } }
解决方案
方法一:将请求路径附加到AppError中
既然IntoResponse本身无法获取请求,那就在错误产生时把请求路径一起存入AppError:
- 修改
AppError定义,新增存储请求路径的字段:
use axum::http::Uri; enum AppError { UnexpectedError(anyhow::Error, Option<Uri>), } // 调整From实现,默认路径为None impl From<anyhow::Error> for AppError { fn from(e: anyhow::Error) -> Self { Self::UnexpectedError(e, None) } } // 提供方法手动附加请求路径 impl AppError { pub fn with_uri(self, uri: Uri) -> Self { match self { Self::UnexpectedError(e, _) => Self::UnexpectedError(e, Some(uri)), } } }
- 在handler中提取路径并附加到错误:
async fn some_handler(uri: OriginalUri) -> Result<Html<String>, AppError> { // 业务逻辑出错时,将路径附加到错误 Err(anyhow::anyhow!("something went wrong").into()).with_uri(uri.0) }
- 修改
IntoResponse实现,将路径传入模板上下文:
impl IntoResponse for AppError { fn into_response(self) -> Response { match self { Self::UnexpectedError(_, uri) => { let mut env = Environment::new(); env.set_loader(path_loader("templates")); let ctx = context! { request_path => uri.map(|u| u.path().to_string()) }; match env .get_template("500.html.j2") .and_then(|tmpl| tmpl.render(ctx)) { Ok(content) => { (StatusCode::INTERNAL_SERVER_ERROR, Html(content)).into_response() } Err(_) => { (StatusCode::INTERNAL_SERVER_ERROR, "我们遇到了意外的技术问题,请稍后重试。感谢您的耐心。").into_response() } } } } } }
方法二:用中间件存路径到请求扩展,通过错误处理层获取
利用Axum的Extensions存储请求临时数据,再通过统一的错误处理层获取路径:
- 编写中间件存储请求路径:
use axum::{ http::Request, middleware::Next, response::Response, }; async fn store_request_path<B>(mut req: Request<B>, next: Next<B>) -> Result<Response, AppError> { let uri = req.uri().clone(); req.extensions_mut().insert(uri); Ok(next.run(req).await) }
- 移除
AppError的IntoResponse实现,改用错误处理层:
use axum::extract::Extension; async fn handle_app_error( err: AppError, Extension(uri): Extension<Uri>, Extension(env): Extension<Environment>, ) -> impl IntoResponse { match err { AppError::UnexpectedError(_) => { let ctx = context! { request_path => uri.path().to_string() }; match env .get_template("500.html.j2") .and_then(|tmpl| tmpl.render(ctx)) { Ok(content) => (StatusCode::INTERNAL_SERVER_ERROR, Html(content)), Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "我们遇到了意外的技术问题,请稍后重试。感谢您的耐心。"), } } } }
- 注册中间件和错误处理层到路由:
use axum::routing::get; use axum::Router; let app = Router::new() .route("/", get(some_handler)) .layer(axum::middleware::from_fn(store_request_path)) .layer(axum::error_handling::HandleErrorLayer::new(handle_app_error)) .with_state(Environment::new().set_loader(path_loader("templates")));
这种方法能复用应用状态中的Environment,无需每次错误都重新创建。
方法三:封装模板渲染工具,自动注入路径
编写统一的模板渲染函数,自动从请求扩展中获取路径并注入上下文:
use minijinja::context; use axum::http::Request; pub async fn render_template( env: &Environment, template_name: &str, req: &Request<()>, extra_ctx: Option<minijinja::Value>, ) -> Result<String, minijinja::Error> { let uri = req.extensions().get::<Uri>().cloned().unwrap_or_default(); let mut ctx = context! { request_path => uri.path().to_string() }; // 合并额外上下文 if let Some(extra) = extra_ctx { ctx.extend(extra); } env.get_template(template_name)?.render(ctx) }
之后不管是普通页面还是错误页,都调用这个函数渲染模板,确保路径被自动注入。
内容的提问来源于stack exchange,提问作者Eva
相关产品推荐
相关产品推荐

