Spring Cloud Gateway 网关除了核心的路由判断表达式(predicate :有的译为谓词)和过滤器外,还有一些其它的设置,以便于可以更好的配置和应用 Gateway。
例如,元数据配置,超时处理,跨域处理,HTTPS 安全配置,监控/指标 收集,问题定位等。可以添加一些全局配置,可以收集健康数据,便于快速定位问题等等。
元数据
可以通过使用元数据来给每个路由配置额外的参数(对所有路由起效的全局参数)。示例如下:
spring:
cloud:
gateway:
routes:
- id: route_with_metadata
uri: https://example.org
metadata:
optionName: "OptionValue"
compositeObject:
name: "value"
iAmNumber: 1
可以从 Exchange 中获取的元数据属性。如下示例:
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
// get all metadata properties
route.getMetadata();
// get a single metadata property
route.getMetadata(someKey);
路由定义
SpringCloudGateway 的配置由 RouteDefinitionLocator
实例的集合驱动。下面所示 RouteDefinitionLocator
接口:
public interface RouteDefinitionLocator {
Flux<RouteDefinition> getRouteDefinitions();
}
默认情况下,通过 Spring Boot’s @ConfigurationProperties
机制,一个 PropertiesRouteDefinitionLocator
会加载配置属性。
PropertiesRouteDefinitionLocator 继承自 RouteDefinitionLocator,在网关自动配置类 GatewayAutoConfiguration 中将其注册为了 Bean,注入了 GatewayProperties。
较早的配置示例均使用快捷方式,该快捷方式使用位置参数而不是命名参数。 以下两个示例是等效的:
spring:
cloud:
gateway:
routes:
- id: setstatus_route
uri: https://example.org
filters:
- name: SetStatus #命名参数
args:
status: 401
- id: setstatusshortcut_route
uri: https://example.org
filters:
- SetStatus=401 #位置参数
对于网关的某些用法,属性是足够的,但是某些生产用例将从外部资源(例如 数据库)加载配置中受益。
未来的里程碑版本将基于 Spring Data Repositories(例如 Redis,MongoDB 和 Cassandra)使用 RouteDefinitionLocator
实现。
超时处理
Gateway 可以为所有路由 或 重写指定的路由配置 HTTP 超时(连接超时 和 响应超时)。
全局超时
配置全避局 HTTP 超时,有两个参数可配置:connect-timeout
和 response-timeout
。
- connect-timeout:必须指定位 毫秒(milliseconds)单位
- response-timeout:必须指定位 java.time.Duration 类型
全局 HTTP 超时示例:
spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000
response-timeout: 5s
前置路由超时
配置前置路由(per-route)超时,有两个参数可用:
- connect-timeout:必须指定为 毫秒 单位。
- response-timeout:必须指定为 毫秒 单位。
per-route http timeouts configuration via configuration
spring:
cloud:
gateway:
routes:
- id: per_route_timeouts
uri: https://example.org
predicates:
- name: Path
args:
pattern: /delay/{timeout}
metadata:
response-timeout: 200
connect-timeout: 200
per-route timeouts configuration using Java DSL
import static org.springframework.cloud.gateway.support.RouteMetadataUtils.CONNECT_TIMEOUT_ATTR;
import static org.springframework.cloud.gateway.support.RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR;
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeBuilder){
return routeBuilder.routes()
.route("test1", r -> {
return r.host("*.somehost.org").and().path("/somepath")
.filters(f -> f.addRequestHeader("header1", "header-value-1"))
.uri("http://someuri")
.metadata(RESPONSE_TIMEOUT_ATTR, 200)
.metadata(CONNECT_TIMEOUT_ATTR, 200);
})
.build();
}
流式路由API
为了在 Java 中实现简单的配置,RouteLocatorBuilder
bean 包含了一个流式的API。如下示例:
// static imports from GatewayFilters and RoutePredicates
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder, ThrottleGatewayFilterFactory throttle) {
return builder.routes()
.route(r -> r.host("**.abc.org").and().path("/image/png")
.filters(f ->
f.addResponseHeader("X-TestHeader", "foobar"))
.uri("http://httpbin.org:80")
)
.route(r -> r.path("/image/webp")
.filters(f ->
f.addResponseHeader("X-AnotherHeader", "baz"))
.uri("http://httpbin.org:80")
.metadata("key", "value")
)
.route(r -> r.order(-1)
.host("**.throttle.org").and().path("/get")
.filters(f -> f.filter(throttle.apply(1,
1,
10,
TimeUnit.SECONDS)))
.uri("http://httpbin.org:80")
.metadata("key", "value")
)
.build();
}
此样式还允许更多的自定义路由判断表达式(谓词断言)。通过 RouteDefinitionLocator
Bean 定义的判断表达式使用逻辑 and
进行组合。通过使用流式 Java API,可以在 Predicate 类上使用 and()
,or()
和 negate()
运算符。
DiscoveryClient Routes
DiscoveryClient 路由定义定位器
可以将网关配置为基于在 DiscoveryClient
兼容服务注册表中注册的服务(服务注册和服务发现)来创建路由。
要启用此配置,设置 spring.cloud.gateway.discovery.locator.enabled=true
,并确保在类路径上有一个 DiscoveryClient
实现(例如,Netflix Eureka,Consul,或 Zookeeper)且已启用。
为 DiscoveryClient 路由配置判断表达式和过滤器
默认情况下,Gateway 为使用 DiscoveryClient
创建的路由定义单个判断表达式(谓词)和过滤器。
默认判断表达式是用 /service ID/**
样式定义的路径判断表达式(path predicate ),其中 serviceId
来自DiscoveryClient 发现的服务的 ID。
默认过滤器是使用正则表达式 /serviceId/(?<remaining>.*)
和 /${remaining}
替换的重写路径过滤器(rewrite path filter)。在将请求转发到下游服务之前,将服务ID 从路径中剥离。
如果要自定义 DiscoveryClient
路由使用的 判断表达式 和 过滤器,设置 spring.cloud.gateway.discovery.locator.predicates[x]
和 spring.cloud.gateway.discovery.locator.filters[y]
,这样做时,如果要保留该功能(默认判断表达式和过滤器),则需要确保包含默认判断表达式和过滤器。如下示例:
spring.cloud.gateway.discovery.locator.predicates[0].name: Path
spring.cloud.gateway.discovery.locator.predicates[0].args[pattern]: "'/'+serviceId+'/**'"
spring.cloud.gateway.discovery.locator.predicates[1].name: Host
spring.cloud.gateway.discovery.locator.predicates[1].args[pattern]: "'**.foo.com'"
spring.cloud.gateway.discovery.locator.filters[0].name: Hystrix
spring.cloud.gateway.discovery.locator.filters[0].args[name]: serviceId
spring.cloud.gateway.discovery.locator.filters[1].name: RewritePath
spring.cloud.gateway.discovery.locator.filters[1].args[regexp]: "'/' + serviceId + '/(?<remaining>.*)'"
spring.cloud.gateway.discovery.locator.filters[1].args[replacement]: "'/${remaining}'"
注意:上面的配置来自官网,但属性名后的分隔符 :
应该为 =
。
跨域配置
可以配置 Gateway 控制跨域行为。全局 CORS
配置是一个 URL模式的 Map,配置类是 org.springframework.cloud.gateway.config.GlobalCorsProperties
,属性前缀是 spring.cloud.gateway.globalcors,属性名是 corsConfigurations
。 如下示例:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "https://docs.spring.io"
allowedMethods:
- GET
上面示例:CORS跨域请求配置是,允许来自 docs.spring.io 域名(主机)的所有 GET 请求,开放所有请求路径。
要为某些网关路由判断表达式(predicate:谓词)未处理的请求提供相同的 CORS 配置,设置spring.cloud.gateway.globalcors.add-To-simple-url-handler-mapping
属性设置为true
。若因路由判断表达式由于 HTTP 请求方式是 OPTIONS
而无法判断为 true
时,但又要支持 CORS 跨域请求,这非常有用。
TLS / SSL
网关可以通过遵循常规的 Spring server configuration 来监听 HTTPS 上的请求。如下所示:
application.yml
server:
ssl:
enabled: true
key-alias: scg
key-store-password: scg1234
key-store: classpath:scg-keystore.p12
key-store-type: PKCS12
可以将网关路由路由到 HTTP 和 HTTPS 后端。 如果要路由到 HTTPS 后端,则可以使用以下配置将网关配置为信任所有下游证书:
application.yml
spring:
cloud:
gateway:
httpclient:
ssl:
useInsecureTrustManager: true
使用不安全的信任管理器(trust manager)不适用于生产。 对于生产部署,可以使用以下配置为网关配置一组可以信任的已知证书:
application.yml
spring:
cloud:
gateway:
httpclient:
ssl:
trustedX509Certificates:
- cert1.pem
- cert2.pem
如果未为 Spring Cloud Gateway 提供受信任的证书,则使用默认的信任库(可以通过设置javax.net.ssl.trustStore
系统属性来覆盖它)。
TLS 握手
网关维护一个用于路由到后端的客户端池(client pool)。当通过 HTTPS 进行通信时,客户端发起 TLS 握手。许多超时与此握手关联。可以按如下方式配置这些超时(默认显示):
application.yml
spring:
cloud:
gateway:
httpclient:
ssl:
handshake-timeout-millis: 10000
close-notify-flush-timeout-millis: 3000
close-notify-read-timeout-millis: 0
Netty访问日志
要启用 响应式 Netty 访问日志,设置 -Dreactor.netty.http.server.accessLogEnabled=true
。
注意:必须是 Java 系统属性,而不是 Spring Boot 属性。
可以将日志记录系统(logging system)配置为具有单独的访问日志文件。 以下示例创建一个 Logback 配置:
<appender name="accessLog" class="ch.qos.logback.core.FileAppender">
<file>access_log.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="accessLog" />
</appender>
<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
<appender-ref ref="async"/>
</logger>
问题定位
使用 SpringCloudGateway 时可能出现的常见问题。
日志级别
Log Levels:以下 loggers 可能包含 DEBUG 和 TRACE 级别的有价值的故障排除信息:
- org.springframework.cloud.gateway
- org.springframework.http.server.reactive
- org.springframework.web.reactive
- org.springframework.boot.autoconfigure.web
- reactor.netty
- redisratelimiter
窃听:Wiretap
Reactor Netty HttpClient 和 HttpServer 可以启用窃听。 当将reactor.netty
日志级别设置为 DEBUG
或 TRACE
时,且开启了窃听,将开启信息的日志记录,例如,通过发送和接收的 header 和 body。
要启用 wiretap,需为 httpserver设置 spring.cloud.gateway.httpserver.wiretap=true;为 httpclient 设置spring.cloud.gateway.httpclient.wiretap=true。
开发人员指南
下面是写编写网关的自定义组件的基本指南。
自定义路由判断表达式工厂
要自定义路由判断表达式(Route Predicate),需要实现 RoutePredicateFactory
接口,可以继承一个名为 AbstractRoutePredicateFactory
的抽象类。
MyRoutePredicateFactory.java
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<HeaderRoutePredicateFactory.Config> {
public MyRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
// grab configuration from Config object
return exchange -> {
//grab the request
ServerHttpRequest request = exchange.getRequest();
//take information from the request to see if it
//matches configuration.
return matches(config, request);
};
}
public static class Config {
//Put the configuration properties for your filter here
}
}
自定义 GatewayFilter工厂
要写一个 GatewayFilter
,必须实现 GatewayFilterFactory
。可以继承一个名为 AbstractGatewayFilterFactory
的抽象类。如下示例:
PreGatewayFilterFactory.java:前置过滤器
public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory<PreGatewayFilterFactory.Config> {
public PreGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
// grab configuration from Config object
return (exchange, chain) -> {
//If you want to build a "pre" filter you need to manipulate the
//request before calling chain.filter
ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
//use builder to manipulate the request
return chain.filter(exchange.mutate().request(request).build());
};
}
public static class Config {
//Put the configuration properties for your filter here
}
}
PostGatewayFilterFactory.java:后置过滤器
public class PostGatewayFilterFactory extends AbstractGatewayFilterFactory<PostGatewayFilterFactory.Config> {
public PostGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
// grab configuration from Config object
return (exchange, chain) -> {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
//Manipulate the response in some way
}));
};
}
public static class Config {
//Put the configuration properties for your filter here
}
}
自定义全局过滤器
自定义全局过滤器(global filter),必须实现 GlobalFilter
接口,这会应用于所有请求的过滤。
下面示例全局前置和后置过滤器:
@Bean
public GlobalFilter customGlobalFilter() {
return (exchange, chain) -> exchange.getPrincipal()
.map(Principal::getName)
.defaultIfEmpty("Default User")
.map(userName -> {
//adds header to proxied request
exchange.getRequest().mutate().header("CUSTOM-REQUEST-HEADER", userName).build();
return exchange;
})
.flatMap(chain::filter);
}
@Bean
public GlobalFilter customGlobalPostFilter() {
return (exchange, chain) -> chain.filter(exchange)
.then(Mono.just(exchange))
.map(serverWebExchange -> {
//adds header to response
serverWebExchange.getResponse().getHeaders().set("CUSTOM-RESPONSE-HEADER",
HttpStatus.OK.equals(serverWebExchange.getResponse().getStatusCode()) ? "It worked": "It did not work");
return serverWebExchange;
})
.then();
}
使用Spring MVC或Webflux构建一个简单的Gateway
注意:以下描述了替代样式的网关。不同于先前文档中的描述。
Spring Cloud Gateway 提供了一个名为 ProxyExchange 的实用对象。可以在常规 Spring web handler(Web 处理器)中使用它作为方法参数。
它通过镜像 HTTP 动词的方法来支持基本的下游 HTTP Exchange。对于 MVC,它还支持通过 forward() 方法转发到本地处理器。要使用 ProxyExchange
,需要类路径中引入正确的模块(spring-cloud-gateway-mvc
或spring-cloud-gateway-webflux
)。
以下 MVC 示例将 请求 代理到 /test
下游远程服务器:
@RestController
@SpringBootApplication
public class GatewaySampleApplication {
@Value("${remote.home}")
private URI home;
@GetMapping("/test")
public ResponseEntity<?> proxy(ProxyExchange<byte[]> proxy) throws Exception {
return proxy.uri(home.toString() + "/image/png").get();
}
}
下面是 Webflux 的一个相同的示例:
@RestController
@SpringBootApplication
public class GatewaySampleApplication {
@Value("${remote.home}")
private URI home;
@GetMapping("/test")
public Mono<ResponseEntity<?>> proxy(ProxyExchange<byte[]> proxy) throws Exception {
return proxy.uri(home.toString() + "/image/png").get();
}
}
ProxyExchange 上的便捷方法使处理器(handler)方法可以发现并增强传入请求的 URI 路径。
例如,可能希望提取路径的后缀以将它们传递到下游:
@GetMapping("/proxy/path/**")
public ResponseEntity<?> proxyPath(ProxyExchange<byte[]> proxy) throws Exception {
String path = proxy.path("/proxy/path/");
return proxy.uri(home.toString() + "/foos/" + path).get();
}
网关处理程序方法可以使用 Spring MVC 和 Webflux 的所有功能。 结果,例如,可以注入请求标头和查询参数,并且可以使用映射批注中的声明来约束传入的请求。 更多参考 Spring MVC 中有关 @RequestMapping
的文档。
可以使用 ProxyExchange 上的header()
方法将 HTTP 头添加到下游服务的响应中。
还可以通过将映射器(mapper)添加到get()
方法(和其他方法)来操纵响应头(以及响应中您需要的任何其他内容)。 映射器是一种函数,它接受传入的 ResponseEntity
并将其转换并传出。
为不将敏感头(sensitive)(默认为 cookie
和 authorization
)和代理(proxy)(x-forwarded-*
)头传递到下游服务提供了一流支持。
注意:本文归作者所有,未经作者允许,不得转载