美文网首页
SpringCloud Gateway读取Request Bod

SpringCloud Gateway读取Request Bod

作者: 古甲哈醒 | 来源:发表于2020-05-14 15:00 被阅读0次

    我们使用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,大家可自行去研究。

    相关文章

      网友评论

          本文标题:SpringCloud Gateway读取Request Bod

          本文链接:https://www.haomeiwen.com/subject/cdetohtx.html