SpringCloud Gateway讀取Request Body

我們使用SpringCloud Gateway做微服務網關的時候,經常需要在過濾器Filter中讀取到Post請求中的Body內容進行日誌記錄、簽名驗證、權限驗證等操作。我們知道,Request的Body是隻能讀取一次的,如果直接通過在Filter中讀取,而不封裝回去回導致後面的服務無法讀取數據。
SpringCloud Gateway 內部提供了一個斷言工廠類ReadBodyPredicateFactory,這個類實現了讀取Request的Body內容並放入緩存,我們可以通過從緩存中獲取body內容來實現我們的目的。

1、分析ReadBodyPredicateFactory

public AsyncPredicate<ServerWebExchange> applyAsync(ReadBodyPredicateFactory.Config config) {
        return (exchange) -> {
            Class inClass = config.getInClass();
            Object cachedBody = exchange.getAttribute("cachedRequestBodyObject");
            if (cachedBody != null) {
                try {
                    boolean test = config.predicate.test(cachedBody);
                    exchange.getAttributes().put("read_body_predicate_test_attribute", test);
                    return Mono.just(test);
                } catch (ClassCastException var7) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Predicate test failed because class in predicate does not match the cached body object", var7);
                    }

                    return Mono.just(false);
                }
            } else {
                return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap((dataBuffer) -> {
                    DataBufferUtils.retain(dataBuffer);
                    final Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                        return Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount()));
                    });
                    ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                        public Flux<DataBuffer> getBody() {
                            return cachedFlux;
                        }
                    };
                    return ServerRequest.create(exchange.mutate().request(mutatedRequest).build(), messageReaders).bodyToMono(inClass).doOnNext((objectValue) -> {
                        exchange.getAttributes().put("cachedRequestBodyObject", objectValue);
                        exchange.getAttributes().put("cachedRequestBody", cachedFlux);
                    }).map((objectValue) -> {
                        return config.predicate.test(objectValue);
                    });
                });
            }
        };
    }

通過查看ReadBodyPredicateFactory內部實現,我們可以看到,該工廠類將request body內容讀取後存放在 exchange的cachedRequestBodyObject中。那麼我們可以通過代碼:exchange.getAttribute(“cachedRequestBodyObject”); //將body內容取出來。
知道如何取body內容後,我們只需將該工廠類註冊到yml配置文件中的predicates,然後從Filter中獲取即可。

2、配置ReadBodyPredicateFactory
查看ReadBodyPredicateFactory關於配置的代碼:

public <T> ReadBodyPredicateFactory.Config setPredicate(Class<T> inClass, Predicate<T> predicate) {
            this.setInClass(inClass);
            this.predicate = predicate;
            return this;
        }

配置該工廠類需要兩個參數:
inClass:接收body內容的對象Class,我們用字符串接收,配置String即可。
Predicate:Predicate的接口實現類,我們自定義一個Predicate的實現類即可。

自定義Predicate實現,並註冊Bean。

    /**
     * 用於readBody斷言,可配置到yml
     * @return
     */
    @Bean
    public Predicate bodyPredicate(){
        return new Predicate() {
            @Override
            public boolean test(Object o) {
                return true;
            }
        };
    }

兩個參數都有了,直接在yml中配置:

        predicates:
        - Path=/card/api/**
        - name: ReadBodyPredicateFactory #使用ReadBodyPredicateFactory斷言,將body讀入緩存
          args:
            inClass: '#{T(String)}'
            predicate: '#{@bodyPredicate}' #注入實現predicate接口類

3、編寫自定義GatewayFilterFactory
編寫自己的過濾器工廠類,讀取緩存的body內容,並支持在配置文件中配置。

public class ReadBodyGatewayFilterFactory
        extends AbstractGatewayFilterFactory<ReadBodyGatewayFilterFactory.Config> {

    private Logger logger = LoggerFactory.getLogger(ReadBodyGatewayFilterFactory.class);

    private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";

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

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            //利用ReadBodyPredicateFactory斷言,會將body讀入exchange的cachedRequestBodyObject中
            Object requestBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
            logger.info("request body is:{}", requestBody);

            return chain.filter(exchange);
        });
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("withParams");//將參數放入
    }

    public static class Config {
        private boolean withParams;//接收配置的參數值,可以隨便寫

        public boolean isWithParams() {
            return withParams;
        }

        public void setWithParams(boolean withParams) {
            this.withParams = withParams;
        }
    }
}

將ReadBodyGatewayFilterFactory工程類在容器中注入。

     /**
     * 注入ReadBody過濾器
     * @return
     */
    @Bean
    public ReadBodyGatewayFilterFactory readBodyGatewayFilterFactory() {
        return new ReadBodyGatewayFilterFactory();
    }

到此,我們的Filter類也可以在yml配置文件中直接配置使用了。

4、完整的yml配置

      - id: body_route #讀取post中的body路由
        order: 5
        uri: lb://API-CARD
        filters:
        - ReadBody=true #使用自定義的過濾器工廠類,讀取request body內容
        predicates:
        - Path=/card/api/**
        - name: ReadBodyPredicateFactory #使用ReadBodyPredicateFactory斷言,將body讀入緩存
          args:
            inClass: '#{T(String)}'
            predicate: '#{@bodyPredicate}' #注入實現predicate接口類

OK,以上是通過ReadBodyPredicateFactory這個類讀取到request body內容。
另外springcloud gateway內部還提供了ModifyRequestBodyGatewayFilterFactory類用於修改body內容,既然能修改,自然也能獲取body,大家可自行去研究。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章