API 게이트웨이는 마이크로서비스 애플리케이션을 위한 서비스 라우팅 기능을 제공한다.
서비스 요청을 프록싱하고 대상 서비스가 호출되기 전에 마이크로서비스에 대한 모든 호출이 '현관'을 통과하도록 하는 서비스 게이트웨이다.
서비스 호출의 중앙 집중화로 보안 인가, 인증, 콘텐츠 필터링과 라우팅 규칙 등 표준 서비스 정책을 시행할 수 있다.
또한 서비스 클라이언트 호출에서 보낸 경로를 분해하고 서비스 클라이언트가 호출하려는 서비스를 결정한다.
정적 라우팅 : 서비스 게이트웨이는 단일 서비스 URL과 API 경로로 모든 서비스를 호출한다. 모든 서비스에 대해 하나의 서비스 엔드포인트만 알면 되므로 개발이 편해진다.
동적 라우팅 : 서비스 게이트웨이는 유입되는 서비스 요청을 검사하고 요청 데이터를 기반으로 서비스 호출자를 위한 지능적 라우팅을 수행할 수 있다.
인증 과 인가 : 모든 서비스 호출이 게이트웨이로 라우팅 되기 때문에 서비스 게이트웨이는 서비스 호출자가 자신의 인증 여부를 확인할 수 있는 적합한 장소다.
지표 수집과 로깅 : 서비스 호출이 게이트웨이를 통과하기 때문에 서비스 게이트웨이를 지표와 로그를 수집하는데 사용할 수 있다. 또한 사용자 요청에 대한 중요한 정보가 있는지 확인하여 균일한 로깅을 보장할 수 있다.
아래는 진행하고자하는 서비스들의 소스를 참고할 깃헙 주소이다.
https://github.com/hyeokjinON/microservice_study/tree/master/chapter8
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.optimagrowth</groupId>
<artifactId>gatewayserver</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>API Gateway server </name>
<description>API Gateway server </description>
<properties>
<java.version>11</java.version>
<docker.image.prefix>ostock</docker.image.prefix>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<!-- 메이븐은 스프링 클라우드 게이트웨이 라이브러리를 포함한다.-->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</exclusion>
<exclusion>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-eureka</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- This plugin is used to create a docker image and publish the image to docker hub-->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.13</version>
<configuration>
<repository>${docker.image.prefix}/${project.artifactId}</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
<executions>
<execution>
<id>default</id>
<phase>install</phase>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
bootstrap.yml
spring:
application:
# 스프링 클라우드 컨피그 클라이언트가 조회될 서비스를 알 수 있도록 게이트웨이 서비스의 이름을 지정한다.
name: gateway-server
cloud:
config:
# 스프링 클라우드 컨피그 서버의 위치를 설정한다.
uri: http://configserver:8071
logging:
level:
com.netflix: WARN
org.springframework.web: WARN
com.optimagrowth: DEBUG
그 다음 스프링 컨피그 서버에 유레카 구성정보 설정을 한다.
gateway-server.yml
server:
port: 8072
eureka:
instance:
preferIpAddress: true
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://eurekaserver:8070/eureka/
마지막으로 gatewayServer의
ApiGatewayServerApplication.java에 @EnableEurekaClient 에너테이션을 추가해준다.
ApiGatewayServerApplication.java
package com.optimagrowth.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayServerApplication.class, args);
}
}
게이트웨이에 대한 모든 경로 매핑은 gateway-server.yml 파일에서 경로를 정의해서 수행한다. 하지만 스프링 클라우드 게이트웨이는 gateway-server 구성 파일에 구성 정보를 추가해서 서비스ID를 기반으로 요청을 자동으로 라우팅할 수 있다.
gateway-server.yml
spring:
cloud:
loadbalancer.ribbon.enabled: false
gateway:
# 서비스 디스커버리에 등록된 서비스를 기반으로 게이트웨이가 경로를 생성하도록 설정한다.
discovery.locator:
enabled: true
lowerCaseServiceId: true
postman에서 게이트웨이경로 + 호출할 서비스 명 + 엔드포인트 주소를 이용해 확인해보면 호출 되는것을 볼 수 있다.
http://localhost:8072/organization-service/v1/organization/e839ee96-28de-4f67-bb79-870ca89743a0
스프링 클라우드 게이트웨이는 유레카 서비스 ID로 생성된 자동화된 경로에만 의존하지않고 명시적으로 경로 매핑을 정의할 수 있어 코드를 더욱 세분화 할 수 있다.
bootstrap.yml
spring:
cloud:
loadbalancer.ribbon.enabled: false
gateway:
# 서비스 디스커버리에 등록된 서비스를 기반으로 게이트웨이가 경로를 생성하도록 설정한다.
discovery.locator:
enabled: true
lowerCaseServiceId: true
routes:
# 이 선택적 ID는 임의의 경로에 대한 ID다
- id: organization-service
# 이 경로의 대상 URI를 설정한다
uri: lb://organization-service
# 경로(path)는 load()메서드로 설정되지만, 여러 옵션 중 하나다.
predicates:
- Path=/organization/**
# 응답을 보내기 전이나 후에 요청 또는 응답을 수정하고자 스프링 web.filters들을 필터링한다.
filters:
# 매개변수 및 교체 순서로 경로 정규식을 받아 요청 경로를 /organization/** 에서 /**로 변경한다.
- RewritePath=/organization/(?<path>.*), /$\{path}
- id: licensing-service
uri: lb://licensing-service
predicates:
- Path=/license/**
filters:
- RewritePath=/license/(?<path>.*), /$\{path}
postman으로 확인해보자
기존 URL에 organization-service를 organization으로 지정하였다.
http://localhost:8072/organization/v1/organization/e839ee96-28de-4f67-bb79-870ca89743a0
잘 나오는것을 확인했다.
게이트웨이 서버가 관리하는 경로를 확인하려면 게이트웨이 서버의 actuator/gateway/routes 엔드포인트를 통해 경로 목록을 볼 수 있다
유레카 서비스 ID 경로에 대한 자동화된 매핑을 제외하고 직접 정의한 조직 서비스 또는 라이선싱 서비스만 지원하려고 한다면
gateway-server.yml의 spring.cloud.gateway.discovery.locator 항목을 제거한다.
게이트웨이로 모든 요청을 프록시 할 수 있기대문에 서비스 호출을 단순화 할 수 있다. 게이트웨이를 통하는 모든 서비스 호출에 적용될 사용자 정의 로직을 작성할 때 진정한 힘을 발휘한다.
대부분의 경우 모든 서비스에서 보안,로깅,추적 등 일관된 애플리케이션 정책을 적용하기 위해 이러한 사용자 정의 로직이 사용된다.
Predicate Factories : 게이트웨이의 서술자는 요청을 실행하거나 처리하기 전에 요청이 조건 집합을 충족하는지 확인하는 객체이다
경로마다 논리 AND로 결합할수 있는 여러 Predicate Factories를 나열한다.
predicates
- Path=/organization/**
이러한 서술자는 생성한 구성파일을 사용하여 적용할 수 있다.
Filter Factories : 게이트웨이의 Filter Factories를 사용하면 코드에 정책 시행 지점을 삼입하여 모든 서비스 호출에 대해 일관된 방식으로 작업을 수행할 수 있다. 즉, 이러한 필터로 수신 및 발신하는 HTTP 요청과 응답을 수정할 수 있다.
스프링 클라우트 게이트웨이의 모든 필터 목록이다
스프링 클라우드 게이트웨이 내에서 필터를 사용하여 사용자 정의 로직을 만들 수 있다. 필터를 사용하여 각 서비스 요청이 통과되는 비즈니스 로직 체인을 구현할 수 있다.
사전 필터 : 실제 요청이 목적지로 전송되기 전에 사전 필터가 호출이 된다. 일반적으로 서비스가 일관된 메시지 형식인지 확인하는 작업을 수행하거나 서비스를 호출하는 사용자가 인증되었는지 확인하는 게이트키퍼 역할을 한다.
사후 필터 : 사후 필터는 대상서비스 이후에 호출되고 응답은 클라이언트로 다시 전송된다. 일반적으로 대상 서비스의 응답을 다시 기록하거나 오류를 처리하거나 민감한 정보에 대한 응답을 검사한다.
스프링 클라우드 게이트웨이에서 필터를 만드는 것은 간단하다. 먼저 게이트웨이로 유입되는 모든 요청을 검사하고 요청에서 tmx-correlation-id 라는 HTTP 헤더의 포함 여부를 확인하는 TrackingFilter라는 사전 필터를 만든다.
(tmx-correlation-id는 여러 마이크로서비스를 거쳐 사용자 요청을 추적하는데 사용되는 고유한 ID가 포함된다)
gatewatServer에서 사전필터를 만들어보자
TrackingFilter.java
package com.optimagrowth.gateway.filters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Order(1)
@Component
// 글로벌 필터는 인터페이스를 구현하고자 filter()메서드를 재정의해야한다.
public class TrackingFilter implements GlobalFilter {
private static final Logger logger = LoggerFactory.getLogger(TrackingFilter.class);
// 여러 필터에 걸쳐 공통으로 사용되는 함수는 FilterUtils 클래스에 캡슐화 돤다.
@Autowired
FilterUtils filterUtils;
// 요청이 필터를 통과할 때마다 실행되는 코드다
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// filter() 메서드의 매개변수로 전달된 ServerWebExchange 객체를 사용하여 요청에서 ServerWebExchange 객체 HTTP 헤더를 추출한다
HttpHeaders requestHeaders = exchange.getRequest().getHeaders();
if (isCorrelationIdPresent(requestHeaders)) {
logger.debug("tmx-correlation-id found in tracking filter: {}. ",
filterUtils.getCorrelationId(requestHeaders));
} else {
String correlationID = generateCorrelationId();
exchange = filterUtils.setCorrelationId(exchange, correlationID);
logger.debug("tmx-correlation-id generated in tracking filter: {}.", correlationID);
}
return chain.filter(exchange);
}
// 요청헤더에 상관관계 ID가 있는지 확인하는 헬퍼 메서드다.
private boolean isCorrelationIdPresent(HttpHeaders requestHeaders) {
if (filterUtils.getCorrelationId(requestHeaders) != null) {
return true;
} else {
return false;
}
}
// tmx-correlation-id 가 있는지 확인하는 헬퍼 메서드이며, 상관관계 ID를 UUID 값으로 생성한다.
private String generateCorrelationId() {
return java.util.UUID.randomUUID().toString();
}
}
FilterUtils.java
package com.optimagrowth.gateway.filters;
import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
@Component
public class FilterUtils {
public static final String CORRELATION_ID = "tmx-correlation-id";
public static final String AUTH_TOKEN = "tmx-auth-token";
public static final String USER_ID = "tmx-user-id";
public static final String ORG_ID = "tmx-org-id";
public static final String PRE_FILTER_TYPE = "pre";
public static final String POST_FILTER_TYPE = "post";
public static final String ROUTE_FILTER_TYPE = "route";
// getCorrelationId() 에서 tmx-correlation-id 추출하기
public String getCorrelationId(HttpHeaders requestHeaders){
if (requestHeaders.get(CORRELATION_ID) !=null) {
List<String> header = requestHeaders.get(CORRELATION_ID);
return header.stream().findFirst().get();
}
else{
return null;
}
}
// HTTP 헤더에 tmx-correlation-id 설정하기
public ServerWebExchange setRequestHeader(ServerWebExchange exchange, String name, String value) {
return exchange.mutate().request(
exchange.getRequest().mutate()
.header(name, value)
.build())
.build();
}
public ServerWebExchange setCorrelationId(ServerWebExchange exchange, String correlationId) {
return this.setRequestHeader(exchange, CORRELATION_ID, correlationId);
}
}
postman에서 서비스를 요청하고, 로그를 확인하면 tmx-correlation-id 값이 UUID 형식으로 기록이 된것을 확인할 수 있다.
gatewayserver_1 | 2022-08-15 09:00:31.326 DEBUG 1 --- [or-http-epoll-3] c.o.gateway.filters.TrackingFilter : tmx-correlation-id generated in tracking filter: b7b3e81c-6337-4b73-adfb-ac3274c2e84d
만약 서비스 헤더에 tmx-correlation-id 값을 셋팅하고 요청을 보낸다면
(test_id_haha 라고 적었다)
아래와 같이 로그가 출력되는 것을 볼 수 있다.
gatewayserver_1 | 2022-08-15 08:59:07.497 DEBUG 1 --- [or-http-epoll-3] c.o.gateway.filters.TrackingFilter : tmx-correlation-id found in tracking filter: test_id_haha.
이제 게이트웨이를 통과하는 모든 마이크로서비스 호출에 상관관계 ID가 추가되었기 때문에 다음 사항을 확인하고자 한다.
이를 구현하기위해 각 마이크로 서비스에서 UserContextFilter, UserContext, UserContextInterceptor 세 가지 클래스 세트를 빌드한다.
이러한 클래스는 유입되는 HTTP 요청의 상관관계 ID를 읽기 위해 협업하고, 애플리케이션의 비즈니스 로직에서 쉽게 엑세스 하고 사용할 수 있는 클래스에 ID를 매핑해서 모든 하위 서비스 호출에 전파할 것이다.
아래는 라이선싱서비스에 추가되는 예를 보여준다.
UserContextFilter.java
유입되는 HTTP 요청을 가로챈다
package com.optimagrowth.license.utils;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
// 스프링에서 선택한 필터를 등록한다
@Component
public class UserContextFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(UserContextFilter.class);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
// 헤더에 상관관계 ID를 정의하고 UserContext에 값을 설정한다.
UserContextHolder.getContext().setCorrelationId( httpServletRequest.getHeader(UserContext.CORRELATION_ID) );
UserContextHolder.getContext().setUserId(httpServletRequest.getHeader(UserContext.USER_ID));
UserContextHolder.getContext().setAuthToken(httpServletRequest.getHeader(UserContext.AUTH_TOKEN));
UserContextHolder.getContext().setOrganizationId(httpServletRequest.getHeader(UserContext.ORGANIZATION_ID));
logger.debug("UserContextFilter Correlation id: {}", UserContextHolder.getContext().getCorrelationId());
filterChain.doFilter(httpServletRequest, servletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
서비스에 쉽게 액세스 할 수 있는 HTTP 헤더를 만든다
UserContext.java
package com.optimagrowth.license.utils;
import org.springframework.stereotype.Component;
@Component
public class UserContext {
public static final String CORRELATION_ID = "tmx-correlation-id";
public static final String AUTH_TOKEN = "tmx-auth-token";
public static final String USER_ID = "tmx-user-id";
public static final String ORGANIZATION_ID = "tmx-organization-id";
private String correlationId= new String();
private String authToken= new String();
private String userId = new String();
private String organizationId = new String();
public String getCorrelationId() { return correlationId;}
public void setCorrelationId(String correlationId) {
this.correlationId = correlationId;
}
public String getAuthToken() {
return authToken;
}
public void setAuthToken(String authToken) {
this.authToken = authToken;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getOrganizationId() {
return organizationId;
}
public void setOrganizationId(String organizationId) {
this.organizationId = organizationId;
}
}
UserContextHolder
package com.optimagrowth.license.utils;
import org.springframework.util.Assert;
public class UserContextHolder {
private static final ThreadLocal<UserContext> userContext = new ThreadLocal<UserContext>();
public static final UserContext getContext(){
UserContext context = userContext.get();
if (context == null) {
context = createEmptyContext();
userContext.set(context);
}
return userContext.get();
}
public static final void setContext(UserContext context) {
Assert.notNull(context, "Only non-null UserContext instances are permitted");
userContext.set(context);
}
public static final UserContext createEmptyContext(){
return new UserContext();
}
}
상관관계 ID 전파를 위한 사용자 정의
RestTemplate 와 UserContextInterceptor 이다
UserContextInterceptor.java
package com.optimagrowth.license.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import java.io.IOException;
// ClientHttpRequestInterceptor를 구현한다
public class UserContextInterceptor implements ClientHttpRequestInterceptor {
private static final Logger logger = LoggerFactory.getLogger(UserContextInterceptor.class);
// RestTemplate에서 실제 HTTP 서비스 호출이 발생하기전에 intercept()를 호출한다.
@Override
public ClientHttpResponse intercept(
HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
HttpHeaders headers = request.getHeaders();
// 발신 서비스를 호출하고자 준비 중인 HTTP 요청 헤더에 UserContext에 저장된 상관관계 ID를 추가한다.
headers.add(UserContext.CORRELATION_ID, UserContextHolder.getContext().getCorrelationId());
headers.add(UserContext.AUTH_TOKEN, UserContextHolder.getContext().getAuthToken());
return execution.execute(request, body);
}
}
UserContextInterceptor를 사용하려면 RestTemplate 빈을 정의한 후 UserContextInterceptor를 그 빈에 추가해야 한다.
LicenseServiceApplication.java 에 돌아와서 RestTemplate 빈을 정의한다.
LicenseServiceApplication.java
// 이 RestRemplate 객체가 로드밸런서를 사용한다는 것을 나타낸다
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
RestTemplate template = new RestTemplate();
List interceptors = template.getInterceptors();
// RestTemplate 인스턴스에 UserContextInterceptor를 추가한다.
if (interceptors==null){
template.setInterceptors(Collections.singletonList(new UserContextInterceptor()));
}
else{
interceptors.add(new UserContextInterceptor());
template.setInterceptors(interceptors);
}
return template;
}
이렇게 organization-serive에도 같이 적용하면 되겠다
이제 최종적으로 게이트웨이에서 수동경로로 맵핑한 라이선싱 서비스를 호출하면서 RestTemplate를 사용하여 조직 서비스까지 조회하도록 HTTP호출을 해보자
postman을 이용하여 아래와 같은 URL 라이선싱 서비스를 호출하며
HTTP 헤더에 tmx-correlation-id 를 추가하고 값은 test_id_param로 요청보냈다
로그를 살펴보면 우리가 서비스 호출 경로와 요청헤더에 입력된 값을 확인 할 수 있었다. 아래와 같다.
사전 필터에서 데이터를 캡처하는 것과 연관되어 있다면 게이트웨이 사후 필터는 지표를 수집하고 사용자의 트랜잭션과 관련된 모든 로깅을 완료하는데 이상적인 위치다. 마이크로서비스에 전달한 상관관계 ID를 사용자에게 다시 전달해서 이것을 활용하고자 한다.
gatewayserver 에서 사후 필터(ResponseFilter.java)를 구현한다.
ResponseFilter.java
package com.optimagrowth.gateway.filters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
@Configuration
public class ResponseFilter {
final Logger logger =LoggerFactory.getLogger(ResponseFilter.class);
@Autowired
FilterUtils filterUtils;
@Bean
public GlobalFilter postGlobalFilter() {
return (exchange, chain) -> {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
HttpHeaders requestHeaders = exchange.getRequest().getHeaders();
// 원본 HTTP 요청에 전달된 상관관계 ID를 가져온다
String correlationId = filterUtils.getCorrelationId(requestHeaders);
logger.debug("Adding the correlation id to the outbound headers. {}", correlationId);
// 응답에 상관관계 ID를 삽입한다.
exchange.getResponse().getHeaders().add(FilterUtils.CORRELATION_ID, correlationId);
// 게이트웨이로 유입된 해당 사용자 요청의 오고가는 항목을 모두 보여주는 '북엔드' 가 되도록 발신 요청 URI를 로깅한다.
logger.debug("Completing outgoing request for {}.", exchange.getRequest().getURI());
}));
};
}
}
postman을 사용하여 HTTP 응답 헤더에 tmx-correlation-id 를 확인해보자 (요청할때 값을 test_id_haha로 보냈다)
잘 나온것을 확인 했다.
🧨 다음 챕터에는 마이크로서비스 보안에 대해 설명하도록 하겠다.