Spring Gateway

SangLog·2023년 6월 27일
0

기본상식

목록 보기
3/8
post-thumbnail

사전지식

프록시란

spring gateway를 보통 리버스 프록시와 같은 기능을 한다고 한다.
프록시에 대해서 간단하게 알아보면 다음과 같다.

프록시

  • 프록시 서버는 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 접
    속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램을 가르킨다.

  • 대리 라는 의미를 가지며 서버와 서버사이의 중계기 역할을 한다

  • 보안, 캐싱, 암호화 등의 역할로서 사용이 된다.

프록시 종류

네트워크상의 어디에 위치하느냐에 따라서 프록시는 종류가 나뉘게 된다.
즉 인터넷 앞에서 클라이언트의 응답을 처리해서 통신을 하는지
아니면 인터넷에서 요청되어 오는것을 앞에서 막아서 처리하는지로 나뉘게 된다.


이미지 출처

프론트 프록시

클라이언트 바로 뒤에 놓여 있는 경우이며 같은 내부망에 존재하는 클라이언트의 요청을 받아 인터넷을 통해 외부서버에서 데이터를 가져와 클라이언트에게 응답해 준다.

즉 클라이언트의 요청을 포워드 프록시가 받고 그 포워드 프록시가 실제 서버에 인터넷으로 요청을 하는ㄴ것이다.

리버스 프록시

웹서버 앞 쪽에 놓여 있는것으로 클라이언트가 인터넷을 통해 요청한것을 받아서
실제 서버쪽으로 요청을 넣어주는 역할을 한다.
회사에서 DMZ에 리버스 프록시 서버를 두고 실제 서버는 내부방에 위치 시키는 것을 생각 해볼 수 있다.

Spring Gateway

Spring gateway란

스프링 클라우드 게이트웨이는 API 게이트웨이 중 하나이다.
즉 Spring Reactive 환경에 구현된 API 로 비동기 처리에 유리하다.

Reactive : non-blocking으로 기존의 명령형 프로그램과는 다르게 데이터의 흐름을 먼저 정의하면 데이터의 벼화 혹은 작업의 종료에 따라 반응하여 진행되는 프로그램

API 게이트웨이의 역할

  • API 라우팅
  • 인증 및 권한 부여
  • 속도 제한
  • 부하 분산(로드밸런싱)
  • 로깅(모니터링)

기본 옵션

  • Route
    - 응답을 보낼 목적지 uri와 필터 항목을 식별하기 위한 ID 로 구성되어 있고 라우팅의 목적지를 의미한다.
  • Predicate
    - 요청을 처리하기 전 HTTP 요청이 정의된 조건에 부합하는지 검사한다.
  • Filter
    - 게이트웨이에서 받은 요청과 응답을 수정하는 역할로 Spring Framework 의 WebFilter 인스턴스 이다.
# application.yml 예시
/test/** path로 들어온다면 
해당하는 uri 뒤에 /test/** 의 path를 연결해서 route 처리  

spring:
  cloud:
      gateway:
        default-filters:
          - name: GlobalFilter
        routes:
          - id: test-service
            uri: http://localhost:8080
            predicates:
              - Path=/test/**
            filters:             
              - testFilter              
              - RewritePath=/(?<segment>.*), /$\{segment}
          

*정규식

RewritePath는 , 앞에는 기존에 전달받은 uri에서 캡쳐하고자 하는부분
뒤쪽은 재작성하는 부분(앞쪽에서 캡처한 segment를 참조하여)

/v1/test/* 라는 predicates에서 이후 재작성시 /v1을 제거하고 싶다면 아래와 같은 형태로 작성
RewritePath=/v1/(?.
), /${segment}

Filter 생성

gateway에서 filter를 custom 해서 사용하는 방법은 아래의 예시와 같이 AbstractGatewayFilterFactory 를 상속받아서 filter 하고자 하는 내용을 apply 함수에 오버라이딩 하여 사용한다.
생성된 filter는 application.yml에 추가하여 사용 가능하다.

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

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();

           	// filter 에서 처리가 되어야 하는 내용들 삽입 

            return chain.filter(exchange); // 토큰이 일치할 때

        });
    }

    public static class Config {

    }
}

Request Body 변환

암호화 하여 통신을 하는 경우 body부분을 확인하여 암호화를 해제하고 응답을 넘겨줘야하는 경우가 있다.

기본적으로 BodyInserter와 CachedBodyOutputMessage를 사용해서 변경된 body값을 넣어준다. 중요한건 body가 변경되면서 body의 size가 변화되기 때문에 header에 존재하는 content-length 를 변경해주지 않으면 제대로된 통신이 이뤄지지 않는다.

따라서 Headear과 body부분을 조회하는 부분을 오버라이드하여 filter chain에서 넘겨준다.

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

  @Override
  public GatewayFilter apply(Config config) {
    return ((exchange, chain) -> {
      Flux<DataBuffer> requestBody = exchange.getRequest().getBody();
      return DataBufferUtils.join(requestBody)
          .flatMap(dataBuffer -> {
            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bytes);
            DataBufferUtils.release(dataBuffer);
//            String check = new String(bytes, StandardCharsets.UTF_8);
            Map modifiedMap = new HashMap();
            modifiedMap.put("test1","test1");
            ObjectMapper mapper = new ObjectMapper();
            String stringmap;
            try {
              stringmap = mapper.writeValueAsString(modifiedMap);
            } catch (JsonProcessingException e) {
              throw new RuntimeException(e);
            }
            BodyInserter bodyInserter = BodyInserters.fromPublisher(Mono.just(stringmap), String.class);
            HttpHeaders headers = new HttpHeaders();
            headers.putAll(exchange.getRequest().getHeaders());
            headers.remove(HttpHeaders.CONTENT_LENGTH);
            CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
            return bodyInserter.insert(outputMessage, new BodyInserterContext())
                .then(Mono.defer(() -> {
                  ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
                      exchange.getRequest()) {
                    @Override
                    public HttpHeaders getHeaders() {
                      long contentLength = headers.getContentLength();
                      HttpHeaders httpHeaders = new HttpHeaders();
                      httpHeaders.putAll(super.getHeaders());
                      if (contentLength > 0) {
                        httpHeaders.setContentLength(contentLength);
                      } else {
                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                      }
                      return httpHeaders;
                    }

                    @Override
                    public Flux getBody() {
                      return outputMessage.getBody();
                    }
                  };
                  return chain.filter(exchange.mutate().request(decorator).build());
                }));

          });


    });
  }

  public static class Config {

  }
}

Path 변환

gateway로 들어오는 path 값이 변경되어야 하는 케이스가 있다.
특이사항일것 같지만 아래와 같이 gateway에서 userId 값을 조회해서
값을 전달해야하는 케이스
/test/{userId}

더 좋은 방법이 있을 수도 있지만 내가 생각한건 URI path 값을 강제로 변환해서 넘기는 방법이었다

@Override
  public GatewayFilter apply(Config config) {
    return ((exchange, chain) -> {
      Flux<DataBuffer> requestBody = exchange.getRequest().getBody();
      Map<String, String> uriVariables = ServerWebExchangeUtils.getUriTemplateVariables(exchange);
      String segment = uriVariables.get("segment"); // 전달되었던 값 

      UriComponents updatedUriComponents = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
          .replacePath("/receive/test2") //변경하고자하는값 
          .build();
      ServerHttpRequest updatedRequest = exchange.getRequest().mutate()
          .uri(updatedUriComponents.toUri())
          .build();

      return chain.filter(exchange.mutate().request(updatedRequest).build());
    });

gateway filter 의 기능들이 정리된 블로그
https://woooongs.tistory.com/55
-> 공식문서 https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.0.M1/







참고 자료
https://ko.n4zc.com/article/74vk8srx.html (body 변환)
https://inpa.tistory.com/entry/NETWORK-📡-Reverse-Proxy-Forward-Proxy-정의-차이-정리
https://velog.io/@gowjr207/스프링-클라우드-게이트웨이를-설명해보다

profile
기록 쌓기

0개의 댓글