能做什么?
网关是整个微服务API请求的入口,负责拦截所有请求,分发到服务上去。可以实现日志拦截、权限控制、解决跨域问题、限流、熔断、负载均衡、隐藏服务端的ip,黑名单与白名单拦截、授权等。
如何使用?
<!-- spring-cloud网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--Spring Webflux-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
如果不想启用gateway可以使用参数关闭 spring.cloud.gateway.enabled=false
重要概念
- 路由:路由网关的基本构建块。它是由ID,目标URI,断言集合和过滤器集合定义。如果断言成功,则匹配路由
- 断言:输入类型为ServerWebExchange。让你匹配http请求中的任何内容,例如请求参数等
- 过滤器:GatewayFilter的实现。可以通过过滤器再发送给下游服务之前修改请求和响应。
工作原理
处理流程:
- 请求过Gateway,首先Gateway Handler Mapping 断言
- 如果断言成功,通过Gateway Web Handler 去匹配路由
- 之后通过过滤器链,最后路由到目标服务
配置Gateway参数
简写配置
application.yml
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Cookie=mycookie,mycookievalue
翻译:断言Cookie中 key 为 mycookie的值为mycooievalue。这里注意,逗号后面是值,前面是key
完全展开的配置
application.yml
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- name: Cookie
args:
name: mycookie
regexp: mycookievalue
翻译:和简写效果一致,只是不简写方式
断言工厂
1. After 断言
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://www.baidu.com
predicates:
- After=2022-06-04T14:15:00.000+08:00[Asia/Shanghai]
翻译:超过After后面的时间,断言才成功,跳转到baidu
2. Before断言
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://www.baidu.com
predicates:
- Before=2022-06-04T14:15:00.000+08:00[Asia/Shanghai]
翻译:在之前的时间访问,断言才成功,跳转到baidu
3. Between 断言
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://www.baidu.com
predicates:
- Between=2022-06-04T14:15:00.000+08:00[Asia/Shanghai],2022-06-04T15:15:00.000+08:00[Asia/Shanghai]
翻译:在这两个时间之间才能断言成功,可以 在抢购场景使用
4. Cookie 断言
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
翻译:访问时,携带cookie 并且 有个叫chocolate的key,value为ch.p,断言为true
5. Header 断言
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
翻译:必须携带请求头,并且有一个key为X-Request-Id,value为1个或者多个数字,断言为ture
6. Host 断言
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://www.baidu.com
predicates:
- Host=localhost:8080
翻译:网关是本地起的并且端口是8080,如果拦截域名(xiaoyi.com),可以使用Host=xiaoyi.com,如果想拦截二级域名也可以使用Host=**.xiaoyi.com。这里我想用localhost.** 拦截,不能成功
7. 请求方式断言
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST
翻译:当请求方式为GET,POST,则断言成功
8. 请求路径(Path)断言
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
翻译:使用正则匹配请求路径,比如/red/1
or /red/blue
or /blue/green都会被断言为true。
这里截断的变量可以在后续的filter使用,如这里的segment,可以通过下面代码获取
Map<String, String> uriVariables = ServerWebExchangeUtils.getPathPredicateVariables(exchange);
String segment = uriVariables.get("segment");
9. 请求参数断言
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=green
翻译:请求参数有gree时,断言成功
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=red, gree.
翻译:请求参数有red,并且red=gree. ,断言成功
10. RemoteAddr 断言
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
翻译:IP为192.168.1 开头的ip断言为true,这/24是子网掩码的写法。
11. 权重断言
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
翻译:group1中 80% 的概率 断言到 weight_high,20%的概率 断言到 weight_low
过滤器
1. AddRequestHeader
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue
翻译:为第一个下游请求中加入X-Request-red=blue的请求头
2. AddRequestParameter
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
filters:
- AddRequestParameter=red, blue
翻译:为下游请求中加入请求参数 red=blue
3. AddResponseHeader
spring:
cloud:
gateway:
routes:
- id: add_response_header_route
uri: https://example.org
filters:
- AddResponseHeader=X-Response-Red, Blue
翻译:响应头中加入X-Response-Res=Blue
4. DedupeResponseHeader
spring:
cloud:
gateway:
routes:
- id: dedupe_response_header_route
uri: https://example.org
filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
翻译:在网关 CORS 逻辑和下游逻辑都添加它们的情况下,这将删除重复值Access-Control-Allow-Credentials
和响应标头。Access-Control-Allow-Origin
5. RewritePath
spring:
cloud:
gateway:
routes:
- id: rewritepath_route
uri: https://example.org
predicates:
- Path=/red/**
filters:
- RewritePath=/red(?<segment>/?.*), $\{segment}
翻译:重写uri,如请求为/red/xxx,通过gateway的此过滤器之后 变为/xxx。
/red(?<segment>/?.*), $\{segment} 逗号前为源字符串,逗号后面为转换后的字符串
============================2022年6月6日15:31:55========
问题:
请求地址:http://localhost:9999/mtc-auth/sys/role/list
gateway相关路由配置:
routes:
- id: mtc-auth
uri: lb://mtc-auth
predicates:
- Path=/auth/**
filters:
- RewritePath=/auth/?(?<segment>.*), /$\{segment}
gateway启动文件(开启了服务发现功能)
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
上面请求地址能正确访问到auth服务。为什么能正确转发呢?这块断言只写了Path=/auth/**
答:源码探究:
当启用服务发现功能时,会启用网关的GatewayDiscoveryClientAutoConfiguration的类,其中主要代码。
@Bean
public DiscoveryLocatorProperties discoveryLocatorProperties() {
DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
properties.setPredicates(initPredicates());
properties.setFilters(initFilters());
return properties;
}
public static List<PredicateDefinition> initPredicates() {
ArrayList<PredicateDefinition> definitions = new ArrayList<>();
// TODO: add a predicate that matches the url at /serviceId?
// add a predicate that matches the url at /serviceId/**
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class));
predicate.addArg(PATTERN_KEY, "'/'+serviceId+'/**'");
definitions.add(predicate);
return definitions;
}
上面注释很清楚的说明:默认会条件一个断言条件: /serviceId/ 此处 serviceId 为服务名称。
替换服务名的过程 要追踪到 CachingRouteLocator 监听器 其中进入subscribe方法
@Override
public void onApplicationEvent(RefreshRoutesEvent event) {
try {
fetch().collect(Collectors.toList()).subscribe(list -> Flux.fromIterable(list)
.materialize().collect(Collectors.toList()).subscribe(signals -> {
applicationEventPublisher
.publishEvent(new RefreshRoutesResultEvent(this));
cache.put(CACHE_KEY, signals);
}, throwable -> handleRefreshError(throwable)));
}
catch (Throwable e) {
handleRefreshError(e);
}
}
一直追踪到RouteDefinitionRouteLocator
@Override
public Flux<Route> getRoutes() {
Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions()
.map(this::convertToRoute);
if (!gatewayProperties.isFailOnRouteDefinitionError()) {
// instead of letting error bubble up, continue
routes = routes.onErrorContinue((error, obj) -> {
if (logger.isWarnEnabled()) {
logger.warn("RouteDefinition id " + ((RouteDefinition) obj).getId()
+ " will be ignored. Definition has invalid configs, "
+ error.getMessage());
}
});
}
return routes.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition matched: " + route.getId());
}
return route;
});
}
之后进入DiscoveryClientRouteDefinitionLocator的getRouteDefinitions方法
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
...
return serviceInstances.filter(instances -> !instances.isEmpty())
.map(instances -> instances.get(0)).filter(includePredicate)
.map(instance -> {
RouteDefinition routeDefinition = buildRouteDefinition(urlExpr,
instance);
final ServiceInstance instanceForEval = new DelegatingServiceInstance(
instance, properties);
for (PredicateDefinition original : this.properties.getPredicates()) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs()
.entrySet()) {
//这里是替换服务名的地方
String value = getValueFromExpr(evalCtxt, parser,
instanceForEval, entry);
predicate.addArg(entry.getKey(), value);
}
routeDefinition.getPredicates().add(predicate);
}
for (FilterDefinition original : this.properties.getFilters()) {
FilterDefinition filter = new FilterDefinition();
filter.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs()
.entrySet()) {
String value = getValueFromExpr(evalCtxt, parser,
instanceForEval, entry);
filter.addArg(entry.getKey(), value);
}
routeDefinition.getFilters().add(filter);
}
return routeDefinition;
});
}
直到上面注释的地方就是替换服务名的地方了。
===========================2022年9月13日08:54:31 补充=======================
开启兼容服务注册表中注册的服务创建路由
spring.cloud.gateway.discovery.locator.enabled=true
开启此属性,gateway会从配置好的服务注册中心拉取对应服务注册表。
结合上面创建的默认路由源码,可以看出开启此属性之后,即使不写路由配置规则也会生成默认的路由规则。
例如有服务名为xiaoyi-masterdata,会默认生成下面路由规则。
routes:
- id: xiaoyi-masterdata
uri: lb://xiaoyi-masterdata
predicates:
- Path=/xiaoyi-masterdata/**
filters:
- RewritePath=/xiaoyi-masterdata/?(?<segment>.*), /$\{segment}