agentleFS
TienDat-VnaTienDat-Vna/BackSpec/.agents/skills/spring-middleware-pipeline/SKILL.md

spring-middleware-pipeline

Skill1 starsChanged 46 days agoMITView on GitHub

Quy chuẩn thiết kế và điều phối chuỗi Middleware trong Spring Boot: Servlet Filter Chain, HandlerInterceptor, Request/Response Body Wrapping, MDC Tracking và phân lập luồng xử lý ngoại vi.

The file

Files
SKILL.md
<!-- GENERATED FROM registry/skills/spring-middleware-pipeline/SKILL.md — DO NOT EDIT DIRECTLY --><!-- GENERATED FROM .shared/skills/02-codestyle/spring-middleware-pipeline/SKILL.md — DO NOT EDIT DIRECTLY -->

Quy Chuẩn Thiết Kế & Vận Hành Chuỗi Middleware (Servlet Filter & Spring Interceptor Pipeline)

Tài liệu này định nghĩa kiến trúc, luồng thực thi và quy chuẩn lập trình chuỗi Middleware (Filter & Interceptor) trong hệ thống Backend Spring Boot (Spring Boot 3, Spring Security 6, Groovy 4 / Java 21).


1. Phân Định Kiến Trúc: Filter vs Interceptor vs ControllerAdvice

Hiểu đúng vị trí và vòng đời của từng thành phần trong chuỗi xử lý HTTP Request:

HTTP Request
     │
     ▼
[Tomcat / Servlet Container]
     │
     ├─► 1. Servlet Filters (OncePerRequestFilter)
     │     ├── HeaderIdentifyFilter (LocaleContextHolder, MDC traceId)
     │     ├── JwtAuthenticationFilter (Verify Bearer Token)
     │     └── DecryptBodyFilter (JWE RSA Decryption + Redis Idempotency)
     │
     ▼
[Spring DispatcherServlet]
     │
     ├─► 2. HandlerInterceptors (preHandle)
     │     └── RateLimitInterceptor (Kiểm tra quota, dynamic routing)
     │
     ▼
[Controller Layer (@RestController)]
     │
     ├─► @Valid RequestBody & Service Execution
     │
     ▼
[ControllerAdvice / APIExceptionHandler] (Xử lý nếu phát sinh ngoại lệ)
     │
     ▼
[HandlerInterceptors (postHandle & afterCompletion)]
     │
     ▼
[Servlet Filters (Sau khi response hoàn tất: Cleanup MDC, Logging)]
     │
     ▼
HTTP Response

Bảng So Sánh Chi Tiết

Đặc TínhServlet Filter (Filter / OncePerRequestFilter)HandlerInterceptor (HandlerInterceptor)
Vị Trí Thực ThiNgoài DispatcherServlet (Tầng Servlet Container).Trong DispatcherServlet (Tầng Spring MVC).
Truy Cập Request BodyTrực tiếp qua HttpServletRequest.getInputStream().Không đọc trực tiếp InputStream (dùng RequestWrapper).
Truy Cập Spring ContextGián tiếp (phải inject Spring Bean cẩn thận).Trực tiếp (có quyền truy cập HandlerMethod).
Nhiệm Vụ Phù HợpGiải mã JWE, Xác thực JWT, Quản lý MDC traceId, Caching Body, CORS, Gzip.Đo lường thời gian thực thi (Metrics), Rate Limit theo Controller method, Phân quyền chi tiết theo Annotation.

2. Mô Hình Chuỗi Filter Chuẩn (Security Filter Pipeline)

flowchart TD
    Req["Incoming HTTP Request"] --> F1["1. HeaderIdentifyFilter\n- Trích xuất x-request-id -> MDC traceId\n- Trích xuất Accept-Language -> LocaleContextHolder"]
    F1 --> F2["2. JwtAuthenticationFilter\n- Kiểm tra Header Authorization (Bearer JWT)\n- Nạp Authentication vào SecurityContextHolder"]
    F2 --> F3["3. DecryptBodyFilter\n- Giải mã JWE RSA-OAEP-256 + A256GCM\n- Ghi nhận Idempotency Lock qua Redis\n- Bọc Request bằng DecryptedBodyHttpServletRequest"]
    F3 --> Ctrl["4. REST Controller"]

3. Kỹ Thuật Request Body Caching & Wrapper (Đọc Body Nhiều Lần)

Vấn đề:

HttpServletRequest.getInputStream() là một One-time Stream. Sau khi Filter đọc giải mã JWE hoặc ghi log, Stream sẽ bị đóng/rỗng, khiến @RequestBody tại Controller bị lỗi HttpMessageNotReadableException.

Giải Pháp: HttpServletRequestWrapper

Bắt buộc bọc dữ liệu đã đọc vào một Wrapper tùy biến và truyền tiếp qua chuỗi filterChain.doFilter(wrapper, response):

class DecryptedBodyHttpServletRequest extends HttpServletRequestWrapper {

    private final byte[] cachedBody

    DecryptedBodyHttpServletRequest(HttpServletRequest request, byte[] decryptedContent) {
        super(request)
        this.cachedBody = decryptedContent ?: new byte[0]
    }

    @Override
    ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody)
        return new CustomServletInputStream(byteArrayInputStream)
    }

    @Override
    BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8))
    }
}

4. Quản Lý MDC Logging & Dọn Dẹp Bộ Nhớ (ThreadLocal Cleanup)

Để tránh rò rỉ bộ nhớ (Memory Leak) và lẫn lộn log giữa các Request trên Thread Pool của Tomcat, MDC Context phải được dọn sạch tại khối finally của Filter đầu tiên:

@Component
class HeaderIdentifyFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        String requestId = request.getHeader("x-request-id") ?: UUID.randomUUID().toString()
        MDC.put("traceId", requestId)
        response.setHeader("x-request-id", requestId)

        try {
            filterChain.doFilter(request, response)
        } finally {
            // Bắt buộc dọn sạch MDC để tránh leak dữ liệu sang request tiếp theo trên cùng thread
            MDC.remove("traceId")
            MDC.clear()
        }
    }
}

5. Xử Lý Ngoại Lệ Tại Tầng Filter (Filter-Level Exception Handling)

Ngoại lệ phát sinh trong Servlet Filter nằm ngoài tầm quét của @RestControllerAdvice. Để xử lý chuẩn, sử dụng HandlerExceptionResolver để chuyển tiếp ngoại lệ về APIExceptionHandler:

@Component
class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        try {
            // Thực hiện xác thực JWT...
            filterChain.doFilter(request, response)
        } catch (Exception e) {
            // Đẩy ngoại lệ về APIExceptionHandler để định dạng response JSON chuẩn
            resolver.resolveException(request, response, null, e)
        }
    }
}

6. Quy Chuẩn Thiết Kế HandlerInterceptor

Khi cần chặn ở tầng Spring MVC (sau khi đã biết chính xác Controller nào sẽ xử lý):

@Component
class ExecutionTimeInterceptor implements HandlerInterceptor {

    private static final String START_TIME_ATTR = "INTERCEPTOR_START_TIME"

    @Override
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        request.setAttribute(START_TIME_ATTR, System.currentTimeMillis())
        return true
    }

    @Override
    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        Long startTime = (Long) request.getAttribute(START_TIME_ATTR)
        if (startTime) {
            long duration = System.currentTimeMillis() - startTime
            if (duration > 1000) {
                // Log cảnh báo API chạy chậm (Slow API Warning)
                log.warn("[SLOW_API] URL: {}, Duration: {} ms", request.requestURI, duration)
            }
        }
    }
}

7. Bảng Kiểm Định An Toàn Cho Middleware (Review Checklist)

STTTiêu Chí Kiểm TraĐạt Chuẩn
1Filter có kế thừa OncePerRequestFilter để tránh thực thi nhiều lần trên 1 request không?ĐẠT
2Nếu Filter đọc InputStream, đã bọc lại vào HttpServletRequestWrapper chưa?ĐẠT
3Khối finally của Filter đầu tiên đã có MDC.clear() để tránh rò rỉ ThreadLocal chưa?ĐẠT
4Ngoại lệ trong Filter có được chuyển tiếp qua HandlerExceptionResolver không?ĐẠT
5Các Filter có được cấu hình thứ tự tường minh qua SecurityFilterChain hoặc @Order không?ĐẠT

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.