공부/Spring

[Spring Cloud] API Gateway (Spring Cloud Gateway)

린구 2024. 8. 5. 15:18
반응형

 

📌 API 게이트웨이

`API 게이트웨이`는 클라이언트의 요청을 받아 다양한 백엔드 서비스로 라우팅하고 다양한 부가 기능을 제공하는 중간 서버이다.

즉 클라이언트 요청을 받아 적절한 서비스로 전달하는 역할을 수행한다. 그 외에도 인증 및 권한 부여, 로드 밸런싱, 모니터링 및 로깅 등의 기능을 수행한다.

 

📌 Spring Cloud Gateway

`Spring Cloud Gateway`란 Spring 프로젝트의 일환으로 개발된 API 게이트웨이로 MSA 아키텍처에서 널리 사용된다.

동적 라우팅, 필터링, 모니터링, 보안 등의 기능을 제공한다.

 

`Spring Cloud Gateway`를 사용하려면 관련 의존성을 추가해야 한다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
	  implementation 'org.springframework.boot:spring-boot-starter-actuator'
	  implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
	  implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}

 

또한 `application.yml` 파일에서 라우팅 설정을 정의할 수 있다. 예시는 실습에서 볼 것이다!

 

Spring Cloud Gateway 에는 두 가지 필터가 있다모든 요청에 대해 작동하는 `Global Filter`와 특정 라우트에만 작동하는 `Gateway Filter` 이다.필터를 구현하려면 `GlobalFilter`/`GatewayFilter` 인터페이스를 구현하고, `filter` 메서드를 오버라이드 해야 한다.

 

필터에는 시점별로 두 가지 종류가 있는데 요청이 처리되기 전에 실행되는 `Pre` 필터와요청이 처리된 후 실행되는 `Post` 필터가 있다.

@Component
public class PreFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 요청 로깅
        System.out.println("Request: " + exchange.getRequest().getPath());
        // 수행할 필터 로직
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {  // 필터의 순서를 지정
        return -1;
    }
}

`Post` 필터는 `then` 메서드를 사용하여 응답이 완료된 후 실행할 작업을 정의해야 한다.

@Component
public class PostFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            // 응답 로깅
            System.out.println("Response Status: " + exchange.getResponse().getStatusCode());
            // 수행할 로직
        }));
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

return 한 후에 필터 로직을 수행하기 때문에 `then`이 필요한 것이다!

 

📌 Spring Cloud Gateway 실습

Erueka + Order (1개) + Product (3개) + Cloud Gateway 를 사용하여 진행한다.

 

gateway로 요청하여 다양한 서비스로 라우팅하는 방식이다.

 

server:
  port: 19091  # 게이트웨이 서비스가 실행될 포트 번호

spring:
  main:
    web-application-type: reactive  # Spring 애플리케이션이 리액티브 웹 애플리케이션으로 설정됨
  application:
    name: gateway-service  # 애플리케이션 이름을 'gateway-service'로 설정
  cloud:
    gateway:
      routes:  # Spring Cloud Gateway의 라우팅 설정
        - id: order-service  # 라우트 식별자
          uri: lb://order-service  # 'order-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
          predicates:
            - Path=/order/**  # /order/** 경로로 들어오는 요청을 이 라우트로 처리
        - id: product-service  # 라우트 식별자
          uri: lb://product-service  # 'product-service'라는 이름으로 로드 밸런싱된 서비스로 라우팅
          predicates:
            - Path=/product/**  # /product/** 경로로 들어오는 요청을 이 라우트로 처리
      discovery:
        locator:
          enabled: true  # 서비스 디스커버리를 통해 동적으로 라우트를 생성하도록 설정

eureka:
  client:
    service-url:
      defaultZone: http://localhost:19090/eureka/  # Eureka 서버의 URL을 지정

게이트웨이 어플리케이션의 `application.yml` 파일이다.

이런 식으로 `routes`에 서비스들을 등록하고 경로에 따라 어떤 라우트로 처리할건지 설정해주면 된다.

 

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.logging.Logger;

@Component
public class CustomPreFilter implements GlobalFilter, Ordered {

    private static final Logger logger = Logger.getLogger(CustomPreFilter.class.getName());

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest response = exchange.getRequest();
        logger.info("Pre Filter: Request URI is " + response.getURI());
        // Add any custom logic here

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.logging.Logger;

@Component
public class CustomPostFilter implements GlobalFilter, Ordered {

    private static final Logger logger = Logger.getLogger(CustomPostFilter.class.getName());

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            logger.info("Post Filter: Response status code is " + response.getStatusCode());
            // Add any custom logic here
        }));
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

이는 `PreFilter`와 `PostFilter`를 작성한 것이다.

 

`Mono`는 필터의 주요 객체로 리액티브 프로그래밍에서 0 또는 1개의 데이터를 비동기적으로 처리한다.

`ServerWebExchange`는 Http 요청과 응답을 캡슐화한다.

`exchange.getRequest()`는 Http 요청을 가져오고 `exchange.getResponse()`는 Http 응답을 가져온다.

`GatewayFilterChain`은 여러 필터를 체인처럼 연결한다. `chain.filter(exchange)를 통해 다음 필터로 요청을 전달하고 있다.

 

나머지 서비스들은 이전에 진행한 실습 코드와 거의 동일하기에 생략하도록 하겠다.

 

유레카 서버 ⇒ 게이트웨이 ⇒ 주문 ⇒ 상품 순으로 어플리케이션을 실행하면

`/19091/order` 로 접속 시 order 서비스를 호출하고

`/19091/product` 로 접속 시 product 서비스를 호출하는 것을 확인할 수 있다.

 

또한 로그 확인 시, 호출 할때마다 필터가 동작하는 것을 확인할 수 있다!

 


 

실습을 진행한 결과 API Gateway는 다양한 백엔드 서비스로 라우팅하는 중간 계층 역할을 수행하고

해당 과정에서 인증, 필터링, 로드 밸런싱 등의 다양한 기능을 수행하는 것을 알게 되었다.

 

 

반응형