백엔드 Backend/서버 Server

API 게이트웨이 서비스 - (2) Spring Cloud Gateway

달래dallae 2025. 1. 6. 14:25

기존의 Netfilx Zuul의 단점 : 비동기/Functional 지원 X 또는 스프링 라이브러리와 호환성 문제

 

Spring Cloud Gateway

- 비동기 서비스 지원

- 서블릿 X -> 기존에 사용하던 ServletRequest/Response 쓰지 않고 ServerRequest/Response 사용

 

 

1. Spring Cloud Gateway 서버 구성하기

application.yml

server:
  port: 8080

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http:localhost:8761/eureka


spring:
  application:
    name: apigateway-service

  cloud:
    gateway:
      routes:
        - id: first-service
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**

 

서버를 띄우면 다음과 같은 로그를 확인할 수 있다. 

2025-01-06T13:40:59.846+09:00  INFO 9740 --- [apigateway-service] [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 8080 (http)

 

이전의 netfilx zuul을 사용할 때는 tomcat 내장서버를 사용하여 동기로 처리되었는데, spring cloud gateway를 사용하면 netty  비동기 내장 서버를 사용하게 된다. => 비동기방식으로 작동

 

단, 주의할 점은 spring boot 3점대 이상 기준 spring initilizer로 프로젝트를 생성하면 spring-cloud-starter-gateway-mvc 의존성을 가져오게되는데, spring-cloud-starter-gateway 의존성을 가져와야 정상적으로 Netty서버로 실행된다. (아니면 그냥 tomcat 서버 실행)

 

 

 

2. Filter 적용하기 - 설정 Config하기

1) Java코드로 설정하기 - bean등

@Configuration
public class FilterConfig {

    @Bean
    public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/first-service/**")
                             .filters(f -> f.addRequestHeader("first-request", "first-request-header")
                                            .addResponseHeader("first-response", "first-response-header"))
                             .uri("http://localhost:8081"))
                .route(r -> r.path("/second-service/**")
                             .filters(f -> f.addRequestHeader("second-request", "second-request-header")
                                            .addResponseHeader("second-response", "second-response-header"))
                             .uri("http://localhost:8082"))
                .build();
    }

}

 

클라이언트가 gateway의 서버 주소인 localhost:8000으로  위의 FilterConfig에서 라우팅 설정한 대로  요청하게 되면( localhost:8000/first-service/...) 서비스 요청 전/후로 requestheader와 responseheader를 추가해서 해당 uri로 요청/응답한다.

 

2) yml파일로 설정하기

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
        - id: first-service
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**
          filters:
            - AddRequestHeader=first-request, first-requests-header2
            - AddResponseHeader=first-response, first-response-header2
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
            - AddRequestHeader=second-request, second-requests-header2
            - AddResponseHeader=second-response, second-response-header2

 

 

 

 

3. Filter 적용하기 - Filter 생성하기 (1) Custom Filter

AbstractGatewayFilterFactory 상속

@Component
@Slf4j
public class CustomFilter extends AbstractGatewayFilterFactory<Object> {

    @Override
    public GatewayFilter apply(Object config) {
        // Custom Pre Filter
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            log.info("Custom Pre Filter : request id = {}", request.getId());

            // Custom Post Filter
            return chain.filter(exchange)
                    .then(Mono.fromRunnable(() -> {
                        log.info("Custom Post Filter : response code = {}", response.getStatusCode());
                    }));
        };
    }
}

 

- Pre Filter : 

request, response 정보 받기 -> servlet을 사용하지 않기 때문에 server request/response를 받아온다.

 

 

- Post Filter : 

chain에서 filter를 반환 시키면서 사후의 동작을 설정

Mono : 웹플럭스(Spring 5에서 추가) -> 동기 방식이 아닌 비동기방식의 서버를 지원할 때 단일값 전달 

 

- application.yml에 custom filter 등록

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
        - id: first-service
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**
          filters:
            - CustomeFilter
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
            - CustomeFilter

라우트 정보가 있을 때, 라우트 정보마다 무조건 사용하는 필터

 

- 결과

2025-01-06T14:59:11.805+09:00  INFO 26704 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.CustomFilter                : Custom Pre Filter : request id = cf36bbf9-2
2025-01-06T14:59:11.927+09:00  INFO 26704 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.CustomFilter                : Custom Post Filter : response code = 200 OK

 

 

 


3. Filter 적용하기 - Filter 생성하기 (2) Global Filter

- 요구사항 : CustomeFilter + Global Filter 추가 (설정값 추가)

 

application.yml

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter
          args:
            baseMessage: Spring Cloud Gateway Global Filter
            preLogger: true
            postLogger: true
      routes:
        - id: first-service
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**
          filters:
            - CustomFilter
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
            - CustomFilter

 

Global Filter Java코드

@Component
@Slf4j
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {
    public GlobalFilter() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        // Custom Pre Filter
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            log.info("Global Filter baseMessage : {}", config.getBaseMessage());

            if(config.isPreLogger()) {
                log.info("Global Filter Start: request id -> {}", request.getId());
            }

            // Custom Post Filter
            return chain.filter(exchange)
                    .then(Mono.fromRunnable(() -> {
                        if(config.isPostLogger()) {
                            log.info("Global Filter End: response code -> {}", response.getStatusCode());
                        }
                    }));
        };
    }

    @Data
    public static class Config {
        private String baseMessage;
        private boolean preLogger;
        private boolean postLogger;
    }

}

 

 

로그 확인

2025-01-06T15:25:15.632+09:00  INFO 10884 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.GlobalFilter                : Global Filter baseMessage : Spring Cloud Gateway Global Filter
2025-01-06T15:25:15.632+09:00  INFO 10884 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.GlobalFilter                : Global Filter Start: request id -> 28aa7bd8-1
2025-01-06T15:25:15.633+09:00  INFO 10884 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.CustomFilter                : Custom Pre Filter : request id = 28aa7bd8-1
2025-01-06T15:25:15.854+09:00  INFO 10884 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.CustomFilter                : Custom Post Filter : response code = 200 OK
2025-01-06T15:25:15.854+09:00  INFO 10884 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.GlobalFilter                : Global Filter End: response code -> 200 OK

Global Filter -> Custom Filter 순

 

 

 

3. Filter 적용하기 - Filter 생성하기 (3) Logging Filter

Custom Filter를 응용해서 Loggin Filter 만들기

요구사항 :  first-service, second-service가 있을때 global filter를 전역으로 설정하고, custom filter도 두 가지 서비스에 모두 적용하고, loggin filter는 second-service에만 등록하기

순서 : Gloabl Filter -> Custom Filter -> Loggin Filter 순

 

 

application.yml

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter
          args:
            baseMessage: Spring Cloud Gateway Global Filter
            preLogger: true
            postLogger: true
      routes:
        - id: first-service
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**
          filters:
            - CustomFilter
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
            - CustomFilter
            - name: LoggingFilter
              args:
                baseMessage: Hi, there.
                preLogger: true
                postLogger: true

 

 

LogginFilter

@Component
@Slf4j
public class LoggingFilter extends AbstractGatewayFilterFactory<LoggingFilter.Config> {
    public LoggingFilter() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        GatewayFilter filter = new OrderedGatewayFilter((exchange, chain) -> {
        	// Custom Pre Filter
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            log.info("Logging Filter baseMessage : {}", config.getBaseMessage());

            if(config.isPreLogger()) {
                log.info("Logging Pre Filter: request id -> {}", request.getId());
            }

            // Custom Post Filter
            return chain.filter(exchange)
                    .then(Mono.fromRunnable(() -> {
                        if (config.isPostLogger()) {
                            log.info("Logging Post Filter: response code -> {}", response.getStatusCode());
                        }
                    }));

        }, Ordered.HIGHEST_PRECEDENCE);
        return filter;
    }

    @Data
    public static class Config {
        private String baseMessage;
        private boolean preLogger;
        private boolean postLogger;
    }

}

 

 

 

로그 확인

first-service  호출 시

2025-01-06T15:42:37.351+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.GlobalFilter                : Global Filter baseMessage : Spring Cloud Gateway Global Filter
2025-01-06T15:42:37.351+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.GlobalFilter                : Global Filter Start: request id -> 24a063a7-1
2025-01-06T15:42:37.352+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.CustomFilter                : Custom Pre Filter : request id = 24a063a7-1
2025-01-06T15:42:37.495+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.CustomFilter                : Custom Post Filter : response code = 200 OK
2025-01-06T15:42:37.495+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.GlobalFilter                : Global Filter End: response code -> 200 OK

Logging Filter 호출x

 

 

second-service 호출 시

2025-01-06T15:43:14.327+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.LoggingFilter               : Logging Filter baseMessage : Hi, there.
2025-01-06T15:43:14.328+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.LoggingFilter               : Logging Pre Filter: request id -> 24a063a7-2
2025-01-06T15:43:14.328+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.GlobalFilter                : Global Filter baseMessage : Spring Cloud Gateway Global Filter
2025-01-06T15:43:14.328+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.GlobalFilter                : Global Filter Start: request id -> 24a063a7-2
2025-01-06T15:43:14.328+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.CustomFilter                : Custom Pre Filter : request id = 24a063a7-2
2025-01-06T15:43:14.384+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.CustomFilter                : Custom Post Filter : response code = 200 OK
2025-01-06T15:43:14.384+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.GlobalFilter                : Global Filter End: response code -> 200 OK
2025-01-06T15:43:14.385+09:00  INFO 19208 --- [apigateway-service] [ctor-http-nio-3] c.e.a.filter.LoggingFilter               : Logging Post Filter: response code -> 200 OK

Logging Filter 호출 O

HIGHEST 순서 설정 때문에 Logging -> Global -> Custom 순서가 되엇음

LOWEST로 설정하면 Global -> Custom -> Logging 순으로 만들 수 있음