client 가 직접 Microservice 에 접근하는건 좋지 않다고 했다.
실제로 yml 파일에 gateway:routes 설정을 하면서 Spring Cloud Gateway 를 사용해보자.
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/**
해당 서버들을 모두 실행하고 각 uri 로 접근한 결과 Controller 가 잘 동작하는 것을 확인했다.
현재까지의 프로세스를 정리
FilterConfig
@Configuration
public class FilterConfig {
@Bean
public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) {
// yml 파일에서 했던 것을 Java Code 로 작성
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();
}
}
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
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
return chain.filter(exchange).then(Mono.fromRunnable(()->{ // 비동기 방식 서버에서 단일값을 전달할 때 Mono 타입으로 전달한다.
log.info("Custom POST filter : response id -> {}" , response.getStatusCode());
}));
};
}
public static class Config {
// configuration 정보를 여기에 넣으면 된다.
}
}
// application.yml
cloud:
gateway:
routes:
- id: first-service
uri: http://localhost:8081/
predicates:
- Path=/first-service/**
filters:
- CustomFilter
Custom Filter 는 yml 파일에서 routes: 으로 각각 라우팅 정보 마다 지정을 해줬어야 했는데 Global Filter 는 공통된 부분은 하나의 필터로 사용할 수 있다.
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(()->{ // 비동기 방식 서버에서 단일값을 전달할 때 Mono 타입으로 전달한다.
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;
}
// yml 파일
spring:
application:
name: apigateway-service
cloud:
gateway:
default-filters:
- name: GlobalFilter
args:
baseMessage: Spring Cloud Gateway Global Filter
preLogger: true
postLogger: true
코드는 Global 과 거의 유사하다.
cloud:
gateway:
...
routes:
...
filters:
# 추가적인 전달할 파라미터가 있다면 name 옵션을 써야한다.
- name: CustomFilter
- name: LoggingFilter
args:
baseMessage: Hi , there.
preLogger: true
postLogger: true
routes:
- id: first-service
uri: lb://MY-FIRST-SERVICE
predicates:
- Path=/first-service/**
filters:
- CustomFilter
- id: second-service
uri: lb://MY-SECOND-SERVICE
predicates:
- Path=/second-service/**
실제로 모든 코드를 수정한 후 실행해보면 유레카 대시보드에 3개가 등록되어 있었고 POSTMAN 으로 각 요청들을 호출해보면 정상적으로 동작한다.
first-service 를 랜덤포트를 사용
instance:
instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
이렇게 한 service 에 인스턴스가 2개가 있을 때 어느 인스턴스로 갔는지 알 수 있는 방법은?
log.info("Server port = {}",request.getServerPort());
env.getProperty("local.server.port"));