Spring Cloud Gateway介绍(一)
GlobalFilter与GatewayFilter具有一样的接口。
这些全局过滤器都有对应的配置类,例如:
org.springframework.cloud.gateway.config.GatewayAutoConfigurationorg.springframework.cloud.gateway.config.GatewayMetricsAutoConfigurationorg.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration
当一个Request匹配路由,则会把所有全局Filter和GatewayFilter组合成一个Filter Chain,并通过org.springframework.core.Ordered接口进行排序,数值越小优先级越高,高优先级在pre阶段排在前面,在post阶段排在后面。
用于本地forward,也就是将请求在Gateway服务内进行转发,而不是转发到下游服务。
当请求进来时,ForwardRoutingFilter 会查看一个URL,该URL为 exchange 属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果该 url 的 scheme(协议) 是 forward(例如:forward://localendpoint),那么该Filter会使用Spirngd的DispatcherHandler 来处理这个请求。该请求的URL路径部分,会被forward URL中的路径覆盖掉。而未修改过的原始URL,会被追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 属性中。
这个Filter是用来整合Ribbon的,其核心就是解析 scheme 为lb的 url,以此获取微服务的名称,然后再通过Ribbon获取实际的调用地址。
当请求进来时,LoadBalancerClientFilter 会查看一个URL,该URL为 exchange 的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果该 url 的 scheme 是 lb,(例如:lb://myservice ),那么该Filter会使用Spring Cloud的 LoadBalancerClient 来将 myservice 解析成实际的host 和 port ,并替换掉原本 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性的值。而原始 url 会追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 属性中。该过滤器还会查看 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR 属性,如果发现该属性的值是 lb ,也会执行相同逻辑。
spring: cloud: gateway: routes: - id: myRoute uri: lb://service predicates: - Path=/service/**默认情况下,如果无法通过 LoadBalancer 找到指定服务的实例,那么会返回503(如上配置示例, 若 LoadBalancer 找不到名为 service 的实例时,就会返回503);可使用配置: spring.cloud.gateway.loadbalancer.use404=true ,让其返回404。
LoadBalancer 返回的 ServiceInstance 的 isSecure 的值,会覆盖请求的scheme。举个例子,如果请求打到Gateway上使用的是 HTTPS ,但 ServiceInstance 的 isSecure 是false,那么下游微服务接收到的则是HTTP请求,反之亦然。另外,如果该路由指定了 GATEWAY_SCHEME_PREFIX_ATTR 属性,那么前缀将会被剥离,并且路由URL中的scheme会覆盖 ServiceInstance 的配置。
LoadBalancerClientFilter 默认使用的是一个阻塞式的LoadBalancerClient ,建议使用ReactiveLoadBalancerClientFilter来替换,可以通过设置spring.cloud.loadbalancer.ribbon.enabled=false来切换。
功能同LoadBalancerClientFilter 。
当请求进来时,NettyRoutingFilter 会查看一个URL,该URL是 exchange 的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果该 url 的 scheme 是 http 或 https ,那么该Filter会使用 Netty 的 HttpClient 向下游的服务发送代理请求。获得的响应将放在 exchange 的 ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR 属性中,以便在后面的 Filter 里使用。(有一个实验性的过滤器: WebClientHttpRoutingFilter 可实现相同功能,但无需Netty)
NettyWriteResponseFilter 用于将代理响应写回网关的客户端侧,所以该过滤器会在所有其他过滤器执行完成后才执行,并且执行的条件是 exchange 中 ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR 属性的值不为空,该值为 Netty 的 Connection 实例。(有一个实验性的过滤器: WebClientWriteResponseFilter 可实现相同功能,但无需Netty)。
这个过滤器用于将从request里获取的原始url转换成Gateway进行请求转发时所使用的url。当请求进来时,RouteToRequestUrlFilter 会从 exchange 中获取 ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR 属性的值,该值是一个 Route 对象。若该对象不为空的话,RouteToRequestUrlFilter 会基于请求 URL 及 Route 对象里的 URL 来创建一个新的 URL。新 URL 会被放到 exchange 的 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性中。 如果 URL 具有 scheme 前缀,例如 lb:ws://serviceid ,该 lb scheme将从URL中剥离,并放到 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR 中,方便后面的过滤器使用。
该过滤器的作用与 NettyRoutingFilter 类似。当请求进来时,WebsocketRoutingFilter 会查看一个URL,该URL是 exchange 中 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性的值,如果该 url 的 scheme 是 ws 或者 wss,那么该Filter会使用 Spring Web Socket 将 Websocket 请求转发到下游。 另外,如果 Websocket 请求需要负载均衡的话,可为URL添加 lb 前缀以实现负载均衡,例如 lb:ws://serviceid 。
pring: cloud: gateway: routes: # SockJS route - id: websocket_sockjs_route uri: http://localhost:3001 predicates: - Path=/websocket/info/** # Normal Websocket route - id: websocket_route uri: ws://localhost:3001 predicates: - Path=/websocket/**想要启用Gateway Metrics Filter,需在项目中添加 spring-boot-starter-actuator 依赖,然后在配置文件中配置 spring.cloud.gateway.metrics.enabled 的值为true。该过滤器会添加名为 gateway.requests 的时序度量(timer metric),其中包含以下标记:
routeId:路由IDrouteUri:API将路由到的URIoutcome:由 HttpStatus.Series 分类status:返回给客户端的Http StatushttpStatusCode:返回给客户端的请求的Http StatushttpMethod:请求所使用的Http方法 这些指标暴露在 /actuator/metrics/gateway.requests 端点中,并且可以轻松与 Prometheus 整合,从而创建一个 Grafana dashboard。当一个请求走完整条过滤器链后,负责转发请求到下游的那个过滤器会在 exchange 中添加一个 gatewayAlreadyRouted 属性,从而将 exchange 标记为 routed(已路由)。一旦请求被标记为 routed ,其他路由过滤器将不会再次路由该请求,而是直接跳过。 了解了以上所有内置的全局过滤器后,我们知道不同协议的请求会由不同的过滤器转发到下游。所以负责添加这个gatewayAlreadyRouted 属性的过滤器就是最终负责转发请求的过滤器:
http、https请求会由NettyRoutingFilter或WebClientHttpRoutingFilter添加这个属性forward请求会由ForwardRoutingFilter添加这个属性websocket请求会由WebsocketRoutingFilter添加这个属性 这些过滤器调用了以下方法将 exchange 标记为 routed ,或检查 exchange 是否是 routed:ServerWebExchangeUtils.isAlreadyRouted:检查exchange是否为routed状态ServerWebExchangeUtils.setAlreadyRouted:将exchange设置为routed状态 简单来说,就是Gateway通过 gatewayAlreadyRouted 属性表示这个请求已经转发过了,而无需其他过滤器重复路由,从而防止重复的路由转发。HttpHeadersFilters 在请求往下游发送之前应用到headers,例如:NettyRoutingFilter。
添加Fowarded header,或者添加host header,当前请求的schema,port到已存在Fowarded header。
从一个已Fowarded的请求中删除某些header,默认值(来源于IETF)为:
Connection
Keep-Alive
Proxy-Authenticate
Proxy-Authorization
TE
Trailer
Transfer-Encoding
Upgrade
可以通过设置spring.cloud.gateway.filter.remove-non-proxy-headers.headers来修改默认列表。
类似 Fowarded,添加X-Forwarded-*。
通过以下属性控制(默认为true):
spring.cloud.gateway.x-forwarded.for-enabled
spring.cloud.gateway.x-forwarded.host-enabled
spring.cloud.gateway.x-forwarded.port-enabled
spring.cloud.gateway.x-forwarded.proto-enabled
spring.cloud.gateway.x-forwarded.prefix-enabled
可以通过设置以下属性(默认为true)来控制是否append:
spring.cloud.gateway.x-forwarded.for-append
spring.cloud.gateway.x-forwarded.host-append
spring.cloud.gateway.x-forwarded.port-append
spring.cloud.gateway.x-forwarded.proto-append
spring.cloud.gateway.x-forwarded.prefix-append
gateway可以通过以下配置监听HTTPS请求:
server: ssl: enabled: true key-alias: scg key-store-password: scg1234 key-store: classpath:scg-keystore.p12 key-store-type: PKCS12网关路由可以路由到 http 和 https 后端。如果路由到 https 后端,则可以将网关配置为信任具有以下配置的所有下游证书:
spring: cloud: gateway: httpclient: ssl: useInsecureTrustManager: true使用不安全的信任管理器不适合生产。对于生产部署,可以使用以下配置配置一组可信任的已知证书:
spring: cloud: gateway: httpclient: ssl: trustedX509Certificates: - cert1.pem - cert2.pem如果Spring Cloud Gateway未配置可信证书,则使用默认信任库(可以使用系统属性javax.net.ssl.trustStore覆盖)。
网关维护一个客户端池,用于路由到后端。通过https进行通信时,客户端会启动TLS握手。这次握手会有很多超时。可以配置这些超时(显示默认值):
cloud: gateway: httpclient: ssl: handshake-timeout-millis: 10000 close-notify-flush-timeout-millis: 3000 close-notify-read-timeout-millis: 0Spring Cloud Gateway 的配置是由一系列的 RouteDefinitionLocator 类来管理的。
public interface RouteDefinitionLocator { Flux<RouteDefinition> getRouteDefinitions(); }默认情况下,PropertiesRouteDefinitionLocator 使用 Spring Boot 的@ConfigurationProperties 机制加载属性。
上面的配置示例都使用了一个使用位置参数而不是命名参数的快捷符号。以下两个例子是等效的:
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对于网关的一些用法,属性是足够的,但是一些生产用例将受益于从外部源(例如数据库)加载配置。未来的里程碑版本将RouteDefinitionLocator基于Spring Data Repositories实现,例如:Redis,MongoDB和Cassandra。
可以使用metadata为每个route增加附加属性。
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);connect-timeout :单位:毫秒.response-timeout:单位:毫秒
- id: per_route_timeouts uri: https://example.org predicates: - name: Path args: pattern: /delay/{timeout} metadata: response-timeout: 200 connect-timeout: 200JAVA代码:
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(); }为了允许在Java中进行简单配置,RouteLocatorBuilder 中定义了一个流式的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 定义的谓词使用逻辑与组合。通过使用流式的 Java API,您可以在 Predicate 类上使用and(),or()和 negate()运算符。
可以将网关配置为基于在 DiscoveryClient 兼容服务注册中心注册的服务来创建路由。要启用此功能,请设置 spring.cloud.gateway.discovery.locator.enabled = true 并确保DiscoveryClient 实现类位于classpath下并已启用(例如 Netflix Eureka,Consul 或Zookeeper)。
默认情况下,DiscoveryClient创建的Routes仅定义了一个predicate和filer,
predicates: - Path=/serviceId/** filters: - RewritePath=/serviceId/(?<remaining>.*), /$\{remaining}如果想定义多个,则通过以下配置:
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}'"网关可以通过配置控制 CORS 行为。 “全局”CORS 配置是 Spring Framework CorsConfiguration的 URL 模式映射。
spring: cloud: gateway: globalcors: cors-configurations: '[/**]': allowedOrigins: "https://docs.spring.io" allowedMethods: - GET在上面的示例中,对于所有 GET 请求的路径,将允许来自 docs.spring.io 的请求的 CORS请求。
Spring Cloud Gateway提供了一个实用程序对象ProxyExchange,可以在常规Spring Web处理程序中将其用作方法参数。它通过反射HTTP谓词的方法支持基本的下游HTTP交换。使用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(); } }Flux:
@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(); } }
Spring MVC 或 Webflux 的所有特性都可用于 Gateway 处理方法。例如,您可以注入请求头和查询参数,并且您可以使用映射注释中的声明来约束传入的请求。
可以使用 ProxyExchange 上的 header()方法将头信息添加到下游响应中。
您还可以通过向 get()等方法添加映射器来操作响应头信息(以及响应中你喜欢的任何其他内容)。映射器是一个函数,它接收传入的 ResponseEntity 并将其转换为传出的ResponseEntity。
最高优先级的类支持处理让"sensitive"敏感头信息(默认情况下是"cookie"和"authorization")不往下游传递,并且对于"proxy"代理头信息处理为(x-forwarded-*)。
属性设置附录:
https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/appendix.html
https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/#actuator-api
https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/#recap-the-list-of-all-endpoints