diff --git a/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md b/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md index 922dbf70..cf63bc89 100644 --- a/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md +++ b/backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md @@ -102,7 +102,8 @@ 交付物:[../server-rs/apps/api-server/src/http_error.rs](../server-rs/apps/api-server/src/http_error.rs)、[../server-rs/apps/api-server/src/error_middleware.rs](../server-rs/apps/api-server/src/error_middleware.rs)、[../server-rs/apps/api-server/src/app.rs](../server-rs/apps/api-server/src/app.rs) - [x] 接入当前项目兼容的 response envelope 交付物:[../server-rs/apps/api-server/src/api_response.rs](../server-rs/apps/api-server/src/api_response.rs)、[../server-rs/apps/api-server/src/request_context.rs](../server-rs/apps/api-server/src/request_context.rs)、[../server-rs/apps/api-server/src/http_error.rs](../server-rs/apps/api-server/src/http_error.rs) -- [ ] 接入 `x-request-id` +- [x] 接入 `x-request-id` + 交付物:[../server-rs/apps/api-server/src/response_headers.rs](../server-rs/apps/api-server/src/response_headers.rs)、[../server-rs/apps/api-server/src/app.rs](../server-rs/apps/api-server/src/app.rs) - [ ] 接入 `x-api-version` - [ ] 接入 `x-route-version` - [ ] 接入 `x-response-time-ms` diff --git a/server-rs/apps/api-server/README.md b/server-rs/apps/api-server/README.md index 3c70c48b..0891e816 100644 --- a/server-rs/apps/api-server/README.md +++ b/server-rs/apps/api-server/README.md @@ -43,7 +43,7 @@ 1. 中间件优先读取来访 `x-request-id`,未提供时生成新的 UUID。 2. `request_id` 会统一写入请求 `extensions` 与请求头,供 tracing、错误处理中间件和响应头层复用。 -3. 响应头回写 `x-request-id` 仍属于后续独立任务,本阶段只完成请求上下文准备。 +3. 最终响应会回写同一个 `x-request-id`,保证调用方、日志链路和后续 envelope `meta.requestId` 可对齐。 当前错误处理中间件约定: diff --git a/server-rs/apps/api-server/src/app.rs b/server-rs/apps/api-server/src/app.rs index 8abffd7e..e1f8dbc8 100644 --- a/server-rs/apps/api-server/src/app.rs +++ b/server-rs/apps/api-server/src/app.rs @@ -5,6 +5,7 @@ use tracing::{info_span, Level}; use crate::{ error_middleware::normalize_error_response, request_context::{attach_request_context, resolve_request_id}, + response_headers::propagate_request_id_header, state::AppState, }; @@ -13,6 +14,8 @@ pub fn build_router(state: AppState) -> Router { Router::new() // 错误归一化层放在 tracing 里侧,让 tracing 记录到最终对外返回的状态与错误体形态。 .layer(middleware::from_fn(normalize_error_response)) + // 响应头回写放在错误归一化外侧,确保最终写回的是归一化后的最终响应。 + .layer(middleware::from_fn(propagate_request_id_header)) // 当前阶段先统一挂接 HTTP tracing,后续 request_id、响应头与错误中间件继续在这里扩展。 .layer( TraceLayer::new_for_http() diff --git a/server-rs/apps/api-server/src/main.rs b/server-rs/apps/api-server/src/main.rs index ba69c81e..6f840e8c 100644 --- a/server-rs/apps/api-server/src/main.rs +++ b/server-rs/apps/api-server/src/main.rs @@ -5,6 +5,7 @@ mod error_middleware; mod http_error; mod logging; mod request_context; +mod response_headers; mod state; use tokio::net::TcpListener; diff --git a/server-rs/apps/api-server/src/response_headers.rs b/server-rs/apps/api-server/src/response_headers.rs new file mode 100644 index 00000000..ca95909b --- /dev/null +++ b/server-rs/apps/api-server/src/response_headers.rs @@ -0,0 +1,23 @@ +use axum::{ + extract::Request, + http::{header::HeaderName, HeaderValue}, + middleware::Next, + response::Response, +}; + +use crate::request_context::{resolve_request_id, X_REQUEST_ID_HEADER}; + +pub async fn propagate_request_id_header(request: Request, next: Next) -> Response { + let request_id = resolve_request_id(&request); + let mut response = next.run(request).await; + + if let Some(request_id) = request_id { + if let Ok(header_value) = HeaderValue::from_str(&request_id) { + response + .headers_mut() + .insert(HeaderName::from_static(X_REQUEST_ID_HEADER), header_value); + } + } + + response +}