# 优雅的实现spring-cloud-gateway修改请求和响应体
  
 ## 引言
     最近公司的微服务越来越多,管理起来比较麻烦,决定引入微服务网关。咨询了百度老师发现Spring Cloud gateway功能已经比较完善,性能也还很不错。决定就是它了。
     建工程,加依赖,写配置,一切看起来都那么的简单加顺利。
     只是某天某人突然提了一个需求,为了方便调试让我增加1个可以查看每个post+json+restApi请求的请求头,请求体,响应头,响应体的功能。
     我想这还不简单嘛,直接加个全局过滤器就搞定了。
     赶紧加班加点写了一个过滤器实现GlobalFilter和Ordered接口,在接口中接收获取请求和响应的信息通过日志打印出来就OK了吧。
     
     理想是美好的,现实是残酷的。才发现遇到了不好处理的问题。
 ## 遇到的问题* 1.ServerHttpRequest请求对象的请求体只能获取一次,一旦获取了就不能继续往下传递。
     Flux<DataBuffer> getBody();* 2.请求体方法返回的是基于webFlux响应式的函数结构,特别难处理。
      
 * 3.ServerHttpResponse响应体根本就没有获取响应体的方法。* 4.系统提供的修改请求体和响应体的类只实现了GatewayFilter过滤器,无法对全局进行监控而且还只能通过Java DSL进行配置.不可能每增加一个服务都修改代码。我需要的是全局过滤器。
     ModifyRequestBodyGatewayFilterFactory  系统提供的修改请求体的类
     ModifyResponseBodyGatewayFilterFactory 系统提供的修改响应体的类
 ## 解决方案
  
  查询了网上很多的实现方法始终都不能完美的处理。不是丢包就是根本获取不到数据。
  
  基本思想:代理实现ServerHttpRequest和ServerHttpResponse类,自己先处理请求和响应体,然后重新生成1个新的数据返回出去。网上大多数都是这种方案。
  
  想起组件本身提供的有ModifyRequestBodyGatewayFilterFactory和ModifyResponseBodyGatewayFilterFactory来对请求和响应体进行修改。
  
  不过研究之后发现。这两个类生成的是GatewayFilter路由过滤器,而我们需要GlobalFilter(全局过滤器)
  
  要么把这个类拷贝出来重新自己实现1个基于全局过滤器的版本。不过这样代码改动比较大,而且重复严重,哪怕是跟组件本身的代码重复这也是不允许的。不提倡重复造轮子。 想到 GatewayFilter和GlobalFilter过滤器除了实现的接口不同,它要实现的方法签名都是相同的。既然官方已经实现了GatewayFilter版本的,我应该可以代理直接使用。
  
  设计思路:提供一个类专门用来处理请求和响应。可以实现接口RewriteFunction,为了统一处理,输入输出参数都用byte[].代码如下:
  
  ```
   
  import java.util.stream.Collectors;
  import org.reactivestreams.Publisher;
  import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
  import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
  import org.springframework.http.HttpHeaders;
  import org.springframework.http.MediaType;
  import org.springframework.http.server.reactive.ServerHttpRequest;
  import org.springframework.http.server.reactive.ServerHttpResponse;
  import org.springframework.web.server.ServerWebExchange;
  
  import lombok.extern.slf4j.Slf4j;
  import reactor.core.publisher.Mono;
  /**
   * 泛型参数1:源请求体类,源响应体
   * 泛型参数2:新请求体类,新响应体
   * @author cqyhm
   *
   */
  @Slf4j
  public class BodyRewrite implements RewriteFunction<byte[], byte[]> {
     
     /**
      * 在执行全局请求或响应的过滤器的时候会执行该方法,并把请求体或响应体传递进来。
      * @param exchange 网关处理上下文
      * @param body 源请求或响应体
      * @return 返回处理过的请求体或响应体
      */
      @Override
      public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) {
         //如果路由没有完成应该是请求过滤器执行
          if(!ServerWebExchangeUtils.isAlreadyRouted(exchange)) {            
              exchange.getAttributes().put("request_key", new String(body));  //保存请求体到全局上下文中
              exchange.getAttributes().put("startTime", System.currentTimeMillis()); //保存启动时间到上下中
             //TODO 可以在这里对请求体进行修改
          } else { //已经路由应该是响应过滤器执行
             //TODO 可以在这里对响应体进行修改
              response(exchange, body);
          }
          return Mono.just(body);
      }
     /**
      *  打印输出响应的参数,请求体,响应体,请求头部,响应头部,请求地址,请求方法等。
      *  @param exchange 网关处理上下文
      *  @param body 源请求或响应体
      *  @return 返回处理过的请求体或响应体
      */
      public byte[] response(ServerWebExchange exchange,  byte[] responseBody) {
          try {
              ServerHttpRequest request=exchange.getRequest();
              ServerHttpResponse response=exchange.getResponse();
              String requestbody=exchange.getAttribute("request_key");
              Long startTime=exchange.getAttributeOrDefault("startTime", 0L);
              Long time=System.currentTimeMillis()-startTime;
              boolean flag=MediaType.APPLICATION_JSON.isCompatibleWith(response.getHeaders().getContentType());
              //responseBody=objectMapper.writeValueAsString(MessageBox.ok());
              log.info("\n[{}]请求地址:\n\t{} {}\n[{}]请求头部:\n{}\n[{}]路径参数:\n{}\n[{}]请求参数:\n{}"
                       + "\n[{}]响应头部:\n{}\n[{}]响应内容:\n\t{}\n[{}]执行时间[{}]毫秒", 
                      request.getId(), request.getMethod(), request.getPath(),
                      request.getId(), headers(request.getHeaders()), 
                      request.getId(), request(request),
                      request.getId(), requestbody,
                      request.getId(), headers(response.getHeaders()),
                      request.getId(), flag ? new String(responseBody) : "非JSON字符串不显示", 
                      request.getId(),    time);
              //TODO 可以对响应体进行修改
              return responseBody;        
          } catch (Exception e) {
              throw new RuntimeException("响应转换错误");
          } finally {
              exchange.getAttributes().remove("request_key");
              exchange.getAttributes().remove("startTime");
          }
      }
      
      public String headers(HttpHeaders headers) {
          return headers.entrySet().stream()
                  .map(entry -> "\t" + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
                  .collect(Collectors.joining("\n"));
      }
      /**
       * 处理其它get请求参数
       */
      public String request(ServerHttpRequest request) {
          String params=request.getQueryParams().entrySet().stream()
                  .map(entry -> "\t" + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
                  .collect(Collectors.joining("\n"));
          return params;
      }
  }
  ```
     业务处理逻辑写好之后,剩下的就是配置了。我这里只是把参数打印日志没有做处理,你甚至可以在这里修改请求体和响应体。
     代理ModifyRequestBodyGatewayFilterFactory和ModifyResponseBodyGatewayFilterFactory类中的GatewayFilter来定义GlobalFilter过滤器。      这里是使用内部类直接实现的,但是指定GlobalFilter的顺序有问题导致无法监控,最好的办法还是单独设计两个GlobalFilter过滤器类。
     
     查看代码:
 ```
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.cloud.gateway.filter.GatewayFilter;
 import org.springframework.cloud.gateway.filter.GatewayFilterChain;
 import org.springframework.cloud.gateway.filter.GlobalFilter;
 import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
 import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
 import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.annotation.Order;
 import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;
@Configuration
 @ConditionalOnProperty(prefix = "logging.filter", name = "enabled", havingValue = "true", matchIfMissing = false)
 public class CustomGatewayConfig {
     
     @Bean
     public BodyRewrite bodyRewrite() {
         return new BodyRewrite();
     }
     /**
      * 定义全局拦截器拦截请求体
      */
     @Bean
     @Order(NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 2)   //指定顺序必须在之前
     public GlobalFilter requestFilter(ModifyRequestBodyGatewayFilterFactory modifyRequestBody) {        
         GatewayFilter delegate=modifyRequestBody.apply(new ModifyRequestBodyGatewayFilterFactory.Config()
                                                  .setRewriteFunction(byte[].class, byte[].class, bodyRewrite()));
         return new GlobalFilter() {
             @Override
             public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                 return delegate.filter(exchange, chain); 
             }
         };
     }
     /**
      * 定义全局拦截器拦截响应体
      */
     @Bean
     @Order(NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 2) //指定顺序必须在之前
     public GlobalFilter responseFilter(ModifyResponseBodyGatewayFilterFactory modifyResponseBody) {
         GatewayFilter delegate=modifyResponseBody.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
                                                  .setRewriteFunction(byte[].class, byte[].class, bodyRewrite()));
         return new GlobalFilter() {
             @Override
             public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                 return delegate.filter(exchange, chain);
             }
         };
     }
 }
 ```
     关键的地方就在于官方提供的类ModifyResponseBodyGatewayFilterFactory提供的方法apply
 ```    
     public GatewayFilter apply(Config config);
 ```    
     该方法返回的是GatewayFilter我们正好可以利用它的方法filter方法
 ```    
     public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain) 
 ```
    我们只需要构造1个它的参数config就好了。分析源代码发现它的参数有个setRewriteFunction函数
    
 ```
    /**
     * @param inClass   请求体的原始数据类型,我们统一使用byte[]
     * @param outClass 请求体变更过后的数据类型,我们统一使用byte[]
     * @param rewriteFunction 对请求体进行处理的类。就是我们前面定义的BodyRewrite的对象
     */
    public <T, R> Config setRewriteFunction(Class<T> inClass, Class<R> outClass, RewriteFunction<T, R> rewriteFunction)   
    
 ```   
   执行了ModifyResponseBodyGatewayFilterFactory的apply方法传入config参数能够得到1个唯1个GatewayFilter对象
 ```  
   GatewayFilter delegate=modifyResponseBody.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
                                            .setRewriteFunction(byte[].class, byte[].class, bodyRewrite()));
    
 ```
   只需要在我们自定义的 GlobalFilter过滤器中调用delegate.filter(exchange, chain)方法
 ```  
   return new GlobalFilter() {
       @Override
       public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
           return delegate.filter(exchange, chain);
       }
   };
 ```      以上简化的方法设置可能存在在某些版本上无法监控的问题,只需要把请求过滤器和响应过滤器完全独立出来开发就可以了。具体为什么,我还没来得及分析。以下是请求过滤器。响应过滤器类似。
```
import org.springframework.cloud.gateway.filter.GatewayFilter;
 import org.springframework.cloud.gateway.filter.GatewayFilterChain;
 import org.springframework.cloud.gateway.filter.GlobalFilter;
 import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
 import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
 import org.springframework.core.Ordered;
 import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;
/**
  * 全局请求体过滤器
  * @author cqyhm
  *
  */
 public class RequestBodyFilter implements GlobalFilter, Ordered {
     
     private GatewayFilter delegate;
     public RequestBodyFilter(ModifyRequestBodyGatewayFilterFactory modifyRequestBody, BodyRewrite bodyRewrite) {
         this.delegate=modifyRequestBody.apply(new ModifyRequestBodyGatewayFilterFactory.Config()
                                                              .setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
     }    @Override
     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
         return delegate.filter(exchange, chain);
     }
     
     @Override
     public int getOrder() {
         return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 2;
     }
 }
 ```至于响应过滤器跟这个差不多,一葫芦画瓢很快就能写1个出来。
```
/**
  * 全局响应体过滤器
  */
 public class ResponseBodyFilter implements GlobalFilter, Ordered  {
     
     private GatewayFilter delegate;
     public ResponseBodyFilter(ModifyResponseBodyGatewayFilterFactory modifyResponseBody, BodyRewrite bodyRewrite) {
         this.delegate=modifyResponseBody.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
                                                                 .setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
     }    @Override
     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
         return delegate.filter(exchange, chain);
     }
     
     @Override
     public int getOrder() {
         return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 2;
     }
 }```

 然后注册这两个Bean基本上都可以了。
  这种方案应该是最将简便的,最优雅的,只需要实现我们的业务类BodyRewrite其它都直接使用Java config配置,对系统改动小,侵入性低。
  
  这种方案目前正在验证当中,有什么问题欢迎交流。
  好了,这就大功告成了。