前言
Spring Cloud Gateway(下称SCG)涉及几个最近使用上的问题,自己感觉还挺典型的。所以罗列出来。
- 缓存请求体会二次过滤
- 服务端响应头删除
当前环境版本:
- Spring Boot 2.1.6.RELEASE
- Spring Cloud Greenwich.SR3
- 对应SCG版本2.1.3.RELEASE
缓存请求体会二次过滤
这是一个bug,官方已经在2019.10.2修复,但是因为本地环境所限,无法直接升级版本
SCG就缓存请求体这一需求,社区很早就已经给出了解决方案,但是方案不是很完美。所以经过了几次的修改,我自己使用上,觉得现在的版本用着还可以。
问题复现
工程详见:error-cache-body
直接下载运行,post请求:8080/post即可,执行:
1 | http post :8080/post foo=bar |
Log如下:
1 | 2019-11-18 18:07:32.899 TRACE 64684 --- [ctor-http-nio-2] o.s.c.g.f.WeightCalculatorWebFilter : Weights attr: {} |
上面的是一个完整的请求,开启了org.springframework.cloud.gateway的trace日志。其中可以看到AccessLogGlobalFilter访问了两次,提取出来,如下所示:
1 | i.g.c.filter.AccessLogGlobalFilter : access 1 times. |
第二次在请求在走filter的时候,其实第一次的请求已经向后端提交了,所以二次请求无效。对响应内容也没有影响,二次请求不会打印retaining body in exchange attribute,所以可以断定重新过的时候,body被删除或者清空了,所以没有缓存的操作,其他的请求内容就会使用一些不可变对象封装请求,例如:请求头会使用org.springframework.http.ReadOnlyHttpHeaders对象封装,无法操作请求Header。
相关Issues:gh-1315,PR: 856a2417b6535d54d8f07625dfb48bc5080e87fe
问题解析
官方的PR解决了整个二次请求的问题,This allows the AdaptCahcedBodyGlobalFilter to not worry about handling empty.现在就可以处理空请求体了。可以直接升级到2.2.0.RC2版本来生效,或者直接把相关代码拷贝到当前实现类中即可。下面这个BodyUtils可以拆掉请求体外部封装并转为Map或String等形式,供后面Filter使用。
包含如下方法:
- BodyUtils#toRaw(DataBuffer body) - 获取body内的String
- BodyUtils#toFormDataMap(ServerHttpRequest httpRequest, DataBuffer body) - body转为FormData, 针对content-type为
application/x-www-form-urlencoded - BodyUtils#cacheRequestBody - 缓存Body,ServerWebExchangeUtils中的完整拷贝
NOTE:Reactive Streams中不推荐做这种转换
服务端响应头删除
在用httpbin.org做报文请求响应测试时,服务端会添加一系列的响应头,比如一些安全请求头,但是网关本身就开了SecureHeader的Filter,会出现重复
问题复现
当Body缓存没有修复时,执行不到这一步就会抛出异常,因为二次请求会报针对只读请求头的写操作异常(UnsupportedOperationException),当前问题和Body缓存也无关,可以抛开缓存来看。
所有配置都在application.properties中,包括:
- 安全头配置 - 全局配置,只开启
x-frame-options,值为SAMEORIGIN - 转发配置 - 转发至httpbin.org
1 | =SAMEORIGIN |
执行如下请求:
1 | λ http post :8080/post -v |
可以看上面的响应头,有两个X-Frame-Options安全头,SAMEORIGIN是网关添加的,而DENY是httpbin.org响应的请求头。(可以做一个直接请求httpbin的作为对比)
问题解析
这里我们以当前最新的2.1.4.RELEASE版本的SCG为例,SecureHeadersGatewayFilterFactory会在响应前把所需的返回客户端的头写进Exchange的Response中,这不影响Request的发送。但是在处理响应时,会将预先的设置好的头放到完整响应中,具体在NettyRoutingFilter.java的118行,如下:
1 | // NettyRoutingFilter.java |
在整合之前,会有一个请求头过滤的操作,包含转发头的过滤等,为了去掉后端发回来的相同头,需要先删除响应中的头。
1 | // NettyRoutingFilter.java |
SCG提供了HttpHeadersFilter,用于过滤请求/响应头,先解析一下这个类:
1 | public interface HttpHeadersFilter { |
实现一个HttpHeadersFilter,加上自定义逻辑就可以了。ps. 如果要操作响应的话,记得实现一下default方法support(),针对安全头过滤的实现如下:
1 | /** |
结束!🔚
