最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Cloud Gateway(讀取、修改 Request Body)的操作

 更新時間:2020年12月01日 14:39:29   作者:好一則博  
這篇文章主要介紹了Spring Cloud Gateway(讀取、修改 Request Body)的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧

Spring Cloud Gateway(以下簡稱 SCG)做為網(wǎng)關(guān)服務(wù),是其他各服務(wù)對外中轉(zhuǎn)站,通過 SCG 進(jìn)行請求轉(zhuǎn)發(fā)。

在請求到達(dá)真正的微服務(wù)之前,我們可以在這里做一些預(yù)處理,比如:來源合法性檢測,權(quán)限校驗(yàn),反爬蟲之類…

因?yàn)闃I(yè)務(wù)需要,我們的服務(wù)的請求參數(shù)都是經(jīng)過加密的。

之前是在各個微服務(wù)的攔截器里對來解密驗(yàn)證的,現(xiàn)在既然有了網(wǎng)關(guān),自然而然想把這一步驟放到網(wǎng)關(guān)層來統(tǒng)一解決。

如果是使用普通的 Web 編程中(比如用 Zuul),這本就是一個 pre filter 的事兒,把之前 Interceptor 中代碼搬過來稍微改改就 OK 了。

不過因?yàn)槭褂玫?SCG,它基于 Spring 5 的 WebFlux,即 Reactor 編程,要讀取 Request Body 中的請求參數(shù)就沒那么容易了。

本篇內(nèi)容涉及 WebFlux 的響應(yīng)式編程及 SCG 自定義全局過濾器,如果對這兩者不了解的話,可以先看看相關(guān)的內(nèi)容。

兩個大坑

我們先建一個 Filter 來看看

public class ValidateFilter implements GlobalFilter, Ordered {
 @Override
 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  ServerHttpRequest request = exchange.getRequest();
  HttpHeaders headers = request.getHeaders();
  MultiValueMap<String, HttpCookie> cookies = request.getCookies();
  MultiValueMap<String, String> queryParams = request.getQueryParams();
  Flux<DataBuffer> body = request.getBody();
  return null;
 }

 @Override
 public int getOrder() {
  return 0;
 }
}

從上邊的返回值可以看出,如果是取 Header、Cookie、Query Params 都易如反掌,如果你需要校驗(yàn)的數(shù)據(jù)在這三者之中的話,就沒必要往下看了。

說回 Body,這里是一個Flux<DataBuffer>,即一個包含 0-N 個DataBuffer類型元素的異步序列。

首先不考慮 Request Body 只能讀取一次問題(這個問題可以用緩存解決),我們先來把這個 Flux 轉(zhuǎn)化成我們可以處理的字符串,第一反應(yīng)想到的有兩個辦法:

block() 異步變同步

subscribe() 訂閱并觸發(fā)序列

BUT,理想很豐滿,現(xiàn)實(shí)卻很骨感——這兩個辦法都有問題:

WebFlux 中不能使用阻塞的操作

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-server-epoll-7

subscribe() 只會接收到第一個發(fā)出的元素,所以會導(dǎo)致獲取不全的問題(太長的 Body 會被截?cái)啵?。這個問題網(wǎng)上有人用 AtomicReference<String> 來包裝獲取到字符串,有人用 StringBuilder/StringBuffer

以上兩個問題在網(wǎng)上找了半天,也沒找到一個靠譜的解決辦法,都是人云亦云。特別是第二個問題的所謂的“解決辦法”,大家無非就在是不遺余力的在展示 DataBuffer 轉(zhuǎn) String 的 N 種寫法,而沒有從根本上解決被截?cái)嗟膯栴}。

正確姿勢

2019.08.26 更新:

評論里有網(wǎng)友提醒到 Spring Cloud Gateway 2.1.2 下 DefaultServerRequest、CachedBodyOutputMessage 類的訪問權(quán)限已經(jīng)改了。這一塊我看了一下,源碼確實(shí)改動了一些,不過 DefaultServerRequest 這個類已經(jīng)不需要了,而 CachedBodyOutputMessage 類我們可以模(chao)仿(xi)它的實(shí)現(xiàn)。

其實(shí)這里的實(shí)現(xiàn)不管再怎么變,我們只要死盯著 ModifyRequestBodyGatewayFilterFactory 就行了。即使以后這里邊的相關(guān)類的訪問權(quán)限都改成 Default 了,我們也不用一個個去抄一遍,只要在org.springframework.cloud.gateway.filter.factory.rewrite 這個 package 下寫我們自己的類就好了。

———– 分割線 ———-

最終找到解決方案還是通過研讀 SCG 的源碼。

本文使用的版本:

Spring Cloud: Greenwich.RC2

Spring Boot: 2.1.1.RELEASE

在 org.springframework.cloud.gateway.filter.factory.rewrite 包下有個 ModifyRequestBodyGatewayFilterFactory,顧名思義,這就是修改 Request Body 的過濾器工廠類。

但是這個類我們無法直接使用,因?yàn)橐玫脑掃@個 FilterFactory 只能用 Fluent API 的方式配置,而無法在配置文件中使用,類似于這樣

.route("rewrite_request_upper", r -> r.host("*.rewriterequestupper.org")
 .filters(f -> f.prefixPath("/httpbin")
   .addResponseHeader("X-TestHeader", "rewrite_request_upper")
   .modifyRequestBody(String.class, String.class,
     (exchange, s) -> {
      return Mono.just(s.toUpperCase()+s.toUpperCase());
     })
 ).uri(uri)
)

我更喜歡用配置文件來配置路由,所以這種方式并不是我的菜。

這時候我就需要自己弄一個 GlobalFilter 了。既然官方已經(jīng)提供了“葫蘆”,那么我們就畫個“瓢”吧。

如果了解的 GatewayFilterFactory 和 GatewayFilter 的關(guān)系的話,不用我說你就知道該怎么辦了。不知道也沒關(guān)系,我們把 ModifyRequestBodyGatewayFilterFactory 中紅框部分 copy 出來,粘貼到我們之前創(chuàng)建的 ValidateFilter#filter 中

我們稍作修改,即可實(shí)現(xiàn)讀取并修改 Request Body 的功能了(核心部分見上圖黃色箭頭處)

/**
 * @author yibo
 */
public class ValidateFilter implements GlobalFilter, Ordered {


 @Override
 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  ServerRequest serverRequest = new DefaultServerRequest(exchange);
  // mediaType
  MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
  // read & modify body
  Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
    .flatMap(body -> {
     if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) {

      // origin body map
      Map<String, Object> bodyMap = decodeBody(body);

      // TODO decrypt & auth

      // new body map
      Map<String, Object> newBodyMap = new HashMap<>();

      return Mono.just(encodeBody(newBodyMap));
     }
     return Mono.empty();
    });

  BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
  HttpHeaders headers = new HttpHeaders();
  headers.putAll(exchange.getRequest().getHeaders());

  // the new content type will be computed by bodyInserter
  // and then set in the request decorator
  headers.remove(HttpHeaders.CONTENT_LENGTH);

  CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
  return bodyInserter.insert(outputMessage, new BodyInserterContext())
    .then(Mono.defer(() -> {
     ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
       exchange.getRequest()) {
      @Override
      public HttpHeaders getHeaders() {
       long contentLength = headers.getContentLength();
       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<DataBuffer> getBody() {
       return outputMessage.getBody();
      }
     };
     return chain.filter(exchange.mutate().request(decorator).build());
    }));
 }

 @Override
 public int getOrder() {
  return 0;
 }

 private Map<String, Object> decodeBody(String body) {
  return Arrays.stream(body.split("&"))
    .map(s -> s.split("="))
    .collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
 }

 private String encodeBody(Map<String, Object> map) {
  return map.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
 }
}

至于拿到 Body 后具體要做什么,也就上邊代碼中的TODO部分,就由你自己來發(fā)揮吧~ 別玩壞就好

建議大家可以多關(guān)注關(guān)注 SCG 的源碼,說不定什么時候就會多出一些有用的 Filter 或 FilterFactory。

另外,目前 ModifyRequestBodyGatewayFilterFactory 上的 Javadoc 有這么一句話:

This filter is BETA and may be subject to change in a future release.

所以大家要保持關(guān)注呀~

以上這篇Spring Cloud Gateway(讀取、修改 Request Body)的操作就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java生成條形碼code128(親測有效)

    Java生成條形碼code128(親測有效)

    這篇文章主要介紹了Java生成條形碼code128,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • Java OSS批量下載并壓縮為ZIP代碼實(shí)例

    Java OSS批量下載并壓縮為ZIP代碼實(shí)例

    這篇文章主要介紹了Java OSS批量下載并壓縮為ZIP代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-09-09
  • SpringBoot中忽略實(shí)體類中的某個屬性不返回給前端的方法(示例詳解)

    SpringBoot中忽略實(shí)體類中的某個屬性不返回給前端的方法(示例詳解)

    本文介紹了在Spring Boot中使用Jackson和Fastjson忽略實(shí)體類屬性不返回給前端的方法,在Jackson中,同時使用@JsonProperty和@JsonIgnore時,@JsonIgnore可能失效,Fastjson中可以使用@JSONField(serialize=false)來實(shí)現(xiàn),本文結(jié)合實(shí)例代碼介紹的非常詳細(xì),需要的朋友參考下吧
    2024-11-11
  • Mybatis mapper配置文件xml存放位置

    Mybatis mapper配置文件xml存放位置

    這篇文章主要介紹了Mybatis mapper配置文件xml存放位置,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2023-12-12
  • java隊(duì)列之queue用法實(shí)例分析

    java隊(duì)列之queue用法實(shí)例分析

    這篇文章主要介紹了java隊(duì)列之queue用法實(shí)例分析,Queue 隊(duì)列就是一個先入先出(FIFO)的數(shù)據(jù)結(jié)構(gòu),Queue接口繼承Collection接口。感興趣的可以了解一下
    2020-07-07
  • Mybatis插件之自動生成不使用默認(rèn)的駝峰式操作

    Mybatis插件之自動生成不使用默認(rèn)的駝峰式操作

    這篇文章主要介紹了Mybatis插件之自動生成不使用默認(rèn)的駝峰式操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • 使用java的注解(用在java類的方法上的注解)方法

    使用java的注解(用在java類的方法上的注解)方法

    這篇文章主要介紹了使用java的注解(用在java類的方法上的注解)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • SpringBoot之配置logging日志及在控制臺中輸出過程

    SpringBoot之配置logging日志及在控制臺中輸出過程

    這篇文章主要介紹了SpringBoot之配置logging日志及在控制臺中輸出過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • Java Restful API的攔截詳解

    Java Restful API的攔截詳解

    這篇文章主要介紹了Java 調(diào)用Restful API接口的攔截,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2021-09-09
  • Spring?Boot?實(shí)現(xiàn)?WebSocket?的代碼示例

    Spring?Boot?實(shí)現(xiàn)?WebSocket?的代碼示例

    WebSocket?協(xié)議是獨(dú)立的基于?TCP?協(xié)議。它與?HTTP?的唯一關(guān)系是,它的握手會被?HTTP?服務(wù)器解釋為?Upgrade?請求,接下來通過本文給大家介紹Spring?Boot?實(shí)現(xiàn)?WebSocket?示例詳解,需要的朋友可以參考下
    2022-04-04

最新評論

应用必备| 通许县| 高雄市| 尼玛县| 吉安县| 贵溪市| 昭平县| 云安县| 桐柏县| 韶关市| 普定县| 蓝山县| 新化县| 奉化市| 广河县| 炉霍县| 乐陵市| 南召县| 台南县| 佛冈县| 舒兰市| 云阳县| 河北区| 宁德市| 增城市| 陕西省| 巴林左旗| 慈利县| 渭源县| 沈丘县| 景泰县| 安陆市| 宜宾市| 玉屏| 繁峙县| 新安县| 石台县| 滁州市| 六安市| 达拉特旗| 保德县|