Spring Cloud Gateway
# 1 简介
Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 实现的响应式的 API 网关。
不能在传统的 servlet 容器中工作,也不能构建成 war 包。
Spring Cloud Gateway 旨在为微服务架构提供一种简单且有效的 API 路由的管理方式,并基于 Filter 的方式提供网关的基本功能,例如说安全认证、监控、限流等等。
核心概念
路由(route)
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。
断言(predicates)
Java8中的断言函数,SpringCloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。
过滤器(Filter)
SpringCloud Gateway中的filter分为Gateway FilIer和Global Filter。Filter可以对请求和响应进行处理。
# 2 快速开始
# 2.1 环境配置
# 2.1.1 引入依赖
<!-- gateway网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- nacos服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2
3
4
5
6
7
8
9
10
11
注意
会和spring-webmvc的依赖冲突,需要排除spring-webmvc
# 2.1.2 编写yml配置文件
server:
port: 8888
spring:
application:
name: test-gateway
#配置nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
# 默认为false,设为true开启可以通过微服务名访问服务
# http://localhost:8888/test-order/order/getbyno/1
enabled: true
# 是否开启网关
enabled: true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2.2 路由断言工厂
# 2.2.1 时间匹配
spring:
application:
name: test-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 默认为false,设为true开启可以通过微服务名访问服务
enabled: true # 是否开启网关
routes:
- id: test-order # id只要唯一即可,建议配合服务名
uri: http://localhost:8020 #目标微服务的请求地址和端口
predicates:
# 匹配在指定的日期时间之后发生的请求 入参是ZonedDateTime类型
- After=2021-01-31T22:22:07.783+08:00[Asia/Shanghai]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
获取ZonedDateTime类型的指定日期时间
ZonedDateTime zonedDateTime = ZonedDateTime.now();//默认时区
// 用指定时区获取当前时间
ZonedDateTime zonedDateTime2 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
2
3
# 2.2.2 Cookie匹配
spring:
application:
name: test-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 默认为false,设为true开启可以通过微服务名访问服务
enabled: true # 是否开启网关
routes:
- id: test-order # id只要唯一即可,建议配合服务名
uri: http://localhost:8020 #目标微服务的请求地址和端口
predicates:
# Cookie匹配
- Cookie=username, testname
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.2.3 Header匹配
spring:
application:
name: test-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 默认为false,设为true开启可以通过微服务名访问服务
enabled: true # 是否开启网关
routes:
- id: test-order # id只要唯一即可,建议配合服务名
uri: http://localhost:8020 #目标微服务的请求地址和端口
predicates:
# Header匹配 请求中带有请求头名为 x-request-id,其值与 \d+ 正则表达式匹配
- Header=X-Request-Id, \d+
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.2.4 路径匹配
spring:
application:
name: test-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 默认为false,设为true开启可以通过微服务名访问服务
enabled: true # 是否开启网关
routes:
- id: test-order # id只要唯一即可,建议配合服务名
uri: http://localhost:8020 #目标微服务的请求地址和端口
predicates:
# Path路径匹配
- Path=/test/**
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.2.5 自定义路由断言工厂
自定义路由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在 apply 方法中可以通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。
注意
命名需要以 RoutePredicateFactory
结尾
@Component
@Slf4j
public class CheckAuthRoutePredicateFactory
extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
log.info("调用CheckAuthRoutePredicateFactory" + config.getName());
if(config.getName().equals("testname")){
return true;
}
return false;
}
};
}
/**
* 快捷配置
* @return
*/
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("name");
}
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
spring:
application:
name: test-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 默认为false,设为true开启可以通过微服务名访问服务
enabled: true # 是否开启网关
routes:
- id: test-order # id只要唯一即可,建议配合服务名
uri: http://localhost:8020 #目标微服务的请求地址和端口
predicates:
# 自定义CheckAuth断言工厂
- name: CheckAuth
args:
name: testname
# - CheckAuth=testname
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2.3 过滤器工厂
# 2.3.1 添加请求头
spring:
application:
name: test-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 默认为false,设为true开启可以通过微服务名访问服务
enabled: true # 是否开启网关
routes:
- id: test-order # id只要唯一即可,建议配合服务名
uri: http://localhost:8020 #目标微服务的请求地址和端口
filters:
- AddRequestHeader=X-Request-color, red #添加请求头
2
3
4
5
6
7
8
9
10
11
12
13
14
测试http://localhost:8888/test-order/testgateway
@GetMapping("/testgateway")
public String testGateway(HttpServletRequest request) throws Exception {
log.info("gateWay获取请求头X-Request-color:"
+request.getHeader("X-Request-color"));
return "success";
}
@GetMapping("/testgateway2")
public String test2(@RequestHeader("X-Request-color") String color) throws Exception {
log.info("gateWay获取请求头X-Request-color:"+color);
return "success";
}
2
3
4
5
6
7
8
9
10
11
# 2.3.2 添加请求参数
spring:
application:
name: test-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 默认为false,设为true开启可以通过微服务名访问服务
enabled: true # 是否开启网关
routes:
- id: test-order # id只要唯一即可,建议配合服务名
uri: http://localhost:8020 #目标微服务的请求地址和端口
filters:
- AddRequestParameter=color, blue # 添加请求参数
2
3
4
5
6
7
8
9
10
11
12
13
14
测试http://localhost:8888/test-order/testgateway3
@GetMapping("/testgateway3")
public String test3(@RequestParam("color") String color) throws Exception {
log.info("gateWay获取请求参数color:"+color);
return "success";
}
2
3
4
5
# 2.3.3 路径添加前缀
spring:
application:
name: test-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 默认为false,设为true开启可以通过微服务名访问服务
enabled: true # 是否开启网关
routes:
- id: test-order # id只要唯一即可,建议配合服务名
uri: http://localhost:8020 #目标微服务的请求地址和端口
filters:
- PrefixPath=/test-order-abc # 添加前缀 对应微服务需要配置context-path
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2.3.4 重定向
spring:
application:
name: test-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 默认为false,设为true开启可以通过微服务名访问服务
enabled: true # 是否开启网关
routes:
- id: test-order # id只要唯一即可,建议配合服务名
uri: http://localhost:8020 #目标微服务的请求地址和端口
filters:
- RedirectTo=302, https://www.baidu.com/ #重定向到百度
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2.3.5 自定义过滤器工厂
继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给spring管理。
@Component
@Slf4j
public class CheckAuthGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return (exchange, chain) -> {
log.info("调用CheckAuthGatewayFilterFactory==="
+ config.getName() + ":" + config.getValue());
return chain.filter(exchange);
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
spring:
application:
name: test-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 默认为false,设为true开启可以通过微服务名访问服务
enabled: true # 是否开启网关
routes:
- id: test-order # id只要唯一即可,建议配合服务名
uri: http://localhost:8020 #目标微服务的请求地址和端口
filters:
- CheckAuth=test,123
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2.4 全局过滤器
提示
GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过, GlobalFilter 会作用于所有路由。
# 2.4.1 LoadBalancerClientFilter
LoadBalancerClientFilter 会查看exchange的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR
的值(一个URI),如果该值的scheme是 lb
,比如:lb://myservice ,它将会使用Spring Cloud的LoadBalancerClient 来将 myservice 解析成实际的host和port,并替换掉 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR
的内容。
提示
其实就是用来整合负载均衡器Ribbon的
spring:
application:
name: test-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 默认为false,设为true开启可以通过微服务名访问服务
enabled: true # 是否开启网关
routes:
- id: test-order # id只要唯一即可,建议配合服务名
uri: lb://test-order #目标微服务的请求地址和端口
predicates:
# Path路径匹配
- Path=/test/**
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.4.2 自定义全局过滤器
@Component
@Order(-1)
@Slf4j
public class CheckAuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//校验请求头中的token
List<String> token = exchange.getRequest().getHeaders().get("token");
log.info("token:"+ token);
if (token.isEmpty()){
return null;
}
return chain.filter(exchange);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.5 Gateway跨域配置
# 2.5.1 yml配置方式
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- DELETE
- PUT
- OPTION
2
3
4
5
6
7
8
9
10
11
12
13
# 2.5.2 java配置方式
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class CorsFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (CorsUtils.isCorsRequest(request)) {
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = exchange.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "18000L");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(exchange);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26