栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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";

  
  private String cacheBody;
  
  private MultiValueMap formData;
  
  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 {

  
  private static final List> messageReaders = HandlerStrategies.withDefaults().messageReaders();

  @Override
  public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    ServerHttpRequest request = exchange.getRequest();
    String path = request.getPath().pathWithinApplication().value();
    GatewayContext gatewayContext = new GatewayContext();
    gatewayContext.getAllRequestData().addAll(request.getQueryParams());
    gatewayContext.setPath(path);
    
    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;
  }

  
  private Mono 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 formData = gatewayContext.getFormData();
   
   if(null == formData || formData.isEmpty()){
     return chain.filter(exchange);
   }
   StringBuilder formDataBodyBuilder = new StringBuilder();
   String entryKey;
   List entryValue;
   try {
     
     for (Map.Entry> 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
   }
   
   String formDataBodyString = "";
   if(formDataBodyBuilder.length()>0){
     formDataBodyString = formDataBodyBuilder.substring(0, formDataBodyBuilder.length() - 1);
   }
   
   byte[] bodyBytes = formDataBodyString.getBytes(charset);
   int contentLength = bodyBytes.length;
   ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
exchange.getRequest()) {
     
     @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;
     }

     
     @Override
     public Flux 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);
 }));
  }

  
  private Mono readBody(ServerWebExchange exchange,GatewayFilterChain chain,GatewayContext gatewayContext){
    
    return DataBufferUtils.join(exchange.getRequest().getBody())
 .flatMap(dataBuffer -> {
   
   DataBufferUtils.retain(dataBuffer);
   Flux cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
   
   ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
     @Override
     public Flux getBody() {
return cachedFlux;
     }
   };
   
   ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
   
   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);

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/139178.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号