spring cloud gateway
gateway 简介
Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
(1)基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
(2)集成 Hystrix 断路器
(3)集成 Spring Cloud DiscoveryClient
(4)Predicates 和 Filters 作用于特定路由,易于编写的 Predicates 和 Filters
(5)具备一些网关的高级功能:动态路由、限流、路径重写
gateway 网关背景
Spring Cloud Gateway 可以看做是一个 Zuul 1.x 的升级版和代替品,比 Zuul 2 更早的使用 Netty 实现异步 IO,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。
Spring Cloud Gateway 里明确的区分了 Router 和 Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。
比如内置了 10 种 Router,使得我们可以直接配置一下就可以随心所欲的根据 Header、或者 Path、或者 Host、或者 Query 来做路由。
比如区分了一般的 Filter 和全局 Filter,内置了 20 种 Filter 和 9 种全局 Filter,也都可以直接用。当然自定义 Filter 也非常方便。
gateway 基本概念
网关的主要功能这一就是转发请求,转发规则的定义主要包含三个部分:
| Route(路由) | 路由是网关的基本单元,由ID、URI、一组Predicate、一组Filter组成,根据Predicate进行匹配转发。 |
|---|---|
| Predicate(谓语、断言) | 路由转发的判断条件,目前SpringCloud Gateway支持多种方式,常见如:Path、Query、Method、Header等,写法必须遵循 key=vlue的形式 |
| Filter(过滤器) | 过滤器是路由转发请求时所经过的过滤逻辑,可用于修改请求、响应内容 |
(1)Route(路由):
网关配置的基本组成模块。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。
(2)Filter(过滤器):
使用它拦截和修改请求,并且对上游的响应,进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter 类的实例。
(3)Predicate(断言):
这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange。
//通过配置文件配置
spring:
application:
name: merchant-openapigw
jackson:
time-zone: GMT+8
main:
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
server-addr: 192.168.3.182:8848
enabled: true
autoconfigure:
exclude: org.redisson.spring.starter.RedissonAutoConfiguration
gateway:
discovery:
locator:
enabled: true # 使用服务发现路由
# # 路由配置项,对应 RouteDefinition 数组
routes:
- id: merchant-cts-gray # 路由的编号
uri: lb://merchant-cts-gray # 路由到的目标地址
predicates: # 断言
- Path=/merchant-cts/**
- Header=X-Request-User,1001
filters:
- StripPrefix=1
- AddRequestHeader=Version,v2
- id: merchant-cts # 路由的编号
uri: lb://merchant-cts # 路由到的目标地址
predicates: # 断言
- Path=/merchant-cts/**
filters:
- StripPrefix=1
- AddRequestHeader=Version,v3
路由配置方式
路由匹配-断言
-
各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。
-
一个请求满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。Spring Cloud 内置的几种 Predicate 的实现。
| The After Route Predicate Factory | After=2022-01-01T06:06:06+08:00[Asia/Shanghai] |
| The Before Route Predicate Factory | Before=2022-01-01T06:06:06+08:00[Asia/Shanghai] |
| The Between Route Predicate Factory | - Between=2022-01-01T06:06:06+08:00[Asia/Shanghai], 2022-02-01T06:06:06+08:00[Asia/Shanghai] |
| The Cookie Route Predicate Factory | - Cookie=chocolate, ch.p |
| The Header Route Predicate Factory | - Header=X-Request-Id, \d+ |
| The Host Route Predicate Factory | - Host=.somehost.org,.anotherhost.org |
| The Method Route Predicate Factory | - Method=GET,POST |
| The Path Route Predicate Factory | - Path=/red/{segment},/blue/{segment} |
| The Query Route Predicate Factory | - Query=green |
| The RemoteAddr Route Predicate Factory | - RemoteAddr=192.168.1.1/24 |
| The Weight Route Predicate Factory | - Weight=group1, |
- 通过时间匹配
Predicate 支持设置一个时间,在请求进行转发的时候,可以通过判断在这个时间之前或者之后进行转发。比如我们现在设置只有在 2019 年 1 月 1 日才会转发到我的网站,在这之前不进行转发,我就可以这样配置:
spring:
cloud:
gateway:
routes:
- id: time_route
uri: https://www.ifconfig.me
predicates:
- After=2021-11-01T06:06:06+08:00[Asia/Shanghai]
Spring 是通过 ZonedDateTime 来对时间进行的对比,ZonedDateTime 是 Java 8 中日期时间功能里,用于表示带时区的日期与时间信息的类,ZonedDateTime 支持通过时区来设置时间,中国的时区是:Asia/Shanghai。
After Route Predicate 是指在这个时间之后的请求都转发到目标地址。上面的示例是指,请求时间在 2021 年 1 1月 01 日 6 点 6 分 6 秒之后的所有请求都转发到地址。+08:00是指时间和 UTC 时间相差八个小时,时间地区为Asia/Shanghai。
添加完路由规则之后,访问地址http://localhost:8080会自动转发到https://www.ifconfig.me。
通过请求路径匹配
Path Route Predicate 接收一个匹配路径的参数来判断是否走路由。
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://www.ifconfig.me
predicates:
- Path=/foo/{segment}
如果请求路径符合要求,则此路由将匹配,例如:/foo/1 或者 /foo/bar。
使用 curl 测试,命令行输入:
curl http://localhost:8080/foo/1
curl http://localhost:8080/foo/xx
curl http://localhost:8080/boo/xx
经过测试第一和第二条命令可以正常获取到页面返回值,最后一个命令报 404,证明路由是通过指定路由来匹配。
通过请求参数匹配
Query Route Predicate 支持传入两个参数,一个是属性名一个为属性值,属性值可以是正则表达式。
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://www.ifconfig.me
predicates:
- Query=smile
这样配置,只要请求中包含 smile 属性的参数即可匹配路由。
使用 curl 测试,命令行输入:
curl localhost:8080?smile=x&id=2
经过测试发现只要请求汇总带有 smile 参数即会匹配路由,不带 smile 参数则不会匹配。
还可以将 Query 的值以键值对的方式进行配置,这样在请求过来时会对属性值和正则进行匹配,匹配上才会走路由。
组合使用
上面为了演示各个 Predicate 的使用,我们是单个单个进行配置测试,其实可以将各种 Predicate 组合起来一起使用。
例如:
spring:
cloud:
gateway:
routes:
- id: host_foo_path_headers_to_httpbin
uri: https://www.ifconfig.me
predicates:
- Host=**.foo.org
- Path=/headers
- Method=GET
- Header=X-Request-Id, \d+
- Query=foo, ba.
- Query=baz
- Cookie=chocolate, ch.p
- After=2021-1-01T06:06:06+08:00[Asia/Shanghai]
各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。
路由匹配-过滤器
| 过滤规则 | 实例 | 说明 |
|---|---|---|
| PrefixPath | - PrefixPath=/app | 在请求路径前加上app |
| RewritePath | - RewritePath=/test, /app/test | 访问localhost:9022/test,请求会转发到localhost:8001/app/test |
| SetPath | SetPath=/app/{path} | 通过模板设置路径,转发的规则时会在路径前增加app,{path}表示原请求路径 |
| RedirectTo | 重定向 | |
| RemoveRequestHeader | 去掉某个请求头信息 |
Default-filters
对所有请求添加过滤器。
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Tag, order-x
- PrefixPath=/http
PrefixPath
对所有的请求路径添加前缀:
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: https://httpbin.org
filters:
- PrefixPath=/mypath
访问/hello的请求被发送到httpbin.org/mypath/hell…
RedirectTo
重定向,配置包含重定向的返回码和地址:
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: https://httpbin.org
filters:
- RedirectTo=302, https://ifconfig.me
RemoveRequestHeader
去掉某个请求头信息:
spring:
cloud:
gateway:
routes:
- id: removerequestheader_route
uri: https://httpbin.org
filters:
- RemoveRequestHeader=X-Request-Foo
去掉请求头信息 X-Request-Foo
RemoveResponseHeader
去掉某个回执头信息:
spring:
cloud:
gateway:
routes:
- id: removerequestheader_route
uri: https://httpbin.org
filters:
- RemoveResponseHeader=X-Request-Foo
RemoveRequestParameter
去掉某个请求参数信息:
spring:
cloud:
gateway:
routes:
- id: removerequestparameter_route
uri: https://httpbin.org
filters:
- RemoveRequestParameter=red
RewritePath
改写路径:
spring:
cloud:
gateway:
routes:
- id: rewrite_filter
uri: http://localhost:8081
predicates:
- Path=/test/**
filters:
- RewritePath=/where(?<segment>/?.*), /test(?<segment>/?.*)
/where/... 改成 test/...
使用代码改下路径
RouteLocatorBuilder.Builder builder = routeLocatorBuilder.routes();
builder
.route("path_rote_at_guigu", r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei"))
.route("csdn_route", r -> r.path("/csdn")
.uri("https://blog.csdn.net"))
.route("blog3_rewrite_filter", r -> r.path("/blog3/**")
.filters(f -> f.rewritePath("/blog3/(?<segment>.*)", "/$\\{segment}"))
.uri("https://blog.csdn.net"))
.route("rewritepath_route", r -> r.path("/baidu/**")
.filters(f -> f.rewritePath("/baidu/(?<segment>.*)", "/$\\{segment}"))
.uri("http://www.baidu.com"))
.build();
SetPath
设置请求路径,与RewritePath类似。
spring:
cloud:
gateway:
routes:
- id: setpath_route
uri: https://httpbin.org
predicates:
- Path=/red/{segment}
filters:
- SetPath=/{segment}
如/red/blue的请求被转发到/blue。
SetRequestHeader
设置请求头信息。
spring:
cloud:
gateway:
routes:
- id: setrequestheader_route
uri: https://httpbin.org
filters:
- SetRequestHeader=X-Request-Red, Blue
SetStatus
设置回执状态码。
spring:
cloud:
gateway:
routes:
- id: setstatusint_route
uri: https://httpbin.org
filters:
- SetStatus=401
StripPrefix
跳过指定路径。
spring:
cloud:
gateway:
routes:
- id: nameRoot
uri: https://nameservice
predicates:
- Path=/name/**
filters:
- StripPrefix=2
请求/name/blue/red会转发到/red。
RequestSize
请求大小。
spring:
cloud:
gateway:
routes:
- id: request_size_route
uri: http://localhost:8080/upload
predicates:
- Path=/upload
filters:
- name: RequestSize
args:
maxSize: 5000000
超过5M的请求会返回413错误。
通过代码配置
将路由规则设置为一个Bean即可:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/get")
.uri("http://httpbin.org"))
.route("host_route", r -> r.host("*.myhost.org")
.uri("http://httpbin.org"))
.route("rewrite_route", r -> r.host("*.rewrite.org")
.filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
.uri("http://httpbin.org"))
.route("hystrix_route", r -> r.host("*.hystrix.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
.uri("http://httpbin.org"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.route("limit_route", r -> r
.host("*.limited.org").and().path("/anything/**")
.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
.uri("http://httpbin.org"))
.build();
}
常见业务
一旦网关处理程序确定请求与路由匹配,就会通过过滤器链传递请求。过滤器可以在请求发送之前或之后执行逻辑。
我们编写一个简单的全局过滤器开始。这意味着,它将影响每个请求。
首先,我们将了解如何在发送代理请求之前执行逻辑(也称为“预”过滤器)
Checking and Modifying the Request
我们必须从原始交换对象中获取一个新的 ServerWebExchange 实例,修改原始的 ServerHttpRequest 实例
ServerWebExchange modifiedExchange = exchange.mutate()
// Here we'll modify the original request:
.request(originalRequest -> originalRequest)
.build();
return chain.filter(modifiedExchange);
Modifying the Response
(exchange, chain) -> {
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
Optional.ofNullable(exchange.getRequest()
.getQueryParams()
.getFirst("locale"))
.ifPresent(qp -> {
String responseContentLanguage = response.getHeaders()
.getContentLanguage()
.getLanguage();
response.getHeaders()
.add("Bael-Custom-Language-Header", responseContentLanguage);
});
}));
}
Requests to Other Services
我们假设场景的下一步是依靠第三个服务来指示我们应该使用哪个 Accept-Language 标头。
因此,我们将创建一个新的过滤器来调用此服务,并将其响应主体用作代理服务 API 的请求标头
在反应式环境中,这意味着链接请求以避免阻塞异步执行。
在我们的过滤器中,我们首先向语言服务发出请求:
(exchange, chain) -> {
return WebClient.create().get()
.uri(config.getLanguageEndpoint())
.exchange()
// ...
}
请注意,我们正在返回这个流畅的操作,因为正如我们所说,我们会将调用的输出与我们的代理请求链接起来。
下一步将是从响应正文中提取语言,或者如果响应不成功,则从配置中提取语言并对其进行解析
// ...
.flatMap(response -> {
return (response.statusCode()
.is2xxSuccessful()) ? response.bodyToMono(String.class) : Mono.just(config.getDefaultLanguage());
}).map(LanguageRange::parse)
// ...
最后,我们将 LanguageRange 值设置为我们之前所做的请求标头,并继续过滤器链:
.map(range -> {
exchange.getRequest()
.mutate()
.headers(h -> h.setAcceptLanguage(range))
.build();
return exchange;
}).flatMap(chain::filter);
自定义路由谓词
Spring Cloud Gateway中的谓词是一个对象,用于测试给定请求是否满足给定条件。对于每个路由,我们可以定义一个或多个谓词,如果满足,将在应用任何过滤器后接受对配置的后端的请求。
*Spring Cloud Gateway使用Factory Method Pattern作为一种机制,以可扩展的方式支持创建谓词实例。
内置谓词工厂,它们可以在 spring-cloud-gateway-core 模块的org.springframework.cloud.gateway.handler.predicate包中找到。它们的名称都以RoutePredicateFactory结尾。
- 扩展了AbstractRoutePredicateFactory ,进而实现了网关使用的RoutePredicateFactory接口。
- 谓词定义内部 Config类,该类用于存储测试逻辑使用的配置参数
实现过程:
- 定义一个Config类来保存配置参数
- 使用配置类作为其模板参数扩展 AbstractRoutePredicateFactory
- 覆盖 apply方法,返回实现所需测试逻辑的 谓词
编写谓词工厂
我们假设以下情况:对于给定的API,调用我们必须在两个可能的后端之间进行选择。我们最重视的“正常”客户应该被路由到功能强大的服务器。非正常客户使用功能较弱的灰度服务器。
该服务采用与请求关联的 customerId 并返回其状态。至于customerId,在我们的场景中,我们假设它在cookie中可用。
有了所有这些信息,我们现在可以编写我们的自定义谓词。我们将保留现有的命名约定,并将我们的类命名为
GrayCustomerRoutePredicateFactory:
public class GrayCustomerRoutePredicateFactory extends
AbstractRoutePredicateFactory<GoldenCustomerRoutePredicateFactory.Config> {
// ...
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return (ServerWebExchange t) -> {
List<HttpCookie> cookies = t.getRequest()
.getCookies()
.get(config.getCustomerIdCookie());
boolean isGray;
if ( cookies == null || cookies.isEmpty()) {
isGray = false;
} else {
String customerId = cookies.get(0).getValue();
isGray = isGrayCustomer(customerId);
}
return config.isGray() ? isGray : false;
};
}
@Validated
public static class Config {
boolean isGray = true;
@NotEmpty
String customerIdCookie = "customerId";
// ...constructors and mutators omitted
}
public boolean isGrayCustomer(String customerId) {
if ( "bob".equalsIgnoreCase(customerId)) {
return true;
}
else {
return false;
}
}
}
注册谓语工厂
对自定义谓词工厂进行编码后,我们需要一种使Spring Cloud Gateway知道是否存在的方法。由于我们使用的是Spring,因此可以通过通常的方式完成:我们声明类型为GrayCustomerRoutePredicateFactory的bean 。
由于我们的类型通过Base类实现 RoutePredicateFactory ,因此它将在上下文初始化时由Spring选择并提供给Spring Cloud Gateway。
在这里,我们将使用*@Configuration*类创建bean :
@Configuration
public class CustomPredicatesConfig {
@Bean
public GrayCustomerRoutePredicateFactory goldenCustomer() {
return new GrayCustomerRoutePredicateFactory();
}
}
在YAML中定义
我们可以使用属性或YAML文件以声明的方式获得与以前相同的结果。
注意我们在每个路由中如何使用两个不同的Config配置。在第一种情况下,第一个参数为true,因此当我们收到灰度客户的请求时,谓词将评估为true。至于第二条配置,我们在构造函数中传递 false,因此对于非灰度客户,我们的谓词将返回 true 。
spring:
cloud:
gateway:
routes:
- id: gray
uri: https://httpbin.org
predicates:
- Path=/api/**
- GrayCustomer=true,customerId
filters:
- StripPrefix=1
- AddRequestHeader=GrayCustomer,true
自定义过滤器
Writing Global “Pre” Filter Logic
我们将创建简单的过滤器,因为这里的主要目标只是查看过滤器实际上是在正确的时刻执行的;只需记录一条简单的消息就可以了。
创建自定义全局过滤器所需要做的就是实现 Spring Cloud Gateway GlobalFilter 接口,并将其作为 bean 添加到上下文中:
@Component
public class LoggingGlobalPreFilter implements GlobalFilter {
final Logger logger =
LoggerFactory.getLogger(LoggingGlobalPreFilter.class);
@Override
public Mono<Void> filter(
ServerWebExchange exchange,
GatewayFilterChain chain) {
logger.info("Global Pre Filter executed");
return chain.filter(exchange);
}
}
Writing Global “Post” Filter Logic
关于我们刚刚定义的全局过滤器,需要注意的另一件事是 GlobalFilter 接口只定义了一种方法。因此,它可以表示为 lambda 表达式,让我们可以方便地定义过滤器。 例如,我们可以在配置类中定义我们的“post”过滤器:
@Configuration
public class LoggingGlobalFiltersConfigurations {
final Logger logger =
LoggerFactory.getLogger(
LoggingGlobalFiltersConfigurations.class);
@Bean
public GlobalFilter postGlobalFilter() {
return (exchange, chain) -> {
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
logger.info("Global Post Filter executed");
}));
};
}
}
简单地说,这里我们在链完成执行后运行一个新的 Mono 实例。
现在让我们通过在网关服务中调用 /service/resource URL 并查看日志控制台来尝试一下:
自然,我们可以将“pre”和“post”逻辑组合在一个过滤器中:
@Component
public class FirstPreLastPostGlobalFilter
implements GlobalFilter, Ordered {
final Logger logger =
LoggerFactory.getLogger(FirstPreLastPostGlobalFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
logger.info("First Pre Global Filter");
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
logger.info("Last Post Global Filter");
}));
}
@Override
public int getOrder() {
return -1;
}
}
请注意,如果我们关心过滤器在链中的位置,我们也可以实现 Ordered 接口。
由于过滤器链的性质,具有较低优先级(链中较低顺序)的过滤器将在较早阶段执行其“前置”逻辑,但它的“后”实现将在稍后被调用
graph TD
A[Gateway client] --> B(Geteway Handler)
B -- PRE --> C[Filter Order = -1]
C -- PRE --> D[Filter Order = 0]
D -- PRE --> E[Filter Order = n]
E --> F[Service]
F --> E
E -- PSOT --> D
D -- PSOT --> C
C -- PSOT --> B
B --> A
Creating *GatewayFilter*s
全局过滤器非常有用,但我们经常需要执行仅适用于某些路由的细粒度自定义网关过滤器操作。
为了实现 GatewayFilter,我们必须实现 GatewayFilterFactory 接口。 Spring Cloud Gateway 还提供了一个抽象类来简化流程,AbstractGatewayFilterFactory 类
@Component
public class LoggingGatewayFilterFactory extends
AbstractGatewayFilterFactory<LoggingGatewayFilterFactory.Config> {
final Logger logger =
LoggerFactory.getLogger(LoggingGatewayFilterFactory.class);
public LoggingGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
// ...
}
public static class Config {
// ...
}
}
在这里,我们定义了 GatewayFilterFactory 的基本结构。我们将在初始化时使用 Config 类来自定义我们的过滤器。
例如,在这种情况下,我们可以在配置中定义三个基本字段
public static class Config {
private String baseMessage;
private boolean preLogger;
private boolean postLogger;
// contructors, getters and setters...
}
简单地说,这些字段是:
将包含在日志条目中的自定义消息 指示过滤器是否应在转发请求之前记录的标志 一个标志,指示过滤器是否应在收到代理服务的响应后记录
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// Pre-processing
if (config.isPreLogger()) {
logger.info("Pre GatewayFilter logging: "
+ config.getBaseMessage());
}
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> {
// Post-processing
if (config.isPostLogger()) {
logger.info("Post GatewayFilter logging: "
+ config.getBaseMessage());
}
}));
};
}
我们现在可以轻松地将过滤器注册到我们之前在应用程序属性中定义的路由:
...
filters:
- RewritePath=/service(?<segment>/?.*), $\{segment}
- name: Logging
args:
baseMessage: My Custom Message
preLogger: true
postLogger: true
我们只需要指明配置参数。这里重要的一点是,我们需要在 LoggingGatewayFilterFactory.Config 类中配置一个无参数的构造函数和设置器,以使这种方法正常工作。
如果我们想使用紧凑符号来配置过滤器,那么我们可以这样做:
filters:
- RewritePath=/service(?<segment>/?.*), $\{segment}
- Logging=My Custom Message, true, true
我们需要稍微调整一下我们的工厂。简而言之,我们必须重写shortcutFieldOrder 方法,以指示快捷方式属性将使用的顺序和参数数量:
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("baseMessage",
"preLogger",
"postLogger");
}
Ordering the GatewayFilter
@Override
public GatewayFilter apply(Config config) {
return new OrderedGatewayFilter((exchange, chain) -> {
// ...
}, 1);
}
Registering the GatewayFilter Programmatically
此外,我们也可以通过编程方式注册我们的过滤器。让我们重新定义我们一直使用的路由,这次通过设置一个 RouteLocator bean
@Bean
public RouteLocator routes(
RouteLocatorBuilder builder,
LoggingGatewayFilterFactory loggingFactory) {
return builder.routes()
.route("service_route_java_config", r -> r.path("/service/**")
.filters(f ->
f.rewritePath("/service(?<segment>/?.*)", "$\\{segment}")
.filter(loggingFactory.apply(
new Config("My Custom Message", true, true))))
.uri("http://localhost:8081"))
.build();
}
统一全局异常处理
Spring Cloud Gateway 中的全局异常处理不能直接使用 @ControllerAdvice。
如果不做处理,当发生异常时,Gateway 默认给出的错误信息是页面,不方便前端进行异常处理。
覆盖默认的配置,代码如下所示。
@Slf4j
@Component
@Order(-2)
@RequiredArgsConstructor
public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler {
private final ObjectMapper objectMapper;
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse response = exchange.getResponse();
if (response.isCommitted()) {
return Mono.error(ex);
}
return response.writeWith(Mono.fromSupplier(() -> {
// 返回的数据格式
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
DataBufferFactory bufferFactory = response.bufferFactory();
try {
// 业务处理
return bufferFactory.wrap(objectMapper.writeValueAsBytes(Map.of(Constants.CODE, -1, Constants.MSG, ex.getMessage())));
} catch (Exception e) {
// ignore
return bufferFactory.wrap(new byte[0]);
}
}));
}
}
一个简单的例子
- 在工程中使用依赖管理系统。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.0.0.RC2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 接下来,添加必要的依赖项
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
- 现在我们在 application.yml 文件中创建一个简单的路由配置:
spring:
cloud:
gateway:
routes:
- id: baeldung_route
uri: http://baeldung.com
predicates:
- Path=/baeldung/
management:
endpoints:
web:
exposure:
include: "*'
- 这是网关应用程序代码:
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
应用程序启动后,我们可以访问 url “http://localhost/actuator/gateway/routes/baeldung_route” 来检查所有创建的路由配置
{
"id":"baeldung_route",
"predicates":[{
"name":"Path",
"args":{"_genkey_0":"/baeldung"}
}],
"filters":[],
"uri":"http://baeldung.com",
"order":0
}
我们看到相对 url “/baeldung” 被配置为路由。因此,点击 URL“http://localhost/baeldung”,我们将被重定向到“http://baeldung.com”,正如我们在示例中配置的那样。