2026. 1. 7. 17:44ㆍSpring Microservice/API Gateway
🚦 SCG Filter
SCG의 필터는 한 줄로 요약하면:
ServerWebExchange(요청+응답 컨텍스트)를 받아서, 다음 체인으로 넘기기 전/후에 가공하는 함수형 미들웨어 입니다. 🧩
그리고 WebFlux이므로, “전/후처리”는 동기 try/finally가 아니라 Mono 체인으로 구성됩니다.
1) 🧠 필터 체인 구조: “양파껍질” 모델
SCG 요청 흐름은 대략 이렇게 이해하시면 정확합니다.
요청이 Gateway에 들어옴
Route 매칭(Predicate)
필터 체인 구성
- GlobalFilter들 + 해당 Route의 GatewayFilter들
필터 체인 실행
- Pre는 순서대로 “들어가며” 실행
- Post는 역순으로 “나오며” 실행
실제 라우팅/프록시(Netty routing filter 등)
응답을 다시 필터 체인으로 역류(Post)
클라이언트로 전송
📌 핵심: 필터는 “앞뒤로 감싸는(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 부여)은 앞에 있어야 → 전체 로그가 연결됨
✅ 추천 순서(전형적인 운영 패턴)
- TraceId/RequestId 부여 🧾
- 인증/인가 🔐
- RateLimiter/Bulkhead 🧱
- 라우팅 전 변환(RewritePath 등) 🔁
- 로깅/메트릭 📊
- 응답 후처리(CORS/Headers) 🌐
7) ⚠️ 실무 함정 6가지 (여기서 사고 납니다)
Blocking 호출 금지 ❌
- JDBC, Thread.sleep, 블로킹 RestTemplate… → Netty 이벤트 루프를 잡아먹습니다.
Body는 한 번 읽으면 끝 ⚠️
- “검증하려고 read” 했다가 downstream이 body를 못 읽는 사고
에러 처리 위치 🧯
- 필터에서 예외를 던지면 global error handler로 가는데, 의도한 JSON 에러 포맷이 깨질 수 있습니다.
Order 충돌 🔢
- 생각한 순서로 실행되지 않는 이유 대부분이 Order/우선순위 충돌입니다.
큰 바디를 메모리에 올리기 💥
- ModifyBody를 무심코 쓰면 대용량 요청에서 OOM 위험
헤더/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 |