SpringCloud- Gateway

이재철·2021년 7월 25일
0

MSA

목록 보기
3/13

Gateway

Gateway를 사용하면 비동기도 사용이 가능함. Zuul은 2.4이하에서만 사용 가능.
Lombok, Gateway, Eureka Discovery Client

  • 설정
server:
  port: 8000
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/**
  • first-service, second-service
@RequestMapping("/") 을 아래와 같이 수정
@RequestMapping("/first-service")

위 와 같이 변경하는 이유는 Gateway설정은 호출하면 http://localhost:8081/first-serivce/** 로 호출되기 때문에 위처럼 수정해야 한다.

Filter 적용하기

  1. 클라이언트로 부터 request를 받음
  2. 사전 조건에 맞는지 확인 (어떤 이름으로 왔는지 이름 분기)
  3. 사전 필터
  4. 수행
  5. 사후 필터
  6. 클라이언트에 reponse를 반환

1. 자바로 filter 설정하기

  1. yml 설정
...

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/**
  1. filter 설정
package com.example.apiatewayservice.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

  /**
   * RouteLocator를 반환해야 application.yml에서 설정한 것처럼 라우터 역할을 함
   * RouteLocatorBuilder 인자로 받아 빌더를 라우트 설정 후 리턴
   *
   * 각각의 route path와 uri로 라우트 시키며 중간 filter를 거침
   * first-request 헤더에 first-request-header값을
   * first-response 헤더에 first-response-header값을 넣는다.
   */
  @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();
  }
}
  1. first-service, second-service 다음 메서드 추가
  @GetMapping("/message")
  public String message(@RequestHeader("first-request") String header){
    log.info(header);
    return "Hello World in First service";
  }



위 first-service 처럼 second-service도 동일하게 나옴.

2. yml로 filter 설정하기

  1. FilterConfig.java 에서 @Configuration, @Bean 주석
  2. yml 설정
server:
  port: 8000
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/**
          filters: # 첫번째 키, 두번째 값
            - AddRequestHeader=first-request, first-request-header2
            - AddResponseHeader=first-response, first-response-header2
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
            - AddRequestHeader=second-request, second-request-header2
            - AddResponseHeader=second-response, second-response-header2

3. Custom Filter 적용하기

  1. CustomFilter 생성
package com.example.apiatewayservice.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config> {

  public CustomFilter() {
    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("Custom PRE Filter: request id -> {}", request.getId());

      // Custom Post Filter
      // Mono : 웹 플럭스에서 지원함. 비동기 방식에서 사용
      // 내가 reactor 공부한거 참고!! 깃에 있음!
      return chain.filter(exchange).then(Mono.fromRunnable(() -> {
        log.info("Custom POST Filter: response code -> {}", response.getStatusCode());
      }));
    };
  }

  public static class Config{
    // config 정보를 집어 넣을 수 있음.
  }
}
  1. yml 설정
server:
  port: 8000
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/**
          filters: # 첫번째 키, 두번째 값
#            - AddRequestHeader=first-request, first-request-header2
#            - AddResponseHeader=first-response, first-response-header2
            - CustomFilter
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
#            - AddRequestHeader=second-request, second-request-header2
#            - AddResponseHeader=second-response, second-response-header2
            - CustomFilter
  1. first-service, second-service check method 추가
  @GetMapping("/check")
  public String check(){
    return "Hi, there. this is a message from Fist Service";
  }

4. Global Filter 설정하기

서비스 전역에 해당되는 필터를 만들 수 있다.
1. Global Filter 생성

package com.example.apiatewayservice.filter;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@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());
      }
      return chain.filter(exchange).then(Mono.fromRunnable(() -> {
        if(config.isPostLogger()){
          log.info("Global Filter End: response code -> {}", response.getStatusCode());
        }
      }));
    };
  }

  @Getter
  @Setter
  public static class Config{
    // config 정보를 집어 넣을 수 있음.
    private String baseMessage;
    private boolean preLogger;
    private boolean postLogger;
  }
}
  1. yml 설정
server:
  port: 8000
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/**
          filters: # 첫번째 키, 두번째 값
#            - AddRequestHeader=first-request, first-request-header2
#            - AddResponseHeader=first-response, first-response-header2
            - CustomFilter
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
#            - AddRequestHeader=second-request, second-request-header2
#            - AddResponseHeader=second-response, second-response-header2
            - CustomFilter
      default-filters:
        - name: GlobalFilter
          args: # Global Filter 내 inner Class의 Config class 에 값이 들어감.
            baseMessage: Spring Cloud Gateway Global Filter
            preLogger: true
            postLogger: true

5. Logging Filter 설정하기

  1. LoggingFilter
package com.example.apiatewayservice.filter;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class LoggingFilter extends AbstractGatewayFilterFactory<LoggingFilter.Config> {

  public LoggingFilter() {
    super(Config.class);
  }

  @Override
  public GatewayFilter apply(Config config) {
    /**
     * webflux에서
     * ServerWebExchange exchange 에 request, response가 있음
     * GatewayFilterChain chain은 다양한 필터 프리 필터,포스트 필터, 프리체인 등
     *
     * OrderedGatewayFilter를 생성하여 우선순위(Order) 설정 가능
     * Ordered.HIGHEST_PRECEDENCE : HIGHEST라서 가장 먼저 실행 가능
     * LOWEST_PRECEDENCE : HIGHEST_PRECEDENCE의 반대
     */
    GatewayFilter filter = new OrderedGatewayFilter((exchange, chain) -> {
      ServerHttpRequest request = exchange.getRequest();
      ServerHttpResponse response = exchange.getResponse();

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


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

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

    return filter;
  }

  @Getter
  @Setter
  public static class Config{
    // config 정보를 집어 넣을 수 있음.
    private String baseMessage;
    private boolean preLogger;
    private boolean postLogger;
  }

}
  1. yml 설정
server:
  port: 8000
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/**
          filters: # 첫번째 키, 두번째 값
#            - AddRequestHeader=first-request, first-request-header2
#            - AddResponseHeader=first-response, first-response-header2
            - name: CustomFilter
            - name: LoggingFilter # 만약에 args를 넣어야한다면 filters 내에 name: 을 앞에 추가해서 구분해줘야함.
              args:
                baseMessage: Hi, there
                preLogger: true
                postLogger: true
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
#            - AddRequestHeader=second-request, second-request-header2
#            - AddResponseHeader=second-response, second-response-header2
            - CustomFilter
      default-filters:
        - name: GlobalFilter
          args: # Global Filter 내 inner Class의 Config class 에 값이 들어감.
            baseMessage: Spring Cloud Gateway Global Filter
            preLogger: true
            postLogger: true

Load Balancer - Eureka 연동

1. apigateway-service, first-service, second-service의 pom.xml eureka-client 유무 체크

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2. apigateway-service, first-service, second-service의 application.yml 유레카 설정 다음과 같이 변경

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

3. apigateway-service의 spring-cloud-routes uri 변경

spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      routes:
        - id: first-service
          uri: lb://MY-FIRST-SERVICE
          predicates: # 조건절
            - Path=/first-service/**
          filters: # 첫번째 키, 두번째 값
#            - AddRequestHeader=first-request, first-request-header2
#            - AddResponseHeader=first-response, first-response-header2
            - name: CustomFilter
            - name: LoggingFilter # 만약에 args를 넣어야한다면 filters 내에 name: 을 앞에 추가해서 구분해줘야함.
              args:
                baseMessage: Hi, there
                preLogger: true
                postLogger: true
        - id: second-service
          uri: lb://MY-SECOND-SERVICE
          predicates:
            - Path=/second-service/**
          filters:
#            - AddRequestHeader=second-request, second-request-header2
#            - AddResponseHeader=second-response, second-response-header2
            - CustomFilter

http://localhost:8081/ -> lb://MY-FIRST-SERVICE(유레카에 있는 서비스 명)
http://localhost:8082/ -> lb://MY-SECOND-SERVICE(유레카에 있는 서비스 명)

4. first-service, second-service 2개씩!

  • yml 설정
server:
  port: 0

spring:
  application:
    name: my-first-service

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
  • controller check method 수정 -> 몇번 포트인지 확인하기 위해서
  @GetMapping("/check")
  public String check(HttpServletRequest request){
    log.info("Server port = {}", request.getServerPort());

    return String.format("Hi, there. there is a message from Fist Service on Port %s",
                            environment.getProperty("local.server.port"));
  }


/check 호출을 하면 포트 한번씩 돌아가면서 호출이 된다!

0개의 댓글