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/**
@RequestMapping("/") 을 아래와 같이 수정
@RequestMapping("/first-service")
위 와 같이 변경하는 이유는 Gateway설정은 호출하면 http://localhost:8081/first-serivce/** 로 호출되기 때문에 위처럼 수정해야 한다.
- 클라이언트로 부터 request를 받음
- 사전 조건에 맞는지 확인 (어떤 이름으로 왔는지 이름 분기)
- 사전 필터
- 수행
- 사후 필터
- 클라이언트에 reponse를 반환
...
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/**
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();
}
}
@GetMapping("/message")
public String message(@RequestHeader("first-request") String header){
log.info(header);
return "Hello World in First service";
}
위 first-service 처럼 second-service도 동일하게 나옴.
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
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 정보를 집어 넣을 수 있음.
}
}
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
@GetMapping("/check")
public String check(){
return "Hi, there. this is a message from Fist Service";
}
서비스 전역에 해당되는 필터를 만들 수 있다.
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;
}
}
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
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;
}
}
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
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka
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(유레카에 있는 서비스 명)
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}}
@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 호출을 하면 포트 한번씩 돌아가면서 호출이 된다!