好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

官方文档:

实现

最终代码

先贴最终代码

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

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长度,导致前端请求无法获取修改后的响应结果。

?

1

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是一个只读类,因此需要通过下面例子的方法来进行修改,对于读多写少的场景,这种设计模式是值得借鉴的

?

1

2

ServerHttpRequest newRequest = request.mutate().headers( "key" , "value" ).path( "/myPath" ).build();

ServerWebExchange newExchange = exchange.mutate().response(responseDecorator).build();

Gateway 修改返回的响应体

问题描述:

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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)设置一个响应装饰器(自定义),以下是装饰器具体实现:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

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

查看更多关于Spring Cloud Gateway 如何修改HTTP响应信息的详细内容...

  阅读:27次