软件编程
位置:首页>> 软件编程>> java编程>> Spring Cloud Gateway 如何修改HTTP响应信息

Spring Cloud Gateway 如何修改HTTP响应信息

作者:帷幄庸者  发布时间:2021-07-20 13:33:40 

标签:SpringCloud,Gateway,HTTP,响应

Gateway 修改HTTP响应信息

实践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.,类的注释中说明这个类在以后版本中会改进,实际使用可以参考实现功能,但是性能影响较大,不过没有别的选择还是得选择这个。

官方文档:

实现

最终代码

先贴最终代码


public class ResponseDecryptionGlobalFilter implements GlobalFilter, Ordered {
   private static Logger log = LoggerFactory.getLogger(ResponseDecryptionGlobalFilter.class);
   @Override
   public int getOrder() {
   // 控制在NettyWriteResponseFilter后执行
       return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
   }
   @Override
   public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
       return processResponse(exchange, chain);
   }
   private Mono<Void> processResponse(ServerWebExchange exchange, GatewayFilterChain chain) {
       // 路由中如果不需要过滤则不进行过滤
       if (!BooleanUtils.isTrue()) {
           return chain.filter(exchange);
       }
       ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
           @Override
           public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
               String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
               HttpHeaders httpHeaders = new HttpHeaders();
               httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
               ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);
               DefaultClientResponse clientResponse = new DefaultClientResponse(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 = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders());
               return bodyInserter.insert(outputMessage, new BodyInserterContext())
                       .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);
                                   }
                               }
                               return getDelegate().bufferFactory().wrap(responseStr.getBytes(StandardCharsets.UTF_8));
                           });
                           HttpHeaders headers = getDelegate().getHeaders();
                           // 修改响应包的大小,不修改会因为包大小不同被浏览器丢掉
                           flux = flux.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
                           return getDelegate().writeWith(flux);
                       }));
           }
       };
       return chain.filter(exchange.mutate().response(responseDecorator).build());
   }
   /**
    * 权限数据过滤
    *
    * @param result
    * @return
    */
   private String dataFilter(JSONObject result) {
       Object data = result.get("data");
       return result.toJSONString();
   }
   private class ResponseAdapter implements ClientHttpResponse {
       private final Flux<DataBuffer> flux;
       private final HttpHeaders headers;
       @SuppressWarnings("unchecked")
       private ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders headers) {
           this.headers = headers;
           if (body instanceof Flux) {
               flux = (Flux) body;
           } else {
               flux = ((Mono) body).flux();
           }
       }
       @Override
       public Flux<DataBuffer> getBody() {
           return flux;
       }
       @Override
       public HttpHeaders getHeaders() {
           return headers;
       }
       @Override
       public HttpStatus getStatusCode() {
           return null;
       }
       @Override
       public int getRawStatusCode() {
           return 0;
       }
       @Override
       public 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();

Gateway 修改返回的响应体

问题描述:

在gateway中修改返回的响应体,在全局Filter中添加如下代码:


import org.springframework.core.Ordered;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class RequestGlobalFilter implements GlobalFilter, Ordered {
//...

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
 //...
 ResponseDecorator decorator = new ResponseDecorator(exchange.getResponse());
 return chain.filter(exchange.mutate().response(decorator).build());
}
@Override
public int getOrder() {
 return -1000;
}
}

通过.response(decorator)设置一个响应装饰器(自定义),以下是装饰器具体实现:


import cn.hutool.json.JSONObject;
import org.reactivestreams.Publisher;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.Charset;
/**
* @author visy.wang
* @desc 响应装饰器(重构响应体)
*/
public class ResponseDecorator extends ServerHttpResponseDecorator{
public ResponseDecorator(ServerHttpResponse delegate){
 super(delegate);
}
@Override
@SuppressWarnings(value = "unchecked")
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
 if(body instanceof Flux) {
  Flux<DataBuffer> fluxBody = (Flux<DataBuffer>) body;
  return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
   DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
   DataBuffer join = dataBufferFactory.join(dataBuffers);
   byte[] content = new byte[join.readableByteCount()];
   join.read(content);
   DataBufferUtils.release(join);// 释放掉内存

String bodyStr = new String(content, Charset.forName("UTF-8"));
               //修改响应体
   bodyStr = modifyBody(bodyStr);
   getDelegate().getHeaders().setContentLength(bodyStr.getBytes().length);
   return bufferFactory().wrap(bodyStr.getBytes());
  }));
 }
 return super.writeWith(body);
}
   //重写这个函数即可
private String modifyBody(String jsonStr){
 JSONObject json = new JSONObject(jsonStr);
       //TODO...修改响应体
 return json.toString();
}
}

来源:https://blog.csdn.net/weiwoyonzhe/article/details/90814680

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com