Spring을 기반으로 API를 구축하는 데 사용되는 Gateway이며 클라이언트와 서비스 사이에 통신을 관리하는 역할을 한다.
Spring Boot 버전 2.4 이상에서는 Netflix Zuul을 더 이상 사용할 수 없어 이를 대체하기 위해 사용한다.
Spring Cloud Gateway는 크게 Route
, Predicate
, Filter
로 구성되어 있다.
Route는 응답을 보낼 목적지 uri와 필터 항목을 식별하기 위한 ID로 구성되어 있고 라우팅 목적지를 의미한다. 요청된 uri의 조건이 predicate와 일치하는지 확인 후, 일치하는 경우 해당 uri 경로로 요청을 매칭 시켜준다.
API Gateway로 들어온 요청이 주어진 조건을 만족하는지 확인하는 역할을 한다.
하나 이상의 조건을 정의할 수 있으며, 들어온 HTTP 요청이 predicate 조건에 맞지 않는 경우 HTTP 404 코드를 반환한다.
API Gateway로 들어오는 요청에 대해 Filter를 적용하여 선처리 및 후처리를 할 수 있게 해준다.
클라이언트에서 들어온 요청이 Gateway Handler Mapping을 통해 요청 경로와 일치한지 확인하고, Gateway Web Handler에서 요청과 관련된 필터 체인을 통해 요청이 전송된다.
이후 적용되는 필터를 통해 요청 또는 응답에 필요한 전처리, 후처리를 하고, Proxy Filter는 프록시 요청이 처리될 때 수행된다.
아래는 Eureka만 구현된 서비스의 구조이다.
여기에 API Gateway가 추가되면 아래와 같은 구조가 된다.
이를 바탕으로한 클라이언트 요청 처리는 다음과 같다.
server:
port: 12500
spring:
application:
name: API-GATEWAY
cloud:
gateway:
routes:
- id: api
uri: http://localhost:8086
predicates:
- Path=/api/**
filters:
- CustomFilter
default-filters:
- name: GlobalFilter
args:
baseMessage: Spring Cloud Gateway Global Filter
preLogger: true
postLogger: true
eureka:
instance:
prefer-ip-address: true
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:12300/eureka/
application.yml에 Eureka 서버와 API 서비스와 관련된 설정을 추가한다.
@Component
public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config> {
public CustomFilter() {
super(Config.class);
}
public static class Config {}
@Override
public GatewayFilter apply(Config config) {
// Pre Filter
return ((exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
System.out.println("Pre Filter : request id -> " + request.getId());
// Post Filter
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
System.out.println("Post Filter : response code -> " + response.getStatusCode());
}));
});
}
}
@Component
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {
public GlobalFilter() {
super(Config.class);
}
@Data
public static class Config{
private String baseMessage;
private boolean preLogger;
private boolean postLogger;
}
@Override
public GatewayFilter apply(GlobalFilter.Config config) {
// Pre Filter
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
System.out.println("Global Pre baseMessage = " + config.getBaseMessage());
if(config.isPreLogger()) {
System.out.println("Global Pre Start : request id -> " + request.getId());
}
// Post Filter
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
if(config.isPreLogger()) {
System.out.println("Filter End : response code -> " + response.getStatusCode());
}
}));
};
}
}