首先,让我们摆脱没有内置机制来支持影响响应的Accept
头的问题.请参见this brief discussion.
您可能已经注意到,处理程序必须提供的IntoResponse
特征并不提供对原始请求的访问:
pub trait IntoResponse {
fn into_response(self) -> Response<UnsyncBoxBody<Bytes, Error>>;
}
因此,您必须从处理程序本身或某个中间件获取标头.
更糟糕的是,它要求主体是一个字节序列(这是有意义的,但会丢失类型信息).因此,中间件必须对编码的响应正文中的数据进行解码,以针对不同的MIME类型对其进行修改.如果您的数据真的很简单,这可能会起作用,但在许多情况下,这可能会是一种恼人的、不必要的成本.这种数据流还会影响底层的tower
个服务如何影响响应,因此这种区别无济于事.
幸运的是,还有另一种方法:您可以通过Extension
向您的中间件提供任何数据.通过响应上的.extensions()
/.extensions_mut()
,您可以存储任意数据,因此您的IntoResponse
实现可以将自身存储在一个扩展中,然后中间件可以取出该扩展并根据需要重新格式化.
以下是它的工作原理的一个模型:
use axum::http::header::ACCEPT;
use axum::http::{Request, StatusCode};
use axum::middleware::{from_fn, Next};
use axum::response::{Html, IntoResponse, Json, Response};
use axum::{routing::get, Router};
use serde::Serialize;
#[derive(Serialize)]
struct MyData {
data: String,
}
impl IntoResponse for MyData {
fn into_response(self) -> Response {
let mut response = StatusCode::NOT_IMPLEMENTED.into_response();
response.extensions_mut().insert(self);
response
}
}
async fn get_accept_aware() -> MyData {
MyData {
data: "test".to_owned(),
}
}
async fn my_data_applicator<B>(request: Request<B>, next: Next<B>) -> Response {
let accept_header = request
.headers()
.get(&ACCEPT)
.map(|value| value.as_ref().to_owned());
let mut response = next.run(request).await;
if let Some(my_data) = response.extensions_mut().remove::<MyData>() {
match accept_header.as_deref() {
Some(b"application/json") => return Json(my_data).into_response(),
Some(b"html") => return Html(format!("<body>{}</body>", my_data.data)).into_response(),
_ => { /* yield original 501 response */ }
}
}
response
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(get_accept_aware))
.layer(from_fn(my_data_applicator));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}