Mục lục
- Mục Tiêu Bài Học
- tower::Service + tower::Layer Trait Foundation
- Middleware Lifecycle Mỗi Request
- axum::middleware::from_fn — Function-Style Middleware
- Custom Layer — Reusable Production Middleware
- Middleware Ordering — Bottom-Up Trong axum
- 3 Loại Middleware Scope
- Shop API Middleware Stack — Roadmap Group 8 Đầy Đủ
- Tổng Kết
- Bài Tập Củng Cố
- Bài Tiếp Theo
Mục Tiêu Bài Học
Sau bài học, bạn sẽ:
- Hiểu
tower::Servicetrait vàtower::Layertrait — foundation cross-framework Rust. - Phân biệt
axum::middleware::from_fnvs customLayer— khi nào dùng nào. - Hiểu middleware ordering bottom-up trong axum và pitfall thường gặp.
- Áp dụng pattern 3 loại scope middleware: per-request global, per-route, per-state.
- Hiểu Shop API middleware stack hiện tại 4 layer đã wire trong
router.rs. - Nắm roadmap Group 8 (B76-B85) — 10 bài middleware production-grade phía trước.
tower::Service + tower::Layer Trait Foundation
axum xây dựng trên 2 trait nền tảng của crate tower: Service mô tả "đơn vị xử lý" 1 request thành 1 response, Layer mô tả "factory" wrap 1 service thành 1 service mới (middleware). Hiểu 2 trait này là điều kiện tiên quyết để đọc code axum sâu cũng như reuse middleware từ ecosystem (tower-http, tower-governor, axum-extra).
tower::Service<Request> là trait core:
// Trait core từ crate tower
pub trait Service<Request> {
type Response;
type Error;
type Future: Future<Output = Result<Self::Response, Self::Error>>;
// poll_ready: hỏi service đã sẵn sàng nhận request chưa
// (backpressure, capacity limit, circuit breaker)
fn poll_ready(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>>;
// call: xử lý request, trả Future tạo Response
fn call(&mut self, req: Request) -> Self::Future;
}
Mỗi handler axum thực chất là 1 Service ngầm: function async fn handler(...) -> impl IntoResponse được axum bọc thành struct impl Service<Request> tự động. Middleware là 1 service khác wrap quanh service handler để thêm hành vi (log, validate, transform).
tower::Layer<S> là "constructor" tạo wrapper service từ inner service:
// Layer trait: factory tạo wrapper service
pub trait Layer<S> {
type Service;
fn layer(&self, service: S) -> Self::Service;
}
Composition: layer.layer(handler) trả 1 service mới wrap handler bên trong. Wrap nhiều lần tạo chain: layer_a.layer(layer_b.layer(layer_c.layer(handler))) → request đi qua a → b → c → handler → c → b → a → response. Đây là kiến trúc "hành tây" (onion model) chuẩn middleware.
Điểm mạnh của tower là cross-framework Rust: cùng 1 layer (vd TraceLayer, TimeoutLayer, RateLimitLayer) chạy được trên axum (HTTP), tonic (gRPC), hyper trực tiếp. Pattern viết middleware 1 lần dùng nhiều stack là lợi thế lớn so với hệ sinh thái Express/Koa Node.js hoặc Rails Rack.
Middleware Lifecycle Mỗi Request
Mỗi request đi qua middleware stack theo workflow đối xứng — bước xuống (pre-process) rồi bước lên (post-process):
Client request →
Layer 1 (outermost) — pre-process
Layer 2 — pre-process
Layer 3 (innermost) — pre-process
Handler — business logic
Layer 3 — post-process
Layer 2 — post-process
Layer 1 — post-process
→ Client response
Pre-process diễn ra trước khi gọi next.run(req): đọc header để extract context (auth token, request id), modify request (inject extension, decode body), validate sớm và reject (rate limit, body size). Post-process diễn ra sau khi handler chạy xong và trả response: modify response (set header, compress body, wrap envelope), log + metric (status code, latency), enrich error theo format thống nhất.
Ordering CRITICAL: layer outer bao quanh layer inner. Vd compression đặt outermost nghĩa là compression chỉ thấy response cuối cùng sau khi tất cả layer khác đã hoàn tất modify → compress đúng nội dung cuối. Đặt sai thứ tự dẫn đến bug subtle: compression chạy trước khi enrich_error wrap envelope → response client nhận thiếu field request_id.
Pattern lock Shop API tiếp tục từ B50:
Shop API request lifecycle (target Group 8 hoàn thành):
Layer 1: compression ← outermost (wrap response cuối)
Layer 2: decompression ← decompress request body
Layer 3: request_id ← inject Extension<RequestId>
Layer 4: enrich_error ← đọc RequestId từ Extension
Layer 5: rate_limit (B78) ← reject trước khi đến handler
Layer 6: cors (B77) ← header check
Handler (innermost) ← business logic
Mỗi layer có trách nhiệm đơn lẻ rõ ràng (Single Responsibility Principle) và độc lập, có thể thay thế hoặc bỏ ra mà không ảnh hưởng layer còn lại.
axum::middleware::from_fn — Function-Style Middleware
axum::middleware::from_fn là cách đơn giản nhất viết middleware — convert 1 async function thành Layer + Service đầy đủ qua macro magic. Đây là pattern chiếm 90% case Shop API (request_id B39, enrich_error B39, Idempotency B66 đều dùng from_fn).
// Pattern from_fn: async function trả Response
use axum::{
extract::Request,
middleware::{self, Next},
response::Response,
routing::get,
Router,
};
async fn timing_middleware(req: Request, next: Next) -> Response {
// Pre-process
let start = std::time::Instant::now();
let path = req.uri().path().to_string();
// Gọi handler (hoặc layer inner kế tiếp)
let response = next.run(req).await;
// Post-process
tracing::info!(
path = %path,
elapsed_ms = start.elapsed().as_millis(),
status = response.status().as_u16(),
"request handled"
);
response
}
// Wire vào router
let app: Router = Router::new()
.route("/health", get(|| async { "ok" }))
.layer(middleware::from_fn(timing_middleware));
Pros:
- Quick + readable — 1 async function thay vì 2 struct + 3 impl block.
- Stateless — không cần định nghĩa struct giữ field, chỉ logic thuần.
- Common pattern Shop API — 90% middleware nhỏ vừa fit đủ.
Cons:
- Khó share state — phải đổi sang
from_fn_with_stateđể passAppState. - Generic constraint phức tạp khi nested middleware bọc nhau nhiều tầng.
Biến thể quan trọng:
// Variant 1: from_fn_with_state — pass AppState vào middleware
use axum::extract::State;
async fn auth_middleware(
State(state): State<AppState>,
req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let token = req
.headers()
.get("authorization")
.and_then(|v| v.to_str().ok())
.ok_or(StatusCode::UNAUTHORIZED)?;
let _claims = verify_jwt(token, &state.jwt_secret)?;
Ok(next.run(req).await)
}
let app = Router::new()
.route("/me", get(get_me_handler))
.layer(middleware::from_fn_with_state(
state.clone(),
auth_middleware,
))
.with_state(state);
// Variant 2: from_extractor — chỉ extract, KHÔNG process response
let app = Router::new()
.route("/admin", get(admin_handler))
.layer(middleware::from_extractor::<RequireAdmin>());
Lock decision Shop API: dùng from_fn cho 90% case middleware đơn giản; nâng cấp lên from_fn_with_state khi cần access AppState (DB pool, Redis, config).
Custom Layer — Reusable Production Middleware
Khi middleware cần reusable cross-project (publish crate), stateful phức tạp hơn (config field, internal cache), hoặc tương thích đầy đủ với hệ sinh thái tower (hyper, tonic), pattern custom Layer là lựa chọn chuẩn. Phải define struct riêng + impl Layer + impl Service.
// Skeleton custom Layer + Service
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use tower::{Layer, Service};
use axum::{
extract::Request,
response::Response,
};
// 1. Layer struct — config field nếu có
#[derive(Clone)]
pub struct LoggingLayer {
pub component: &'static str,
}
// 2. Impl Layer — tạo wrapper service khi axum wire
impl<S> Layer<S> for LoggingLayer {
type Service = LoggingMiddleware<S>;
fn layer(&self, inner: S) -> Self::Service {
LoggingMiddleware {
inner,
component: self.component,
}
}
}
// 3. Wrapper service struct
#[derive(Clone)]
pub struct LoggingMiddleware<S> {
inner: S,
component: &'static str,
}
// 4. Impl Service cho wrapper
impl<S> Service<Request> for LoggingMiddleware<S>
where
S: Service<Request, Response = Response> + Clone + Send + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
// Clone inner trước khi async block — pattern lock cho tower service
let mut inner = self.inner.clone();
let component = self.component;
let start = std::time::Instant::now();
let path = req.uri().path().to_string();
Box::pin(async move {
let response = inner.call(req).await?;
tracing::info!(
component,
path = %path,
elapsed_ms = start.elapsed().as_millis(),
"request completed"
);
Ok(response)
})
}
}
Pros:
- Reusable cross-project — publish thành crate (tower-http, tower-governor là ví dụ).
- Stateful dễ — struct field giữ config + internal state (cache, counter, channel).
- Generic constraint kiểm soát tốt hơn
from_fntrong case phức tạp. - Tower ecosystem compatible — chạy được trên hyper, tonic không sửa code.
Cons:
- Boilerplate nhiều — 4 phần (Layer struct, impl Layer, Service struct, impl Service).
- Bound generic phức tạp — phải nắm trait
Send + Sync + 'static+Futurebound.
Lock decision Shop API: custom Layer chỉ áp dụng khi cần reusable hoặc stateful phức tạp; 90% còn lại vẫn ưu tiên from_fn cho tốc độ phát triển. Hầu hết middleware Shop API đến B85 sẽ wrap built-in từ tower-http (đã sẵn pattern custom Layer chuẩn) thay vì viết tay.
Middleware Ordering — Bottom-Up Trong axum
Quy tắc quan trọng nhất: trong axum, thứ tự gọi .layer() đảo ngược thứ tự execution. Layer thêm SAU cùng wrap layer thêm TRƯỚC.
// Đọc code dưới này:
Router::new()
.route("/", handler)
.layer(layer_inner) // chạy thứ 3 (innermost, gần handler)
.layer(layer_middle) // chạy thứ 2
.layer(layer_outer); // chạy thứ 1 (outermost, gần network)
// Execution order:
// request → layer_outer (pre) → layer_middle (pre) → layer_inner (pre)
// → handler
// → layer_inner (post) → layer_middle (post) → layer_outer (post)
// → response
Pitfall thường gặp: developer đến từ Express.js (Node) hoặc Koa nghĩ middleware chạy theo thứ tự khai báo top-down (layer 1 đầu file chạy trước layer 2 sau file). Trong axum thì ngược lại — đọc code từ dưới lên. Hệ quả: developer wire auth trước cors nghĩ rằng auth chạy trước, thực tế cors chạy trước → preflight OPTIONS request bị reject vì chưa qua cors handler.
Shop API stack hiện tại trong crates/shop-api/src/router.rs (sau B50):
// File: crates/shop-api/src/router.rs (snippet sau B50)
use axum::{middleware, Router};
use tower_http::{
compression::{CompressionLayer, CompressionLevel},
decompression::DecompressionLayer,
};
use crate::{
middleware::{enrich_error_response, request_id_middleware},
state::AppState,
};
pub fn build_router(state: AppState) -> Router {
Router::new()
// ... routes merge từ products, orders, cart, users, brands, categories
.merge(crate::routes::products::routes())
.merge(crate::routes::orders::routes())
.merge(crate::routes::cart::routes())
// ... 26+ endpoint khác
//
// Middleware stack — đọc TỪ DƯỚI LÊN để hiểu execution order:
.layer(middleware::from_fn(enrich_error_response)) // INNER 4 — wrap error envelope
.layer(middleware::from_fn(request_id_middleware)) // INNER 3 — inject Extension<RequestId>
.layer(DecompressionLayer::new().gzip(true).br(true)) // INNER 2 — decompress body
.layer( // OUTERMOST 1 — compress response
CompressionLayer::new()
.gzip(true)
.br(true)
.quality(CompressionLevel::Default),
)
.with_state(state)
}
Pattern lock vĩnh viễn: comment thứ tự execution INNER/OUTERMOST trên mỗi .layer() để team đọc code không phải tự suy luận. Comment dạng "INNER 3", "INNER 4", "OUTERMOST 1" rõ ràng hơn comment "auth layer" thuần (không nói rõ vị trí trong stack).
3 Loại Middleware Scope
axum cho phép áp middleware ở 3 scope khác nhau tùy use case:
Decision matrix — chọn scope middleware
Scope | Áp dụng cho | API axum | Vd Shop API
--------------+-------------------+--------------------------------+--------------------------
Per-request | Mọi route | Router::new()...layer(L) | request_id, compression,
(global) | | | trace, cors
--------------+-------------------+--------------------------------+--------------------------
Per-route | 1-vài route | route().layer(L) hoặc | idempotency (B66),
| | route_layer(L) cho path con | body_limit /import (B47)
--------------+-------------------+--------------------------------+--------------------------
Per-state | Mọi route cần | from_fn_with_state(state, fn) | rate_limit (B78),
| access AppState | | auth (B112)
Per-request (global): áp dụng mọi request đi qua router. Wire ở router root sau khi merge tất cả route con.
// Pattern global — wire ở router root
Router::new()
.merge(routes::products::routes())
.merge(routes::orders::routes())
.layer(middleware::from_fn(request_id_middleware));
// Mọi route products + orders đều có request_id
Per-route: áp dụng 1 vài route cụ thể (không phải toàn bộ stack). Dùng cho middleware nặng/không phù hợp mọi endpoint: Idempotency-Key chỉ POST /orders, body limit 10MB chỉ /import NDJSON.
// Pattern per-route — wire ở route cụ thể (B66 Idempotency)
use axum::extract::DefaultBodyLimit;
let import_route = post(import_products_ndjson)
.layer(DefaultBodyLimit::max(10 * 1024 * 1024)); // 10MB cho NDJSON import
let orders_routes = Router::new()
.route(
"/orders",
post(create_order).layer(middleware::from_fn_with_state(
state.clone(),
idempotency_middleware, // B66 chỉ POST /orders
)),
)
.route("/products/import.ndjson", import_route);
Per-state: middleware cần access AppState (DB pool cho rate limit query Redis, JWT secret cho auth). Dùng from_fn_with_state truyền state qua signature.
// Pattern per-state — rate_limit query Redis qua AppState (B78 preview)
async fn rate_limit_middleware(
State(state): State<AppState>,
req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let ip = req.headers().get("x-forwarded-for")
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
let key = format!("rate:{ip}");
let count: i64 = state.redis.incr(&key, 1).await.unwrap_or(0);
if count == 1 { state.redis.expire(&key, 60).await.ok(); }
if count > 100 { return Err(StatusCode::TOO_MANY_REQUESTS); }
Ok(next.run(req).await)
}
Lock pattern Shop API:
- Global: tracing, request_id, compression — mọi request đều cần.
- Per-route: Idempotency (B66 POST mutation), body limit (B47 /import).
- Per-state: rate limit (B78 Redis pool), auth (B112 JWT secret).
Shop API Middleware Stack — Roadmap Group 8 Đầy Đủ
Sau Group 4 + Group 5 + Group 7, Shop API đã có 4 layer global + 1 layer per-route:
- request_id (B39) — inject
Extension<RequestId>mọi request, log + trả headerX-Request-Id. - enrich_error_response (B39) — đọc
RequestIdtừ Extension, inject vào envelope error JSON. - CompressionLayer (B50) — gzip + brotli response > 512 byte (skip image/zip/gzip content type).
- DecompressionLayer (B50) — gzip + brotli decompress request body.
- Idempotency middleware (B66, per-route POST /orders) — cache replay theo header
Idempotency-Key.
Group 8 (B76-B85) sẽ thêm 10 layer mới phủ hết các nhu cầu middleware production-grade:
- B77 cors_layer —
tower-http CorsLayer(allow_origin, allow_methods, allow_headers, max_age). - B77 security_headers_layer — X-Frame-Options, X-Content-Type-Options, Content-Security-Policy, Referrer-Policy.
- B78 rate_limit_layer — per-IP token bucket với crate
governor(in-memory) hoặc Redis distributed. - B79 body_limit_per_route — refactor B47 thành pattern reusable (mỗi route khai báo limit riêng).
- B80 trace_layer —
tower-http TraceLayer+ structured log span (method, uri, status, latency). - B81 metrics_layer — Prometheus middleware mở rộng
/metricsendpoint từ B57 (histogram latency per-endpoint, counter status code). - B82 timeout_layer — per-route request timeout (vd checkout 30s, NDJSON export 300s).
- B83 request_validation — input sanitize (strip HTML, normalize unicode) trước khi handler nhận.
- B84 fallback handler middleware — 404 customization (envelope chuẩn, log unknown path).
- B85 middleware testing — integration test middleware stack qua
tower::ServiceExt::oneshot(pattern B75 mở rộng).
Lock stack thứ tự sau Group 8 hoàn thành:
Final stack — 11 layer production-grade (sau Group 8 B85):
1. compression ← outermost (compress response cuối)
2. decompression ← decompress request body
3. trace_layer (B80) ← log span đầu vào (catch nhiều thông tin nhất)
4. metrics_layer (B81) ← đếm + đo latency tất cả request
5. rate_limit (B78) ← reject sớm trước khi tốn CPU layer khác
6. cors (B77) ← preflight OPTIONS handle
7. security_headers (B77) ← inject header phòng thủ vào response
8. request_id (B39) ← inject Extension trace request
9. enrich_error (B39) ← wrap error envelope với request_id
10. timeout per-route (B82) ← timeout business-logic level
11. idempotency per-route (B66) ← cache replay POST mutation
Handler (innermost) ← business logic + DB
File path lock vĩnh viễn: extend thư mục crates/shop-api/src/middleware/ cho mỗi layer mới — 1 file riêng (cors.rs, security_headers.rs, rate_limit.rs, trace.rs, metrics.rs, timeout.rs, validation.rs, fallback.rs) thay vì gộp 1 file middleware/mod.rs phình to khó maintain.
Tổng Kết
tower::Service+tower::Layertrait foundation cross-framework Rust (axum + hyper + tonic).- Middleware lifecycle: pre-process → handler → post-process — mô hình hành tây bao quanh handler.
- Ordering bottom-up axum: layer thêm SAU = wrap layer thêm TRƯỚC; đọc code từ dưới lên.
from_fnvs custom Layer: function quick (90% case), Layer reusable + stateful (10% case).- 3 loại scope middleware: per-request global, per-route, per-state.
- Pattern lock Shop API:
from_fncho đơn giản, custom Layer cho reusable cross-project. - Shop API stack hiện tại 4 layer: request_id + enrich_error + compression + decompression (+ 1 per-route Idempotency).
- Roadmap Group 8 10 layer mới: cors + security + rate-limit + body-limit + trace + metrics + timeout + validation + fallback + test.
- Final stack 11 layer production-grade sau Group 8 hoàn thành.
- Comment thứ tự execution INNER/OUTERMOST trên mỗi
.layer()tránh confuse cho team. - File path lock: extend
crates/shop-api/src/middleware/cho mỗi layer mới — 1 file riêng (cors.rs, security_headers.rs, rate_limit.rs, …).
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
tower::Servicevstower::Layer— phân biệt vai trò mỗi trait. Layer là gì so với Service?- Middleware ordering bottom-up axum — pitfall thường gặp khi developer nghĩ top-down. Cho ví dụ scenario reverse order ảnh hưởng behavior.
axum::middleware::from_fnvs custom Layer — khi nào chọn cái nào? 90/10 rule Shop API.- 3 loại scope middleware — per-request global, per-route, per-state. Cho ví dụ Shop API mỗi loại.
- 11 layer stack production — sequence từ outermost → innermost ra sao? Tại sao compression OUTERMOST?
Đáp án
tower::Service<Request>mô tả "đơn vị xử lý" — nhận 1Request, trảFuture<Output = Result<Response, Error>>. Có 2 method bắt buộc:poll_ready(hỏi service đã sẵn sàng nhận thêm request chưa — backpressure, capacity limit, circuit breaker) +call(xử lý request thực sự, trả Future). Mỗi axum handler là 1Servicengầm được axum bọc tự động.tower::Layer<S>mô tả "factory" tạo wrapper service — nhận 1 inner serviceS, trả 1 service mới wrap quanh nó (thường thêm pre-process + post-process logic). Có 1 methodfn layer(&self, inner: S) -> Self::Service. Quan hệ:Layerlà constructor/builder pattern tạo raServicewrapper;Servicelà đơn vị runtime thực sự xử lý request. Compositionlayer_a.layer(layer_b.layer(handler))tạo chain service nhiều tầng (onion model). Cross-framework: cùng 1Layer(vdTimeoutLayer,TraceLayer) chạy được trên axum (HTTP) + tonic (gRPC) + hyper trực tiếp vì cả 3 đều dựa trên traittower::Servicechuẩn. Pattern viết middleware 1 lần dùng nhiều stack là lợi thế lớn ecosystem Rust.- Pitfall ordering: developer đến từ Express.js/Koa nghĩ middleware chạy theo thứ tự khai báo top-down (layer 1 viết trước chạy trước). Trong axum thì ngược lại — đọc code TỪ DƯỚI LÊN, layer
.layer()thêm SAU CÙNG wrap (chạy SỚM NHẤT) layer thêm TRƯỚC. Scenario reverse order ảnh hưởng behavior: (a) Auth + CORS reversed — developer wireauthtrướccorsnghĩ auth chạy trước, thực tế cors chạy trước → preflight OPTIONS request bypass auth thành công (đúng spec CORS) nhưng nếu order đảo (cors inner, auth outer) → OPTIONS bị reject vì chưa qua auth → browser CORS preflight fail → toàn bộ frontend không call được API. (b) Rate limit + Auth reversed — wire rate_limit trước auth nghĩ rate trước, thực tế auth chạy trước → mọi request unauthorized cũng tốn DB lookup auth trước khi reject → DoS dễ qua bypass rate limit; pattern đúng là rate_limit OUTER (reject sớm, ít CPU cost) + auth INNER. (c) Compression + enrich_error reversed — compression INNER (chạy sau enrich_error) → response client nhận thiếu fieldrequest_idvì compression encode trước khi enrich_error wrap envelope. Best practice: comment thứ tự execution INNER/OUTERMOST trên mỗi.layer()để team đọc code không phải suy luận; viết test integration `tests/middleware_order_test.rs` verify expected behavior (B85). - 90/10 rule Shop API. Dùng
from_fn(90% case) khi: (a) middleware logic đơn giản < 30 dòng, không cần struct field; (b) stateless hoặc state nhỏ quafrom_fn_with_state(AppState); (c) không cần publish cross-project; (d) team prefer tốc độ phát triển trên reusability. Ví dụ Shop API: request_id (B39), enrich_error (B39), Idempotency middleware logic (B66 — quafrom_fn_with_state), preview auth handler future (B112). Dùng custom Layer + Service (10% case) khi: (a) cần reusable cross-project (publish thành crate riêng); (b) stateful phức tạp (internal cache, counter, channel, background task); (c) generic constraint phức tạpfrom_fnkhông express được; (d) cần tương thích đầy đủ ecosystem tower (hyper, tonic, không bị lock vào axum). Ví dụ Shop API: tất cả layer từtower-http(CompressionLayer B50, DecompressionLayer B50, TraceLayer B80, CorsLayer B77) đã sẵn pattern custom Layer chuẩn → Shop API chỉ wire không viết lại; future shop-middleware crate publish lên crates.io nếu pattern Shop API generic (rate limit Redis distributed, idempotency key middleware). Decision matrix: thửfrom_fntrước (default 90%); chỉ refactor sang custom Layer khi cảm thấy generic constraint khó hoặc cần state phức tạp. Pattern lock vĩnh viễn: ưu tiên đơn giản, không "premature optimization". - 3 scope. (a) Per-request (global): áp dụng mọi request đi qua router root.
Router::new()...routes...layer(L). Vd Shop API:request_id(B39 — mọi request cần trace ID),compression+decompression(B50 — mọi response compress, mọi request body decompress),cors(B77 — mọi cross-origin request),tracing(B80 — log span tất cả). (b) Per-route: áp dụng 1 hoặc vài route cụ thể không phải toàn bộ.route().layer(L)trên 1 route cụ thể hoặcRouter::new()...nest("/admin", admin_routes.layer(L))cho prefix. Vd Shop API:Idempotency(B66 — chỉ POST /orders + POST /api/v1/cart/checkout vì idempotency key vô nghĩa với GET),body_limit per-route(B47 — 10MB cho /products/import.ndjson, default 1MB cho REST khác),timeout per-route(B82 — checkout 30s, NDJSON export 300s). (c) Per-state: middleware cần accessAppState(DB pool, Redis pool, config secret).middleware::from_fn_with_state(state.clone(), my_fn). Vd Shop API:rate_limit(B78 — cần Redis pool query/increment counter per-IP),auth JWT(B112 — cần config secret HS256 verify token),session(B106 — cần Redis pool query session ID),auth role check(B132 — cần DB pool query user role mapping). Pattern lock: chọn scope nhỏ nhất phù hợp — global cho middleware ai cũng cần, per-route cho middleware tốn kém hoặc semantic không phù hợp mọi endpoint, per-state khi cần AppState dependency. - Final stack 11 layer Shop API sau Group 8 (outermost → innermost): 1. compression (outermost) — wrap response cuối sau khi tất cả layer khác modify xong; 2. decompression — decompress request body trước khi layer khác đọc; 3. trace_layer B80 — log span sớm để catch tất cả thông tin request kể cả khi bị reject ở layer dưới; 4. metrics_layer B81 — đếm + đo latency tất cả request kể cả reject; 5. rate_limit B78 — reject sớm trước khi tốn CPU layer khác (cors check, auth verify); 6. cors B77 — preflight OPTIONS handle, allow_origin filter; 7. security_headers B77 — inject X-Frame-Options/CSP/X-Content-Type-Options vào response; 8. request_id B39 — inject
Extension<RequestId>dùng trong handler + log; 9. enrich_error B39 — đọcRequestIdtừ Extension, wrap envelope error JSON; 10. timeout per-route B82 — business-logic level timeout per endpoint; 11. idempotency per-route B66 — cache replay POST mutation; Handler innermost — business logic + DB query + service call. Tại sao compression OUTERMOST: (a) compress response cuối cùng — phải đợi tất cả layer khác modify xong (enrich_error wrap envelope, security_headers inject) rồi mới compress nội dung cuối → tránh recompress hoặc compress lỗi nội dung trung gian; (b) output gần network nhất — compression làm việc với byte stream gửi qua TCP, phải là layer cuối trước khi axum::serve gửi response; (c) negotiation header — compression đọcAccept-Encodingrequest đầu vào, phải có cơ hội đọc trước khi response build xong; tower-httpCompressionLayerimpl đúng pattern này; (d) nếu đặt INNER (gần handler) — compression chạy trước enrich_error → response client nhận thiếu fieldrequest_id(đã bị compress thành byte) hoặc enrich_error fail vì không decompress được response đã compress của handler. Reference: tower::Layer composition docs; axum middleware best practice.
Bài Tiếp Theo
Bài 77: CORS + Security Headers — chi tiết tower-http CorsLayer (allow_origin, allow_methods, allow_headers, max_age), security headers (X-Frame-Options, Content-Security-Policy, X-Content-Type-Options, Referrer-Policy), pattern same-site cookie SameSite=Strict, áp Shop API global stack.
