实践Spring Cloud的过程中,使用Gateway作为路由组件,并且基于Gateway实现权限的验证、拦截、过滤,对于下游微服务的响应结果,我们总会有需要修改以统一数据格式,或者修改过滤用户没有权限看到的数据信息,这时候就需要有一个能够修改响应体的Filter。
Spring Cloud Gateway 版本为2.1.0
在当前版本,ModifyRequestBodyGatewayFilterFactory是官方提供的修改响应体的参考类,This filter is BETA and may be subject to change in a future release.,类的注释中说明这个类在以后版本中会改进,实际使用可以参考实现功能,但是性能影响较大,不过没有别的选择还是得选择这个。
实现
最终代码
先贴最终代码
publicclassResponseDecryptionGlobalFilterimplementsGlobalFilter, Ordered{privatestatic Logger log= LoggerFactory.getLogger(ResponseDecryptionGlobalFilter.class);@OverridepublicintgetOrder(){// 控制在NettyWriteResponseFilter后执行return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER-1;}@Overridepublic Mono<Void>filter(ServerWebExchange exchange, GatewayFilterChain chain){returnprocessResponse(exchange, chain);}private Mono<Void>processResponse(ServerWebExchange exchange, GatewayFilterChain chain){// 路由中如果不需要过滤则不进行过滤if(!BooleanUtils.isTrue()){return chain.filter(exchange);}
ServerHttpResponseDecorator responseDecorator=newServerHttpResponseDecorator(exchange.getResponse()){@Overridepublic Mono<Void>writeWith(Publisher<?extendsDataBuffer> body){
String originalResponseContentType= exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders=newHttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
ResponseAdapter responseAdapter=newResponseAdapter(body, httpHeaders);
DefaultClientResponse clientResponse=newDefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults());
Mono<String> rawBody= clientResponse.bodyToMono(String.class).map(s-> s);
BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter= BodyInserters.fromPublisher(rawBody, String.class);
CachedBodyOutputMessage outputMessage=newCachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders());return bodyInserter.insert(outputMessage,newBodyInserterContext()).then(Mono.defer(()->{
Flux<DataBuffer> messageBody= outputMessage.getBody();
Flux<DataBuffer> flux= messageBody.map(buffer->{
CharBuffer charBuffer= StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);// 将响应信息转化为字符串
String responseStr= charBuffer.toString();if(StringUtils.isNotBlank(responseStr)){try{
JSONObject result= JSONObject.parseObject(responseStr);
System.out.println(dataFilter(result));if(result.containsKey("data")){
responseStr=dataFilter(result);}else{
log.error("响应结果序列化异常:{}", responseStr);}}catch(JSONException e){
log.error("响应结果序列化异常:{}", responseStr);}}returngetDelegate().bufferFactory().wrap(responseStr.getBytes(StandardCharsets.UTF_8));});
HttpHeaders headers=getDelegate().getHeaders();// 修改响应包的大小,不修改会因为包大小不同被浏览器丢掉
flux= flux.doOnNext(data-> headers.setContentLength(data.readableByteCount()));returngetDelegate().writeWith(flux);}));}};return chain.filter(exchange.mutate().response(responseDecorator).build());}/**
* 权限数据过滤
*
* @param result
* @return
*/private StringdataFilter(JSONObject result){
Object data= result.get("data");return result.toJSONString();}privateclassResponseAdapterimplementsClientHttpResponse{privatefinal Flux<DataBuffer> flux;privatefinal HttpHeaders headers;@SuppressWarnings("unchecked")privateResponseAdapter(Publisher<?extendsDataBuffer> body, HttpHeaders headers){this.headers= headers;if(bodyinstanceofFlux){
flux=(Flux) body;}else{
flux=((Mono) body).flux();}}@Overridepublic Flux<DataBuffer>getBody(){return flux;}@Overridepublic HttpHeadersgetHeaders(){return headers;}@Overridepublic HttpStatusgetStatusCode(){return null;}@OverridepublicintgetRawStatusCode(){return0;}@Overridepublic MultiValueMap<String, ResponseCookie>getCookies(){return null;}}}
踩过的坑
- 响应体报文过大: 起初直接读取buffer的响应信息,包小的情况没有问题,但是包大了会抛出json无法转换异常,因为没能读取完整的响应内容,参考ModifyRequestBodyGatewayFilter,等待buffer全部读完再转为数组,然后执行处理。本质原因是底层的Reactor-Netty的数据块读取大小限制导致获取到的DataBuffer实例里面的数据是不完整的。
- 修改响应信息后,响应的ContentLength会发生变化,忘记修改response中的Content-Length长度,导致前端请求无法获取修改后的响应结果。
flux= flux.doOnNext(data-> headers.setContentLength(data.readableByteCount()));
- order值必须小于-1,因为覆盖返回响应体,自定义的GlobalFilter必须比NettyWriteResponseFilter处理完后执行。order越小越早进行处理,越晚处理响应结果。
理解ServerWebExchange
先看ServerWebExchange的注释:
Contract for an HTTP request-response interaction. Provides access to the HTTP request and response and also exposes additional server-side processing related properties and features such as request attributes.
翻译一下大概是:
ServerWebExchange是一个**HTTP请求-响应交互的契约。**提供对HTTP请求和响应的访问,并公开额外的服务器端处理相关属性和特性,如请求属性。
ServerWebExchange有点像Context的角色,我把它理解为http请求信息在Filter透传的容器,之所以称之为容器,因为它可以存储我们像放进去的数据。
注意:
ServerHttpRequest是一个只读类,因此需要通过下面例子的方法来进行修改,对于读多写少的场景,这种设计模式是值得借鉴的
ServerHttpRequest newRequest = request.mutate().headers("key","value").path("/myPath").build();
ServerWebExchange newExchange = exchange.mutate().response(responseDecorator).build();
参考:https://juejin.im/post/5cdfb8fe6fb9a07f04201838#heading-10