Filter

2026. 1. 7. 17:44Spring Microservice/API Gateway

🚦 SCG Filter

SCG의 필터는 한 줄로 요약하면:

ServerWebExchange(요청+응답 컨텍스트)를 받아서, 다음 체인으로 넘기기 전/후에 가공하는 함수형 미들웨어 입니다. 🧩

그리고 WebFlux이므로, “전/후처리”는 동기 try/finally가 아니라 Mono 체인으로 구성됩니다.

1) 🧠 필터 체인 구조: “양파껍질” 모델

SCG 요청 흐름은 대략 이렇게 이해하시면 정확합니다.

  1. 요청이 Gateway에 들어옴

  2. Route 매칭(Predicate)

  3. 필터 체인 구성

    • GlobalFilter들 + 해당 Route의 GatewayFilter들
  4. 필터 체인 실행

    • Pre는 순서대로 “들어가며” 실행
    • Post는 역순으로 “나오며” 실행
  5. 실제 라우팅/프록시(Netty routing filter 등)

  6. 응답을 다시 필터 체인으로 역류(Post)

  7. 클라이언트로 전송

📌 핵심: 필터는 “앞뒤로 감싸는(wrap)” 구조라서 Post가 역순입니다.

2) 🧱 GlobalFilter vs GatewayFilter: “언제/어떻게 끼어드나”

🌍 GlobalFilter

  • 모든 요청에 적용
  • 보통 플랫폼 공통 정책(인증, 로깅, 트레이싱, 메트릭)에 사용
  • 구현은 GlobalFilter 인터페이스(또는 GatewayFilter)로 가능

🎯 GatewayFilter (Route Filter)

  • 특정 Route에만 적용
  • YAML에서 filters:로 선언하거나 커스텀 FilterFactory로 제공
  • “특정 서비스만 정책을 다르게” 만들 때 사용

✅ 실무 팁

  • 인증/추적/로깅은 Global로 시작하되
  • 서비스별 예외나 커스터마이징이 필요하면 Route Filter로 분리하는 패턴이 가장 깔끔합니다.

3) 🧬 WebFlux 핵심 객체: ServerWebExchange를 이해해야 필터를 지배합니다

필터에서 만지는 대상은 거의 전부 여기로 귀결됩니다.

📦 ServerWebExchange 안에 뭐가 있나?

  • exchange.getRequest() : ServerHttpRequest (immutable)
  • exchange.getResponse() : ServerHttpResponse (immutable-ish)
  • exchange.getAttributes() : Map (필터 간 데이터 공유)
  • exchange.getPrincipal() : 인증 주체
  • exchange.getSession() : WebSession

⚠️ 중요: Request/Response는 불변(immutable) 설계라서 “수정”은 mutate()로 새 객체를 만들어 교체합니다.

4) ✍️ “요청을 바꾸는 방법” (Headers / Path / Query / Body)

4-1) 헤더 수정 (가장 흔함) 🏷️

ServerHttpRequest mutated = exchange.getRequest().mutate()
    .header("X-Trace-Id", traceId)
    .build();

return chain.filter(exchange.mutate().request(mutated).build());

✅ 포인트

  • request.mutate()로 새 Request 생성
  • exchange.mutate().request(...)로 교체

4-2) Path/URI 수정 (RewritePath의 본질) 🧭

Path rewrite는 결국 요청 URI를 재작성하는 것입니다.

URI newUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
    .replacePath("/internal/v2/abc")
    .build(true)
    .toUri();

📌 중요한 실무 포인트

  • Path만 바꿨는데 라우팅이 꼬이면, encoded 여부(build(true))가 원인인 경우가 많습니다. 😵‍💫

4-3) Body 수정 (난이도 급상승) 💣

여기가 SCG 필터의 “진짜 어려운 부분”입니다.

WebFlux에서 Request Body는 Flux<DataBuffer> 스트림이고, 한 번 읽으면 소모됩니다.
그래서 Body를 읽거나 바꾸려면 보통 다음 중 하나를 씁니다.

  • 내장 필터: ModifyRequestBody, ModifyResponseBody
  • 또는 캐싱 + 재주입(DataBufferUtils) 패턴

✅ 결론만 말씀드리면

  • “바디 검증/변환”이 목적이면 내장 ModifyRequestBody/ResponseBody를 쓰는 게 안전합니다.
  • 직접 구현은 메모리/버퍼/백프레셔 실수로 장애를 부르기 쉽습니다. 😅

5) 🧾 “응답을 바꾸는 방법” (Status / Headers / Body)

5-1) 상태 코드 변경 🚨

exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();

setComplete()는 곧 차단(Short-circuit) 입니다. ⛔
즉, “필터에서 요청을 끊는다”의 정석입니다.

5-2) 응답 헤더 수정 🌐

exchange.getResponse().getHeaders().add("X-GW", "scg");

5-3) 응답 Body 수정 (역시 난이도 높음) 🧨

응답 Body도 스트림이라 “가로채기”가 어렵습니다.
실무에서는 거의 ModifyResponseBody를 사용합니다.

6) ⛓️ Filter Ordering: “정책의 우선순위를 설계하는 기술”

필터의 순서는 단순히 “먼저/나중”이 아니라, 보안·성능·관측의 우선순위입니다.

예를 들어:

  • 인증 필터가 로깅보다 뒤면 → 인증 실패 요청도 불필요하게 로깅/변환
  • RateLimiter가 너무 뒤면 → 이미 리소스를 써버린 뒤 제한
  • Tracing(TraceId 부여)은 앞에 있어야 → 전체 로그가 연결됨

✅ 추천 순서(전형적인 운영 패턴)

  1. TraceId/RequestId 부여 🧾
  2. 인증/인가 🔐
  3. RateLimiter/Bulkhead 🧱
  4. 라우팅 전 변환(RewritePath 등) 🔁
  5. 로깅/메트릭 📊
  6. 응답 후처리(CORS/Headers) 🌐

7) ⚠️ 실무 함정 6가지 (여기서 사고 납니다)

  1. Blocking 호출 금지

    • JDBC, Thread.sleep, 블로킹 RestTemplate… → Netty 이벤트 루프를 잡아먹습니다.
  2. Body는 한 번 읽으면 끝 ⚠️

    • “검증하려고 read” 했다가 downstream이 body를 못 읽는 사고
  3. 에러 처리 위치 🧯

    • 필터에서 예외를 던지면 global error handler로 가는데, 의도한 JSON 에러 포맷이 깨질 수 있습니다.
  4. Order 충돌 🔢

    • 생각한 순서로 실행되지 않는 이유 대부분이 Order/우선순위 충돌입니다.
  5. 큰 바디를 메모리에 올리기 💥

    • ModifyBody를 무심코 쓰면 대용량 요청에서 OOM 위험
  6. 헤더/URI 인코딩 문제 🧩

    • ${seg} 치환, encoded path, query escape 이슈가 자주 발생합니다.

8) 🎯 “필터는 어디에 쓰는 게 가장 가치 있나?” (대표 유스케이스)

  • 🔐 인증/인가(JWT, API Key, mTLS 헤더 등)
  • 📈 Observability(TraceId, structured logging, latency)
  • 🧱 보호장치(RateLimiter, CircuitBreaker, Bulkhead)
  • 🔁 API 계약 변환(외부 URI ↔ 내부 URI, 헤더/쿼리 변환)
  • 🧭 멀티테넌시 라우팅(tenant → 라우팅/헤더 주입)

'Spring Microservice > API Gateway' 카테고리의 다른 글

Spring Cloud Gateway Reactive 라이프사이클  (0) 2026.01.07
Route vs Microservice  (0) 2026.01.07
Predicate  (0) 2026.01.07
Load Balancer 스키마  (0) 2026.01.07
Route  (0) 2026.01.07