Spring Cloud Gateway 獲取request body(基於源碼改造,不走彎路)

在使用Spring Cloud Gateway的過程當中,常常須要獲取request body,好比用來作日誌記錄、簽名驗證、加密解密等等。html

網上的資料,解決方案五花八門。因此就整理了通過驗證且已經在線上使用的兩種方法,都是基於官方源碼進行擴展。java

本文使用的Spring Cloud Gateway版本爲2.1.1.RELEASE。spring

ModifyRequestBodyGatewayFilterFactory

ModifyRequestBodyGatewayFilterFactory在官方文檔中的介紹以下:json

This filter can be used to modify the request body before it is sent downstream by the Gateway.

也就是用來修改request body的,既然能修改,天然就能獲取到。緩存

java配置

一個簡單的配置以下:微信

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
  return builder.routes()
    .route("rewrite_request_body", r -> r.path("/post_json")
           .filters(f -> f.modifyRequestBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE, (exchange, s) -> Mono.just(s)))
           .uri("lb://waiter"))
    .build();
}

注意modifyRequestBody的方法,有四個參數app

  • inClass:轉換前request body 的類型。
  • outClass:轉換後request body的類型。
  • newContentType:轉換後的ContentType。
  • rewriteFunction:改寫body的方法。這裏就能夠取到request body。

須要特別注意的是inClass和outClass的類型,若是設置得不對,會報錯。ide

小結

優勢post

  • 不只能夠讀取request body,還能夠進行修改。

缺點ui

  • 若是隻須要獲取不須要修改,用這個filter就顯得有點重了。
  • 若是多個filter裏都須要讀取,官方提供的並無支持緩存,沒法一次讀屢次取。(主要也不是用來幹這個的)

ReadBodyPredicateFactory(推薦)

ReadBodyPredicateFactory是用來讀取並判斷request body是否匹配的謂詞。只是在官方文檔中都沒有說明,但不影響使用。

在代碼裏有以下注釋:

We can only read the body from the request once, once that happens if we try to read the body again an exception will be thrown. The below if/else caches the body object as a request attribute in the ServerWebExchange so if this filter is run more than once (due to more than one route using it) we do not try to read the request body multiple times

大體意思是咱們只能從request中讀取request body一次,若是讀取過了,再次讀取就會拋錯。下面的代碼把request body看成了request attribute緩存在ServerWebExchange中,若是這個filter運行了屢次,也無需讀取request body屢次。

java配置

一個簡單的配置以下:

@Autowired
private LogRequestBodyGatewayFilterFactory logRequestBodyGatewayFilterFactory;

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
  return builder.routes()
    .route("rewrite_json", r -> r.path("/post_json")
           .and()
           .readBody(String.class, requestBody -> true)
           .filters(f -> f.filter(logRequestBodyGatewayFilterFactory.apply(new LogRequestBodyGatewayFilterFactory.Config())))
           .uri("lb://waiter"))
    .build();
}

注意readBody的方法,有兩個參數

  • inClass:轉換前request body 的類型,會把request body轉爲這個類型。
  • predicate:謂詞判斷,若是隻是想用作記錄,這裏能夠一直返回true,若是返回false,會致使路由匹配不上。

須要特別注意的是inClass的類型,若是設置得不對,會報錯。

LogRequestBodyGatewayFilterFactory是自定義的一個GatewayFilterFactory,因爲ReadBodyPredicateFactory會緩存request body到ServerWebExchange,須要用的地方只須要從ServerWebExchange中獲取便可。

@Slf4j
@Component
public class LogRequestBodyGatewayFilterFactory extends
        AbstractGatewayFilterFactory<LogRequestBodyGatewayFilterFactory.Config> {

    private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";

    public LogRequestBodyGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String requestBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
            log.info(requestBody);
            return chain.filter(exchange);
        };
    }

    public static class Config {
    }
}

小結

優勢

  • 一次讀屢次取。
  • 能夠用作謂詞判斷,只接受指定類型的請求體。

擴展

官方文檔中說這兩種方式都沒法經過配置文件進行處理,這樣不是很靈活,很難知足實際的須要。

擴展起來其實也不是很難,只要把沒法經過配置文件進行配置的內容屏蔽掉就能夠,對於ModifyRequestBodyGatewayFilterFactoryRewriteFunction,對於ReadBodyPredicateFactoryPredicate

有兩種可行的方案

  • 直接在代碼中把邏輯寫死,去掉這個配置屬性。
  • 經過注入bean的方式,把原來基於接口的實現改成bean注入。相似於RequestRateLimiterGatewayFilterFactory中注入keyResolver

第一種方式在這裏不詳細說明了,本身實現便可。

若是採用第二種方式,下面的配置與上文中ReadBodyPredicateFactory部分寫的java config是等效的。

spring.cloud.gateway.routes[0].id=rewrite_json
spring.cloud.gateway.routes[0].predicates[0]=Path=/post_json
spring.cloud.gateway.routes[0].predicates[1].name=ReadBodyPredicateFactory
spring.cloud.gateway.routes[0].predicates[1].args.inClass=#{T(String)}
spring.cloud.gateway.routes[0].predicates[1].args.predicate=#{@testPredicate}
spring.cloud.gateway.routes[0].filters[0].name=LogRequestBody
spring.cloud.gateway.routes[0].uri=lb://waiter

須要提供一個bean,也就是配置文件中的testPredicate

@Component
public class TestPredicate implements Predicate {
    @Override
    public boolean test(Object o) {
        return true;
    }
}

主要利用了SpEL,注入類型和bean。

將來

目前不管是ModifyRequestBodyGatewayFilterFactory仍是ReadBodyPredicateFactory在2.1.1.RELEASE都仍是BETA版本,但在2.2.0.RC1中,這兩個類上關於BETA版本的註釋都已經沒了,放心大膽的用吧。

參考

看到了這裏必定是真愛了,關注微信公衆號【憨憨的春天】第一時間獲取更新。
qrcode_for_gh_7fff61e23381_344.jpg

相關文章
相關標籤/搜索