+
专题寄语
Spring Cloud Gateway 应用参考(版本 2.2.2.RELEASE)
ID:26,
创建:2020-03-11 23:48,
更新:2023-01-06 10:59,
版本:20,
排序号:100
## 构建springboot gateway应用 参考资料:http://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.2.RELEASE/reference/html/ 我的项目直接从start.spring.io中生成 被代理的服务是通过启动一个docker服务并设置host实现的。 ``` docker run -p 80:80 kennethreitz/httpbin ``` 基于Spring5, Springboot2, Project Reactor。目标:简单、高效、安全、可监控、可伸缩 ### 1 引入 直接引入starter。可在start.spring.io中生成。 在引入starter的前提下,可以使用如下属性禁用 ```properties spring.cloud.gateway.enabled=false ``` 警告: (1)gateway基于异步框架啊,常用的同步框架没有应用如:Spring Data and Spring Security (2)并不是建立在servlet之上的 ### 2 名词 #### Route: 路由: 一个完整的规则和作用描述,包括id,目标uri,断言和过滤器 #### Predicate: 断言:判断是否应用路由的依据 #### Filter: 过滤器: 干预行为具体的实施者(头信息,参数等,提交内容,返回内容) ### 3 工作原理 如下图 Mapping 负责根据路由规则路由,Handler负责驱动在链条中的各filter  ### 4 配置路由断言和过滤器的方式 #### 4.1 简写方式 ```yaml spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: # 格式 filter-name=key,value - Cookie=mycookie,mycookievalue ``` #### 4.2 全写方式 ```yaml spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: # filter name - name: Cookie # value in args pairs args: name: mycookie regexp: mycookievalue ``` ### 5 路由断言工厂 主要介绍一些内建的断言用法 #### 5.1 After 断言 指定时间之后的,路由到目标地址 ```yaml spring: cloud: gateway: routes: # 名称,其实是可以随便起的,不过最好贴近实际意义 - id: after_route # 目标 uri: http://httpbin.org predicates: # 简写方法 ZonedDateTime 实力格式 - After=2020-03-10T21:37:01.545+08:00[Asia/Shanghai] ``` #### 5.2 Before 断言 指定时间之前的,路由到目标地址 ```yaml # ... routes: - id: before_route uri: http://example.org predicates: - Before=2017-01-20T17:42:47.789-07:00[America/Denver] ``` #### 5.3 Between 断言 介于指定时间之间的,路由到指定地址 ```yaml # ... routes: - id: between_route uri: http://example.org predicates: - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] ``` #### 5.4 Cookie 断言 访问测试 curl -v --cookie "chocolate=ch.p" http://localhost:8088/get ```yaml # ... routes: - id: cookie_route uri: https://example.org predicates: # cookie中存在chocolate=ch.p的 - Cookie=chocolate, ch.p ``` #### 5.5 Header 断言 访问测试 curl -v -H "X-Request-Id: 1234" http://localhost:8088/get ```yaml # ... routes: - id: header_route uri: http://httpbin.org predicates: # X-Request-Id是任意数字的情况 - Header=X-Request-Id, \d+ ``` #### 5.6 Host 断言 访问测试 curl -v -H "Host: www.somehost.org" http://localhost:8088/get ```yaml # ... 由于前篇一律,我只写和上面有区别的部分,后面相同 # 符合 Host: www.somehost.org即可路由到此 - Host=**.somehost.org,**.anotherhost.org ``` #### 5.7 Method 断言 即请求方法断言,测试方法 curl -v -X POST http://mainhost:8088/anything ```yaml # methid是get 和 post 才会路由到目标地址 - Method=GET,POST ``` #### 5.8 Path 路径断言 由于我在虚拟机上的shell控制台访问本机的windows电脑,特地设置了hosts为mainhoust,后面大多用这个 测试方法 curl -v http://mainhost:8088/anything curl -v http://mainhost:8088/base64/SFRUUEJJTiBpcyBhd2Vzb21l curl -v http://mainhost:8088/links/10/1 ```yaml # /anything 严格匹配 # /base64/{segment} 匹配后,可以segment当一个变量 # /links/** 匹配所有以 /links/开头的 - Path=/anything,/base64/{segment},/links/** ``` segment会最为变量放在xchange环境中,后期的filter中可获取应用。如下 ```java Map<String, String> uriVariables = ServerWebExchangeUtils.getPathPredicateVariables(exchange); String segment = uriVariables.get("segment"); ``` #### 5.9 Query 参数路由 测试方法 curl -v http://mainhost:8088/get?green=green ```yaml # 带green参数即路由 # Query=green 表示只要带这个参数即可, Query=green, gree.中的gree.表示参数的值,且是一个正则表达式,green,greet - Query=green, gree. ``` #### 5.10 RemoveAddr 断言 根据头信息中的远程地址断言 curl -v -H "RemoteAddr=192.168.1.10" http://mainhost:8088/get ```yaml # 标志远程地址是 192.168.1.10这样的ip可应用此路由 # 实际应用用因为要经过层层代理,所以一般用头信息,只有在内部网络才有可能用到这种情况 - RemoteAddr=192.168.1.1/24 ``` #### 5.11 Weight 权重断言 ```yaml # 暂时没有测试,这个好像可以进行灰度测试(通过多个predicate的组合) 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 ``` #### 5.12 默认获取remote addr是通过request获取,这在proxy之后是获取不到的。 解决方案是可以实现一个RemoteAddressResolver。spring cloud gateway本身也自带一个XForwardedRemoteAddressResolver可以应用 XForwardedRemoteAddressResolver有两个构造方法 - trustAll 取X-Forwarded-For中的第一个 - maxTrustedIndex 从X-Forwarded-For中最后一个为起点,按照数值开始取开始取(通常是多个代理应用的时候用) 使用java代码进行设置 ```java RemoteAddressResolver resolver = XForwardedRemoteAddressResolver .maxTrustedIndex(1); ... .route("direct-route", r -> r.remoteAddr("10.1.1.1", "10.10.1.1/24") .uri("https://downstream1") .route("proxied-route", r -> r.remoteAddr(resolver, "10.10.1.1", "10.10.1.1/24") .uri("https://downstream2") ) ``` 下面是一个测试用例 测试方式 curl -v -H "X-Forwarded-For: 192.169.1.10" http://mainhost:8088/get curl -v http://mainhost:8088/get?green application.yml ```yaml server: port: 8088 spring: cloud: gateway: routes: - id: very uri: http://httpbin.org predicates: # 带green参数即路由 - Query=green ``` GatewayConfig.java 经过测试,代码和配置文件的配置会被合并,共同起作用 ```java package fun.zhangyt.train.gateway.conf; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.support.ipresolver.RemoteAddressResolver; import org.springframework.cloud.gateway.support.ipresolver.XForwardedRemoteAddressResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GatewayConfig { @Bean public RouteLocator myRoutes(RouteLocatorBuilder builder) { RemoteAddressResolver resolver = XForwardedRemoteAddressResolver .maxTrustedIndex(1); return builder.routes() .route("proxied-route", p -> p .remoteAddr(resolver, "192.169.1.1/24") .uri("http://httpbin.org")) .build(); } } ``` ### 6 GatewayFilter 工厂。参与切入行为的过滤器们 可更改请求和返回数据。针对特定的route #### 6.1 AddRequestHeader 过滤器 增加头信息 一个简单却不实用的示例 测试方式 curl -v http://mainhost:8088/get ```yaml spring: cloud: gateway: routes: - id: add_request_header_route uri: http://httpbin.org # 必须有断言,否则报错 predicates: - Path=/** filters: # 只静态的加头信息,貌似没什么用哈 - AddRequestHeader=X-Request-red, blue ``` 最有用的方式其实是可以动态设置变量信息。类似如下方式 ```yaml predicates: # 定义了两个变量 - Path=/anything/{token}/{num} filters: # 使用变量设置头信息值 - AddRequestHeader=X-Request-Red, Blue-{token}-{num} ``` #### 6.2 AddRequestParameter 过滤器(以下内容中,没有写测试方法的,基本没具体测试,因为都是依据官方文档而来,基本没问题) 就是增加请求的query参数,也可以应用断言中的path或者host中的变量,类似如下 ```yaml predicates: - Host: {segment}.myhost.org filters: - AddRequestParameter=foo, bar-{segment} ``` #### 6.3 AddResponseHeader 过滤器 这个是给返回的内容加头信息,同样也可以运用路径或这头信息变量 测试方式 curl -v -H "Host: www.myhost.org" http://mainhost:8088/anything/123 ```yaml predicates: - Host={segment}.myhost.org filters: - AddResponseHeader=foo, bar-{segment} ``` #### 6.4 DedupeResponseHeader 过滤器 对返回的信息去重操作 ```yaml filters: - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin ``` #### 6.5 Hystrix 断路器过滤器 本例使用的依旧是netflix的断路器(默认的),注意在pom中引入 测试方式(最后数字表示模拟延时的秒数) curl -v http://mainhost:8088/delay/3 ```yaml predicates: - Path=/delay/** filters: - Hystrix=myCommandName ``` 可以如下调整断路器时间来测试 ```yaml # 设置一下断路器超时时间,默认是1秒 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 5000 ``` 下面是一个指定超时跳转目标的示例 测试方式是使用浏览器访问 http://localhost:8088/delay/5 curl 也可以 curl -v http://mainhost:8088/delay/3 ```yaml - id: any uri: http://httpbin.org predicates: - Path=/delay/** filters: # 指定过滤器的名称 - name: Hystrix args: name: fallbackcmd fallbackUri: forward:/get # 指定一个匹配的forward路由否则还是会报网关超时异常,匹配路由的有点就是把错误处理可以跳转到其它的微服务上去 # 当然这个也可以不设置,只要网关定义了controller匹配forward的目标即可 - id: get uri: http://httpbin.org predicates: - Path=/get ``` 如果是在网关内自定义的callback,可通过ServerWebExchangeUtils.HYSTRIX_EXECUTION_EXCEPTION_ATTR对应的值获取相关信息 对于用外部地址作为跳转地址的,可以加一个头信息过滤器,把异常信息添加到头信息中 TODO 本节中官方文档提到的两个重点没有涉及,第一就是基于有注册服务器的负载均衡应用时的方式,第二就是rewrite的模块没有测试 #### 6.6 CircuitBreaker 断路器 该断路器后端默认支持hystrix和Resilience4J两种,官方推荐Resilience4J(以后有时间需要研究下),但pom默认引入的还是hystrix 运行该例子需要在pom中引入reactive Resilience4J 测试方法 curl -v http://mainhost:8088/delay/3 ```yaml predicates: - Path=/delay/** filters: - CircuitBreaker=myCircuitBreaker ``` Resilience4J 提到了reactive模式的支持,我搜索了解了一下spring的响应式编程框架webflux。核心思想是非阻塞式时间编程模式。 spring cloud gateway 默认用netty支持。也可用undertow或jetty支持 说优点是提升吞吐量和伸缩性,适用于IO密集型应用如网关(那普通业务应用就可以不用了,毕竟不好调试) #### 一个补充内容,应用注册中心,然后配置负载均衡的方式使用 注册中心参考 https://www.wavesxa.com/mycontent/blog/ReaderServlet/viewContentListWith_1895 application.yml中配置目标uri如下 ```yaml # 使用ribbon负载均衡 uri: lb://httpbin ``` #### 6.7 FallbackHeaders 过滤器 测试方式 curl -v http://mainhost:8088/delay/3 默认填充的头信息key值如下,可再配置中自行更改,比如下面的示例中我就更改了executionExceptionTypeHeaderName executionExceptionTypeHeaderName ("Execution-Exception-Type") executionExceptionMessageHeaderName ("Execution-Exception-Message") rootCauseExceptionTypeHeaderName ("Root-Cause-Exception-Type") rootCauseExceptionMessageHeaderName ("Root-Cause-Exception-Message") ```yaml - id: any uri: lb://httpbin predicates: - Path=/delay/** filters: - name: CircuitBreaker args: name: fetchTimeoutException # 定义出现断路跳转的url fallbackUri: forward:/get - id: fallback uri: lb://httpbin predicates: - Path=/get filters: # 把断路器的异常信息加入头信息 - name: FallbackHeaders args: executionExceptionTypeHeaderName: Test-Exception-Info-Header ``` #### 6.8 MapRequestHeader 过滤器 规则:把原来的头信息的key更换成指定的新的key。如果原头信息不存在,则忽略,如果新的头信息key已经存在,则更新其值。 ```yaml filters: - MapRequestHeader=Blue, X-Request-Red ``` #### 6.9 PrefixPath 过滤器 给原来的请求增加前缀。这个在服务分组的时候可能用到。 测试方式 curl -v http://mainhost:8088/hello ```yaml - id: any uri: lb://httpbin predicates: - Path=/** filters: - PrefixPath=/anything ``` #### 6.10 PreserveHostHeader 过滤器 是否保留原来请求的host头信息 ```yaml filters: - PreserveHostHeader ``` #### 6.11 RequestRateLimiter 过滤器 请求频率限制过滤器,如果超过限制,会返回“HTTP 429 - Too Many Requests” 官方文档中主要提到了接口KeyResolver以及默认的接口实现PrincipalNameKeyResolver 可以通过“spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key(true or false)”来确定没有key的是否处理。 通过“spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code”,设置返回的key。 #### 6.11.1 redis rate limiter 使用令牌桶的算法的方式实现(个人感觉还得依赖redis,也挺让人烦的) 测试方式,直接在浏览器狂刷 http://mainhost:8088/anything/1/2 会发现有429的错误返回 首先需要引入依赖 ```xml <!-- 限流配置用redis, 不能正常引用将报 Unable to find GatewayFilterFactory with name RequestRateLimiter 异常 需要指定版本,因为父项目中并不能直接赋予 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> <version>2.2.5.RELEASE</version> </dependency> ``` 接着需要写一个标志客户端的KeyResolver,我直接写在了本系列示例的GatewayConfig.java ```java /** 限流应用的bean, 应用客户端地址标志唯一客户。在实际生产环境中,获取远程地址的方式一般会用头信息中的host,因为一般都会放在其它网关或者负载均衡器后 */ @Bean KeyResolver userKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); } ``` 然后需要有如下配置 ```yaml spring: cloud: gateway: routes: - id: any uri: lb://httpbin predicates: - Path=/** filters: - name: RequestRateLimiter args: # 这个决定了1个用户在1秒内有多少次请求。就是令牌桶被填充的频率 redis-rate-limiter.replenishRate: 1 # 这个决定1个用户在1秒内可能的最大请求数。也就是令牌桶数量上限,如果这个设置为0,则会拒绝所有请求 redis-rate-limiter.burstCapacity: 2 # 这个定义每次请求消耗的令牌数,默认值就是1。(如果变更,则可自己计算每秒最大请求数了) redis-rate-limiter.requestedTokens: 1 # 限流配置用redis spring.redis: host: 192.168.56.102 port: 6379 database: 0 max-active: 8 max-wait: -1 max-idle: 8 min-idle: 0 timeout: 5000 ``` #### 6.12 RedirectTo 过滤器 重写过滤器,这个应该在其它的网关产品中也经常用。上面的例子中也有挂上的,不过被我过滤掉了 测试方式。在浏览器访问 http://localhost:8088/get ```yaml routes: - id: any uri: lb://httpbin predicates: # 我在这里想多配置一个Path的过滤器看来是不行了,应该是过滤器类型相同的简写方式,标志的名称也相同 - Path=/get filters: # 跳转到网关内部可路由或者是自己的controller地址,这里跳转到下面anymore这个路由配置 - RedirectTo=302, /anything/hello/redirect # 跳转到一个完整的url,经测试也是比较成功的 #- RedirectTo=302, https://www.wavesxa.com - id: anymore uri: lb://httpbin predicates: - Path=/anything/** ``` #### 6.13 RemoveRequestHeader 过滤器 可以删除指定的头信息(如敏感信息) ```yaml - RemoveRequestHeader=X-Request-Foo ``` #### 6.14 RemoveResponseHeader 过滤器 删除指定的返回信息 ```yaml - RemoveResponseHeader=X-Response-Foo ``` 官方资料中介绍可以通过在spring.cloud.gateway.default-filters中配置,应用到所有的路由器上 #### 6.15 RemoveRequestParameter 过滤器 删除一个url中的请求参数 ```yaml - RemoveRequestParameter=red ``` #### #### 6.16 RewritePath 过滤器 重写路径,这种功能在nginx和apache中都支持 两个参数regexp和replacement ```yaml # 例如会把 /anything/red/blue 变成 /anything/blue - RewritePath=/anything/red/(?<segment>/?.*), /anything/$\{segment} ``` 注意在yaml规范中 $需要用$\表示 #### 6.17 RewriteLocationResponseHeader 过滤器 有待验证,参考官方资料 #### 6.18 RewriteResponseHeader 过滤器 更改返回的头信息。使用3个参数 name, regexp, 和 replacement 明明是3个参数,不知道官方示例中为什么有3个都好隔开的4个参数。 测试结果是3个参数和4个参数都可以使用。 经过看源代码,发现就3个参数,所以以后就用3个参数吧 ```yaml # 会把返回的头信息的server值gunicorn/19.9.0替换为gateway - RewriteResponseHeader=Server, , gunicorn/19.9.0, gateway # 下面这个会把/42?user=ford&password=omg!what&flag=true变成/42?user=ford&password=***&flag=true # - RewriteResponseHeader=X-Response-Red, , password=[^&]+, password=*** ``` #### 6.19 SaveSession 过滤器 有待考察,官方资料说是在用spring session时需要使用(还有spring security 应用 spring session时比较关键)。 参考官方资料 #### 6.20 SecureHeaders 有待考察,参考官方资料 #### 6.21 SetPath 过滤器 利用spring框架的uri模板来分段设置path。 url并不会跳转,而是直接把请求转到被改变的目标地址 测试方式 curl -v http://mainhost:8088/anything/v1/hello ```yaml predicates: - Path=/anything/v1/{segment} filters: - SetPath=/anything/v2/{segment} ``` #### 6.22 SetRequestHeader 过滤器 替换所有同名的header的值 两个参数 name和value 测试方式 curl -v http://mainhost:8088/anything/v1/hello ```yaml filters: - SetRequestHeader=X-Request-Red, Blue ``` 带变量的测试 curl -v -H "Host: www.myhost.org" http://mainhost:8088/anything/hello ```yaml predicates: - Path=/anything/{segment1} - Host={segment2}.myhost.org filters: - SetRequestHeader=foo, bar-{segment1}-{segment2} ``` #### 6.23 SetResponseHeader 过滤器 增加或者替换掉同名head的值。 下面是一个直接带变量的测试 curl -v -H "Host: www.myhost.org" http://mainhost:8088/anything/hello ```yaml predicates: - Path=/anything/{segment1} - Host={segment2}.myhost.org filters: - SetResponseHeader=foo, bar-{segment1}-{segment2} ``` #### 6.24 SetStatus 过滤器 设置http返回码过滤器。值必须是合法的http status数字或位子描述(如404,NOT_FOUND) 单独参数 status 测试方法 curl -v http://mainhost:8088/anything/hello ```yaml filters: # 无论何时,都返回 401 (无权限) - SetStatus=401 ``` 同时可以在返回的头信息中加入被代理的原返回码 ```yaml spring: cloud: gateway: set-status: original-status-header-name: original-http-status ``` #### 6.25 StripPrefix 过滤器 忽略前缀(段)。一个参数parts,代表忽略的段的数量 测试方式 curl -v http://mainhost:8088/anything/a/get ```yaml predicates: - Path=/anything/a/get filters: - StripPrefix=2 ``` #### 6.26 Retry 过滤器 重试过滤器。这个参数比较多,如下 - retries: 需要重试的次数,默认值为 3 - statuses:指定需要重试的 http status code - methods: 指定需要重试的方法,默认值 get - series: 那些系列的状态码需要重试,默认值 5XX - exceptions: 那些异常需要重试,默认值 IOException and TimeoutException - backoff: 重试时间间隔设置。计算公式 firstBackoff * (factor ^ n),如果设置了maxBackoff,那他将是最长时间区间 如果basedOnPreviousValue,那计算公式为prevBackoff * factor。默认值disabled 测试方法 curl -v http://mainhost:8088/status/502 ```yaml filters: - name: Retry args: retries: 3 # org.springframework.http.HttpStatus #statuses: 5XX # org.springframework.http.HttpStatus.Series series: SERVER_ERROR methods: GET,POST backoff: firstBackoff: 10ms maxBackoff: 50ms factor: 2 basedOnPreviousValue: false ``` 注意点。当遇见forward到类似网关内部的controller时,没有直接返回异常的情况而不能得到ResponseEntity,会导致配置失败。 另外,对请求带有body值的,网关中会缓存该值,导致比较耗费内存 #### 6.27 RequestSize 过滤器 设置最大上传的数据大小。 一个参数maxSize设置大小上线。默认大小单位为byte。可以跟后缀'KB' 或 'MB 请求数据超过限制,将返回413返回码,并在头信息中加入errorMessage。 默认限制大小时5MB 测试方式。应用自己之前用go语言写过的一个文件上传的服务端。测试的。 发现用curl会被无限期卡住(即使上传成功了,没返回结果) 直接用url测试没问题。 ```yaml routes: - id: any # uri: lb://httpbin uri: http://httpbin.org:7788 predicates: - Path=/** filters: - name: RequestSize args: maxSize: 5KB ``` #### 6.28 ModifyRequestBody 过滤器 更改请求内容过滤器,只支持通过Java DSL实现。如下 (感觉还不如自己写个过滤器来的方便) #### 6.29 ModifyResponseBody 过滤器 更改返回的内容,也只能通过Java DSL实现 #### 6.30 默认过滤器 通过spring.cloud.gateway.default-filters设置,可设置多个 ```yaml spring: cloud: gateway: default-filters: - AddResponseHeader=X-Response-Default-Red, Default-Blue - PrefixPath=/anything # 必须有route定义,否则用不上默认的filter routes: - id: any uri: lb://httpbin predicates: - Path=/** ``` ### 7. GlobalFilter 全局过滤器 全局过滤器将做由于所有路由(不过api在以后的版本中会变更) #### 7.1 融合gate filter 每个路由设置自己的filter的同时,可融合全局过滤器。融合后的顺序由org.springframework.core.Ordered(实现getOrder)决定。 默认有很多内置的全局过滤器在运行(尤其配合路由的过滤器) 优先级高代表在“pre”阶段靠前,在“post”阶段靠后 ```yaml # 先一个测试配置 default-filters: - PrefixPath=/anything routes: - id: any uri: lb://httpbin predicates: - Path=/** ``` ```java package fun.zhangyt.train.gateway.globalfilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; public class CustomGlobalFilter implements GlobalFilter, Ordered { private static Logger log = LoggerFactory.getLogger(CustomGlobalFilter.class); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("custom global filter"); return chain.filter(exchange); } @Override public int getOrder() { // 数值越小,优先级越高 return -1; } } ``` ```java package fun.zhangyt.train.gateway.conf; import fun.zhangyt.train.gateway.globalfilter.CustomGlobalFilter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GlobalConfig { @Bean public GlobalFilter customFilter() { return new CustomGlobalFilter(); } } ``` #### 7.2 ForwardRoutingFilter 从exchange中获取ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的值,如果存在且有如“forward:///localendpoint”模式的值, spring将把原来的请求路径更改为该值,同时把原来的请求uri追加到ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR属性 代表的list中 #### 7.3 LoadBalancerClientFilter 其实上面在uri中使用服务的模式就是用的该filter。 LoadBalancerClientFilter从exchange中获取ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的值,如果时lb模式,将应用LoadBalancerClient 识别对应的服务名称,来确定最终的host和port。原始的url会追加到ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR值(list)中 如果一个service从LoadBalancer找不到,则会返回503。如果想设置成404,则如下设置 spring.cloud.gateway.loadbalancer.use404=true 警告:LoadBalancerClientFilter使用一个阻塞模式的ribbon LoadBalancerClient。官方推荐用ReactiveLoadBalancerClientFilter。使用方式 spring.cloud.loadbalancer.ribbon.enabled: false #### 7.4 ReactiveLoadBalancerClientFilter 基本和7.3 的描述差不多,但是非阻塞式的,官方推荐使用 #### 7.5 Netty routing filter 如果在exchange的ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR属性中获取的uri是以http或者https模式的,将使用netty的http client 负责实际的请求。返回结果同时保存在exchange中的ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR属性值中。 (还有一个WebClientHttpRoutingFilter实现同样的功能,但不依赖netty) #### 7.6 NettyWriteResponseFilter 该过滤器在exchange中的ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR属性值有netty的HttpClientResponse返回结果时运行。他一般在其它filter都 执行完时才运行,且把返回结果返回给网关的客户端(WebClientWriteResponseFilter实现相同的功能,但不依赖netty) #### 7.7 RouteToRequestUrl 根据route设置的uri模式对实际请求的url做变更。如uri配置模式为lb:ws://serviceid,则lb将会被移除 #### 7.8 Websocket Routing Filter 如果设置的路由的uri是ws或者wss,该过滤器将会运行,可以应用“lb:ws://serviceid”模式做websocket的负载均衡 ```yaml # 以下配置暂没测试 spring: 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/** ``` #### 7.9 Gateway Metrics Filter 一个监控矩阵,依赖actuator。 启动开关spring.cloud.gateway.metrics.enabled 访问路径 /actuator/metrics/gateway.requests 官方提示,很容易整合到普罗米修斯(整合prometheus,须加入micrometer-registry-prometheus依赖)。 官方文档没说清楚,起初配置都不成功。经过在网上找到一些资料才配置成功 配置示例(注意如果没有实际访问过经过路由的实际请求,则metrics会返回404.访问过后才可以) ```yaml spring: cloud: gateway: # 标志开启metrics metrics: enabled: true routes: - id: any uri: lb://httpbin predicates: - Path=/anything/** management: # metrics 获取终endpoints的权限 endpoints: web: exposure: include: "*" ``` 如果报送给promethues,需要添加如下依赖 ```xml <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> ``` #### 7.10 标记exchange为路由过的 一旦被一个路由标记过,别的路由就不再对此exchange做路由。 可以通过手动更改ServerWebExchangeUtils.setAlreadyRouted值来更爱此值 ### 8 HttpHeadersFilters 内置的filter之一,在把请求传递到底层之前,更改header信息。经常是我们常用到的客户端地址,端口等信息 #### 8.1 Forwarded Headers Filter 添加 Host 和 port头信息到下一层请求的头西悉尼 #### 8.2 RemoveHopByHop Headers Filter 删除IETF规定的一些头信息。默认删除的图头信息如下 - Connection - Keep-Alive - Proxy-Authenticate - Proxy-Authorization - TE - Trailer - Transfer-Encoding - Upgrade 更改的做法是设置spring.cloud.gateway.filter.remove-non-proxy-headers.headers(没测试,应该是一个数组模式,在数组中添加) #### 8.3 XForwarded Headers Filter 该过滤器创建 X-Forwarded-* 头信息 可以通过如下设置控制开启或者关闭(默认开启) - 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 以下设置可控制多个值是否允许了(默认允许) - 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 ### 9.TLS 和 SSL 基本方案和普通的springboot方案一样。 配置类似如下 ```yaml server: ssl: enabled: true key-alias: scg key-store-password: scg1234 key-store: classpath:scg-keystore.p12 key-store-type: PKCS12 ``` 在我的其它文章中有通过jdk工具或者openssl工具生成自签名的p12证书的步骤。不过如果把网关真暴露在最外层,比如用let's encrypts生成 需要格式转化,类似命令如下 ```jshelllanguage openssl pkcs12 -export -in fullchain.pem \ -inkey privkey.pem \ -out scg-keystore.p12 -name springbootserver \ -CAfile chain.pem \ -caname root ``` 如果被代理的服务是https的,则可通过设置网关全部信任来使网关内的http client能顺利访问 ```yaml spring: cloud: gateway: httpclient: ssl: useInsecureTrustManager: true ``` 或者设置信任证书列表 ```yaml spring: cloud: gateway: httpclient: ssl: trustedX509Certificates: - cert1.pem - cert2.pem ``` #### 9.1 TLS 握手超时设置 网关维护这一个http client的池,每一个连接的初始化都有一定的超时时间。 可通过如下属性设置(示例值就是默认值) ```yaml spring: cloud: gateway: httpclient: ssl: handshake-timeout-millis: 10000 close-notify-flush-timeout-millis: 3000 close-notify-read-timeout-millis: 0 ``` ### 10 配置方式 其实前面都已经应用过了。比如基于代码和注解的以及在配置文件中的 基于代码的实现如下接口 ```java public interface RouteDefinitionLocator { Flux<RouteDefinition> getRouteDefinitions(); } ``` 默认情况下PropertiesRouteDefinitionLocator加载配置文件信息来生成路由。 官方的介绍中还说后期会加入从外仓库(如redis,mongodb,cassandra等)获取配置信息的功能 基于配置分简写模式和明细参数模式,如下面的两个route的效果时相同的 ```yaml 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 ``` ### 11 路由元数据(Metadata)配置 配置 ```yaml spring: cloud: gateway: routes: - id: route_with_metadata uri: https://example.org metadata: optionName: "OptionValue" compositeObject: name: "value" iAmNumber: 1 ``` 使用 ```java Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); // get all metadata properties 该方法返回一个map,在map中获取定义的元数据 route.getMetadata(); // get a single metadata property, 经过测试,没有下面这个方法 // route.getMetadata(someKey); ``` ### 12 http 超时 可以给所有的路由设置超时(response 和 connect) #### 12.1 全局超时设置 样例(默认单位时毫秒) ```yaml spring: cloud: gateway: httpclient: connect-timeout: 1000 response-timeout: 5s ``` #### 12.2 单个路由设置 配置文件样例 ```yaml - id: per_route_timeouts uri: https://example.org predicates: - name: Path args: pattern: /delay/{timeout} metadata: response-timeout: 200 connect-timeout: 200 ``` java代码配置样例 ```java 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 // 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(); } ``` #### 12.4 DiscoveryClient 确定路由 这个比较方便,不用一个一个服务的配置 用这个配置启用spring.cloud.gateway.discovery.locator.enabled=true 且有相关客户端的依赖如(Netflix Eureka, Consul, or Zookeeper) #### 12.4.1 设置基于DiscoveryClient的路由 网关默认使用单一的断言和过滤器来实现路由规则。 默认的断言规则:使用“/serviceId/**”作为路由匹配模式。serviceId就是服务id 默认的过滤器规则:重写路径,使用“/serviceId/(?<remaining>.*)”模式进行匹配,使用/${remaining}发送实际的请求 改变规则的方式 ```yaml 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}'" ``` ### 13 Reactor Netty Access Logs netty 日志 开启方式: -Dreactor.netty.http.server.accessLogEnabled=true 同时也可以创建单独的llog文件。经过logback或者其它log4j2的配置。 不过一个问题时,健康检查的log没有单独关闭的方式,可能需要更改源代码手动干预 reactor.netty.http.server.AccessLog logback配置示例 ```xml <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> ``` ### 14 跨域设置 目前发现返回的头信息中包含 Very: 的跨域键头信息。 只有真正跨域的时候,会返回正常的跨域允许头信息 ```yaml spring: cloud: gateway: # 跨域设置 globalcors: cors-configurations: '[/**]': # allowedOrigins: "https://docs.spring.io" allowedHeaders: "*" allowedOrigins: "*" allowedMethods: - GET ``` 下面基于代码配置的能实现同样的效果 ```java package fun.zhangyt.train.gateway.conf; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; import org.springframework.web.util.pattern.PathPatternParser; // 和的配置文件中的的配置是等价的 @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); } } ``` 为不经过route断言处理添加跨域支持 设置 spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true。 ### 15 Actuator API actuator api负责监控和与网关进行交互 需要进行如下配置开启 ```yaml management.endpoint.gateway.enabled: true # default value management.endpoints.web.exposure.include: gateway ``` #### 15.1 详细api输出格式 查看测试。通过浏览器访问 http://localhost:8088/actuator/gateway/routes 默认是开启的。关闭需要如下设置 ```yaml spring.cloud.gateway.actuator.verbose.enabled: false ``` #### 15.2 获取路由过滤器 包括全局过滤器和单个路由包含的过滤器 #### 15.2.1 获取全局过滤器 查看测试,浏览器访问 http://localhost:8088/actuator/gateway/globalfilters 获取到的信息如下(都是现有的全局过滤器) ```json { "org.springframework.cloud.gateway.filter.ForwardPathFilter@70d77826": 0, "org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@16132f21": -2147483648, "org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@7f323b3a": 2147483646, "org.springframework.cloud.gateway.filter.GatewayMetricsFilter@4f169009": 0, "org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@2cd388f5": 10000, "org.springframework.cloud.gateway.filter.NettyRoutingFilter@7793ad58": 2147483647, "org.springframework.cloud.gateway.filter.ForwardRoutingFilter@4640195a": 2147483647, "fun.zhangyt.train.gateway.globalfilter.CustomGlobalFilter@6f5bd362": -1, "org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@50a13c2f": -2147482648, "org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@27e2287c": -1, "org.springframework.cloud.gateway.filter.LoadBalancerClientFilter@3a401749": 10100 } ``` #### 15.2.2 获取单个路由的过滤器 检查测试,浏览器访问 http://localhost:8088/actuator/gateway/routefilters #### 15.3 刷新路由缓存 检查测试 http://localhost:8088/actuator/gateway/refresh 貌似在自动配置的时候可能有用 即 post /actuator/gateway/refresh #### 15.4 获取网关的路由定义(所有的) 检查测试,浏览器访问 http://localhost:8088/actuator/gateway/routes #### 15.5 获取单个路由的配置 检查测试,浏览器访问http://localhost:8088/actuator/gateway/routes/any 即 post /actuator/gateway/routes/{id} #### 15.6 创建或者删除一个路由 创建:POST request to /actuator/gateway/routes/{id_route_to_create}/, 格式和获取路由信息获取的json格式一致 经过测试发现即使返回201,但通过获取路由的接口查询不到新创建的路由 删除:DELETE request to /actuator/gateway/routes/{id_route_to_delete} 经过测试,发现只能删除通过接口创建的,配置文件和代码配置的都不能正常删除 ### 16 问题排查 #### 16.1 日志级别 以下日志可以设置成debug级别有助于排查信息 - org.springframework.cloud.gateway - org.springframework.http.server.reactive - org.springframework.web.reactive - org.springframework.boot.autoconfigure.web - reactor.netty - redisratelimiter #### 16.2 监听(窃听) netty中反应式的HttpClient和HttpServer可以开启监听,配置reactor.netty的debug级别的log,可以看到请求的header和body 信息。 开启方式,设置 ```yaml spring.cloud.gateway.httpserver.wiretap: true spring.cloud.gateway.httpclient.wiretap: true ``` ### 17 开发向导 这个开发网关自定义组件的向导 #### 17.1 自定义网关路由断言工厂 需要实现RoutePredicateFactory接口,通常可以继承抽象类AbstractRoutePredicateFactory 下面是一个示例模板 ```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 } } ``` 下面是我仿照者 HeaderRoutePredicateFactory 自己写的一个断言,和应用配置示例。 断言定义 ```java package fun.zhangyt.train.gateway.custom; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import org.springframework.web.server.ServerWebExchange; import javax.validation.constraints.NotEmpty; import java.util.Collections; import java.util.List; import java.util.function.Predicate; // 也一个在@Configuration的配置类中加入@Bean的定义,这里为了简单,直接使用@Component注解 // 断言名称就是My,后缀必须是RoutePredicateFactory,为既定规则 @Component public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> { private static Logger logger = LoggerFactory.getLogger(MyRoutePredicateFactory.class); private static final String MY_TOKEN_KEY = "myToken"; public MyRoutePredicateFactory() { super(Config.class); } // 这个实在配置文件简写方式时,配置参数的字段序列 @Override public List<String> shortcutFieldOrder() { return Collections.singletonList(MY_TOKEN_KEY); } @Override public Predicate<ServerWebExchange> apply(Config config) { logger.info("自定义断言判断"); return exchange -> { HttpHeaders headers = exchange.getRequest().getHeaders(); List<String> header = headers.get(config.getMyToken()); logger.info("Token Predicate headers:{}", header); // 只要有值,就认为符合需求 return header.size() > 0; }; } // 配置字段我只设置了一个 @Validated public static class Config { @NotEmpty private String myToken; public String getMyToken() { return myToken; } public void setMyToken(String myToken) { this.myToken = myToken; } } } ``` 应用上面自定义断言的配置,代码配置方式在这里不表 ```yaml - id: my uri: lb://httpbin predicates: # My是因为定义的断言的前缀是My(既定规则),后面是定义断言时的一个参数值(仅定义了一个参数值) - My=mytoken ``` #### 17.2 自定义网关过滤器工厂 需要实现GatewayFilterFactory接口,一般继承抽象类AbstractGatewayFilterFactory 下面是两个官方资料提供的样例模板 ```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 } } ``` ```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 } } ``` 下面是一个自写的简单示例(请求前) ```java package fun.zhangyt.train.gateway.custom; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; @Component public class MyPreGatewayFilterFactory extends AbstractGatewayFilterFactory<MyPreGatewayFilterFactory.Config> { public MyPreGatewayFilterFactory() { 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 // 添加一个头信息,不过发现头信息在被处理后,各单词的首字母被自动大写了 builder.header("my-pre-filter", "hello, pro filter"); return chain.filter(exchange.mutate().request(builder.build()).build()); }; } public static class Config { //Put the configuration properties for your filter here } } ``` 配置使用方式 ```yaml - id: my uri: lb://httpbin predicates: - My=mytoken filters: # 同样也是前缀的既定规则方式 - MyPre ``` 下面是一个返回后的示例 ```java package fun.zhangyt.train.gateway.custom; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; @Component public class MyPostGatewayFilterFactory extends AbstractGatewayFilterFactory<MyPostGatewayFilterFactory.Config> { public MyPostGatewayFilterFactory() { 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 response.getHeaders().set("MyPost-Foo", "bar"); })); }; } public static class Config { //Put the configuration properties for your filter here } } ``` 应用配置 ```yaml - id: my uri: lb://httpbin predicates: # My是因为定义的断言的前缀是My(既定规则),后面是定义断言时的一个参数值(仅定义了一个参数值) - My=mytoken filters: - MyPre - MyPost ``` #### 17.3 自定义全局过滤器 之前其实利用路由metadata的时候已经写过一个。下面是官方资料中自带的两个模板 ```java @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(); } ``` ### 18 使用spring mvc 和 webflux 构建一个简单的网关 其实就是通过 ProxyExchange ,用自定义的controller自定义网关跳转 需要引入依赖 spring-cloud-gateway-mvc 或 spring-cloud-gateway-webflux (spring cloud gateway本身就不要引入了) 官方 mvc 示例 ```java @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 示例 ```java @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(); } } ``` ### 19 配置文件属性参考 https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.2.RELEASE/reference/html/appendix.html
附件列表
spring_cloud_gateway_diagram.png
gateway-tutorial-20200321.zip