评论

收藏

[Java] SpringCloud Finchley Gateway 缓存请求Body和Form表单的实现

编程语言 编程语言 发布于:2021-10-05 21:08 | 阅读数:376 | 评论:0

在接入Spring-Cloud-Gateway时,可能有需求进行缓存Json-Body数据或者Form-Urlencoded数据的情况。这篇文章主要介绍了SpringCloud Finchley Gateway 缓存请求Body和Form表单的实现,感兴趣的小伙伴们可以参考一下
在接入spring-cloud-gateway时,可能有需求进行缓存json-body数据或者form-urlencoded数据的情况。
由于spring-cloud-gateway是以webflux为基础的响应式架构设计,所以在原有zuul基础上迁移过来的过程中,传统的编程思路,并不适合于reactor stream的开发。
网络上有许多缓存案例,但是在测试过程中出现各种bug问题,在缓存body时,需要考虑整体的响应式操作,才能更合理的缓存数据
下面提供缓存json-body数据或者form-urlencoded数据的具体实现方案,该方案经测试,满足各方面需求,以及避免了网络上其他缓存方案所出现的问题
定义一个gatewaycontext类,用于存储请求中缓存的数据
import lombok.getter;
import lombok.setter;
import lombok.tostring;
import org.springframework.util.linkedmultivaluemap;
import org.springframework.util.multivaluemap;
 
@getter
@setter
@tostring
public class gatewaycontext {
 
  public static final string cache_gateway_context = "cachegatewaycontext";
 
  /**
   * cache json body
   */
  private string cachebody;
  /**
   * cache formdata
   */
  private multivaluemap<string, string> formdata;
  /**
   * cache reqeust path
   */
  private string path;
}
实现globalfilter和ordered接口用于缓存请求数据
1 . 该示例只支持缓存下面3种mediatype

  • application_json--json数据
  • application_json_utf8--json数据
  • application_form_urlencoded--formdata表单数据
2 . 经验总结:

  • 在缓存body时,不能够在filter内部直接进行缓存,需要按照响应式的处理方式,在异步操作路途上进行缓存body,由于body只能读取一次,所以要读取完成后要重新封装新的request和exchange才能保证请求正常传递到下游
  • 在缓存formdata时,formdata也只能读取一次,所以在读取完毕后,需要重新封装request和exchange,这里要注意,如果对formdata内容进行了修改,则必须重新定义header中的content-length已保证传输数据的大小一致
import com.choice.cloud.architect.usergate.option.filterorderenum;
import com.choice.cloud.architect.usergate.support.gatewaycontext;
import io.netty.buffer.bytebufallocator;
import lombok.extern.slf4j.slf4j;
import org.springframework.cloud.gateway.filter.gatewayfilterchain;
import org.springframework.cloud.gateway.filter.globalfilter;
import org.springframework.core.ordered;
import org.springframework.core.io.bytearrayresource;
import org.springframework.core.io.buffer.databuffer;
import org.springframework.core.io.buffer.databufferutils;
import org.springframework.core.io.buffer.nettydatabufferfactory;
import org.springframework.http.httpheaders;
import org.springframework.http.mediatype;
import org.springframework.http.codec.httpmessagereader;
import org.springframework.http.server.reactive.serverhttprequest;
import org.springframework.http.server.reactive.serverhttprequestdecorator;
import org.springframework.util.multivaluemap;
import org.springframework.web.reactive.function.server.handlerstrategies;
import org.springframework.web.reactive.function.server.serverrequest;
import org.springframework.web.server.serverwebexchange;
import reactor.core.publisher.flux;
import reactor.core.publisher.mono;
 
import java.io.unsupportedencodingexception;
import java.net.urlencoder;
import java.nio.charset.charset;
import java.nio.charset.standardcharsets;
import java.util.list;
import java.util.map;
 
@slf4j
public class gatewaycontextfilter implements globalfilter, ordered {
 
  /**
   * default httpmessagereader
   */
  private static final list<httpmessagereader<?>> messagereaders = handlerstrategies.withdefaults().messagereaders();
 
  @override
  public mono<void> filter(serverwebexchange exchange, gatewayfilterchain chain) {
  /**
   * save request path and serviceid into gateway context
   */
  serverhttprequest request = exchange.getrequest();
  string path = request.getpath().pathwithinapplication().value();
  gatewaycontext gatewaycontext = new gatewaycontext();
  gatewaycontext.getallrequestdata().addall(request.getqueryparams());
  gatewaycontext.setpath(path);
  /**
   * save gateway context into exchange
   */
  exchange.getattributes().put(gatewaycontext.cache_gateway_context,gatewaycontext);
  httpheaders headers = request.getheaders();
  mediatype contenttype = headers.getcontenttype();
  long contentlength = headers.getcontentlength();
  if(contentlength>0){
    if(mediatype.application_json.equals(contenttype) || mediatype.application_json_utf8.equals(contenttype)){
    return readbody(exchange, chain,gatewaycontext);
    }
    if(mediatype.application_form_urlencoded.equals(contenttype)){
    return readformdata(exchange, chain,gatewaycontext);
    }
  }
  log.debug("[gatewaycontext]contenttype:{},gateway context is set with {}",contenttype, gatewaycontext);
  return chain.filter(exchange);
 
  }
 
 
  @override
  public int getorder() {
  return integer.min_value;
  }
 
  /**
   * readformdata
   * @param exchange
   * @param chain
   * @return
   */
  private mono<void> readformdata(serverwebexchange exchange,gatewayfilterchain chain,gatewaycontext gatewaycontext){
  httpheaders headers = exchange.getrequest().getheaders();
  return exchange.getformdata()
    .doonnext(multivaluemap -> {
      gatewaycontext.setformdata(multivaluemap);
      log.debug("[gatewaycontext]read formdata:{}",multivaluemap);
    })
    .then(mono.defer(() -> {
      charset charset = headers.getcontenttype().getcharset();
      charset = charset == null? standardcharsets.utf_8:charset;
      string charsetname = charset.name();
      multivaluemap<string, string> formdata = gatewaycontext.getformdata();
      /**
       * formdata is empty just return
       */
      if(null == formdata || formdata.isempty()){
      return chain.filter(exchange);
      }
      stringbuilder formdatabodybuilder = new stringbuilder();
      string entrykey;
      list<string> entryvalue;
      try {
      /**
       * remove system param ,repackage form data
       */
      for (map.entry<string, list<string>> entry : formdata.entryset()) {
        entrykey = entry.getkey();
        entryvalue = entry.getvalue();
        if (entryvalue.size() > 1) {
        for(string value : entryvalue){
          formdatabodybuilder.append(entrykey).append("=").append(urlencoder.encode(value, charsetname)).append("&");
        }
        } else {
        formdatabodybuilder.append(entrykey).append("=").append(urlencoder.encode(entryvalue.get(0), charsetname)).append("&");
        }
      }
      }catch (unsupportedencodingexception e){
      //ignore urlencode exception
      }
      /**
       * substring with the last char '&'
       */
      string formdatabodystring = "";
      if(formdatabodybuilder.length()>0){
      formdatabodystring = formdatabodybuilder.substring(0, formdatabodybuilder.length() - 1);
      }
      /**
       * get data bytes
       */
      byte[] bodybytes = formdatabodystring.getbytes(charset);
      int contentlength = bodybytes.length;
      serverhttprequestdecorator decorator = new serverhttprequestdecorator(
        exchange.getrequest()) {
      /**
       * change content-length
       * @return
       */
      @override
      public httpheaders getheaders() {
        httpheaders httpheaders = new httpheaders();
        httpheaders.putall(super.getheaders());
        if (contentlength > 0) {
        httpheaders.setcontentlength(contentlength);
        } else {
        httpheaders.set(httpheaders.transfer_encoding, "chunked");
        }
        return httpheaders;
      }
 
      /**
       * read bytes to flux<databuffer>
       * @return
       */
      @override
      public flux<databuffer> getbody() {
        return databufferutils.read(new bytearrayresource(bodybytes),new nettydatabufferfactory(bytebufallocator.default),contentlength);
      }
      };
      serverwebexchange mutateexchange = exchange.mutate().request(decorator).build();
      log.debug("[gatewaycontext]rewrite form data :{}",formdatabodystring);
      return chain.filter(mutateexchange);
    }));
  }
 
  /**
   * readjsonbody
   * @param exchange
   * @param chain
   * @return
   */
  private mono<void> readbody(serverwebexchange exchange,gatewayfilterchain chain,gatewaycontext gatewaycontext){
  /**
   * join the body
   */
  return databufferutils.join(exchange.getrequest().getbody())
    .flatmap(databuffer -> {
      /**
       * read the body flux<databuffer>
       */
      databufferutils.retain(databuffer);
      flux<databuffer> cachedflux = flux.defer(() -> flux.just(databuffer.slice(0, databuffer.readablebytecount())));
      /**
       * repackage serverhttprequest
       */
      serverhttprequest mutatedrequest = new serverhttprequestdecorator(exchange.getrequest()) {
      @override
      public flux<databuffer> getbody() {
        return cachedflux;
      }
      };
      /**
       * mutate exchage with new serverhttprequest
       */
      serverwebexchange mutatedexchange = exchange.mutate().request(mutatedrequest).build();
      /**
       * read body string with default messagereaders
       */
      return serverrequest.create(mutatedexchange, messagereaders)
        .bodytomono(string.class)
        .doonnext(objectvalue -> {
        gatewaycontext.setcachebody(objectvalue);
        log.debug("[gatewaycontext]read jsonbody:{}",objectvalue);
        }).then(chain.filter(mutatedexchange));
    });
  }
 
}
在后续filter中,可以直接从serverexchange中获取gatewaycontext,就可以获取到缓存的数据,如果需要缓存其他数据,则可以根据自己的需求,添加到gatewaycontext中即可
复制代码 代码如下:
gatewaycontext gatewaycontext = exchange.getattribute(gatewaycontext.cache_gateway_context);
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持CodeAE代码之家
原文链接:https://segmentfault.com/a/1190000017898354

关注下面的标签,发现更多相似文章