Spring Cloud Gateway获取requestBody异常,DataBufferLimitException:Exceeded limit on max bytes to buffer : 262144


问题描述

    gateway网关层进行了参数的加解密操作,但是由于加解密比较复杂,造成参数过多,参数值也比较长,导致网关报错,查询日志发现报org.springframework.core.io.buffer.DataBufferLimitException:Exceeded limit on max bytes to buffer : 262114,查询相关资料说是网关拦截器读取requestBody时,因为requestBody太大(默认不能超过256kb),抛出的此异常。

  • SpringBoot版本:2.3.2.RELEASE

  • Spring core版本:5.2.8.RELEASE

分析过程

  1. 查看报错位置的源码,发现里面有maxByteCount参数,通过debug方法发现此数值为262144。为默认的256kb。

    public class LimitedDataBufferList extends ArrayList<DataBuffer> {
        private final int maxByteCount;
        private int byteCount;
    ​
        public LimitedDataBufferList(int maxByteCount) {
            this.maxByteCount = maxByteCount;
        }
        private void raiseLimitException() {
            throw new DataBufferLimitException("Exceeded limit on max bytes to buffer : "       + this.maxByteCount);
        }
    }

     

  2. 查找调用LimitedDataBufferList类构造方法传入maxByteCount的类,这里主要复制调用部分的源码。

    public abstract class DataBufferUtils {
        private static final Log logger = LogFactory.getLog(DataBufferUtils.class);
        private static final Consumer<DataBuffer> RELEASE_CONSUMER = 					DataBufferUtils::release;
    
        public DataBufferUtils() {
        }
        
        /**
         * spring 5.1.2.RELEASE版本调用此方法,没有传入,默认为-1
         **/
        public static Mono<DataBuffer> join(Publisher<? extends DataBuffer> dataBuffers) {
            return join(dataBuffers, -1);
        }
        
        /**
         * spring 5.2.8.RELEASE版本调用此方法,这里传入了maxByteCount 
         **/
        public static Mono<DataBuffer> join(Publisher<? extends DataBuffer> buffers, int maxByteCount) {
            Assert.notNull(buffers, "'dataBuffers' must not be null");
            return buffers instanceof Mono ? (Mono)buffers : Flux.from(buffers).collect(() -> {
                //此处传入的在创建LimitedDataBufferList对象时传入了maxByteCount
                return new LimitedDataBufferList(maxByteCount);
            }, LimitedDataBufferList::add).filter((list) -> {
                return !list.isEmpty();
            }).map((list) -> {
                return ((DataBuffer)list.get(0)).factory().join(list);
            }).doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
    }
    public abstract class AbstractDataBufferDecoder<T> extends AbstractDecoder<T> {
        private int maxInMemorySize = 262144;
    ​
        protected AbstractDataBufferDecoder(MimeType... supportedMimeTypes) {
            super(supportedMimeTypes);
        }
    ​
        public void setMaxInMemorySize(int byteCount) {
            this.maxInMemorySize = byteCount;
        }
    ​
        public int getMaxInMemorySize() {
            return this.maxInMemorySize;
        }
    ​
        public Mono<T> decodeToMono(Publisher<DataBuffer> input, ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
            return DataBufferUtils.join(input, this.maxInMemorySize).map((buffer) -> {
                return this.decodeDataBuffer(buffer, elementType, mimeType, hints);
            });
        }
    }

     

  3. 通过上面的源码,可以发现AbstractDataBufferDecoder类中maxInMemorySize = 262144为默认值,但是有set方法,所以应该可以通过set方法来设置新值。

 

解决过程

   论坛上找了一些方法,但是自己测试发现都不生效,可能是我的Spring版本问题。遇到该问题的小伙伴可以先参考下面链接:

 

解决方案

  1. 自定义WebFluxConfiguration配置类,配置默认maxInMemorySize为16Mb。

    @Configuration
    public class WebFluxConfiguration implements WebFluxConfigurer {
    ​
        @Override
        public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
            configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024);
        }
    }

     

  2. 在新建的GatewayFilterFactory构造器中将ServerCodecConfigurer作为参数传递进去,并获取messageReaders,祥见示例代码。

    @Component
    @Slf4j
    public class MyModifyRequestBodyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyModifyRequestBodyGatewayFilterFactory.Config> {
    ​
        //此处需要定义List<HttpMessageReader<?>>
        private final List<HttpMessageReader<?>> messageReaders;
    ​
        //构造方法
        public MyModifyRequestBodyGatewayFilterFactory(ServerCodecConfigurer serverCodecConfigurer) {
            super(MyModifyRequestBodyGatewayFilterFactory.Config.class);
            //将messageReaders通过传进来的serverCodecConfigurer赋值。
            this.messageReaders = serverCodecConfigurer.getReaders();
        }
    ​
        @Override
        public GatewayFilter apply(Config config) {
            return new MyModifyRequestBodyGatewayFilter(config);
        }
    ​
        public class MyModifyRequestBodyGatewayFilter implements GatewayFilter, Ordered {
    ​
            private final Config config;
            public MyModifyRequestBodyGatewayFilter(Config config){
                this.config = config;
            }
    ​
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                if (!config.isEnable()){
                    return chain.filter(exchange);
                }
                //此处创建ServerRequest一定要传入messageReaders,不能使用ServerRequest serverRequest = new DefaultServerRequest(exchange);创建
                ServerRequest serverRequest = ServerRequest.create(exchange,messageReaders);
                ServerHttpRequest serverHttpRequest = exchange.getRequest();
                MediaType mediaType = serverHttpRequest.getHeaders().getContentType();
                Mono<String> modifyBody = serverRequest.bodyToMono(String.class).flatMap(o->{
                    if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){
                        // TODO: 2020/12/25 o为获取到的requestBody,最大限制为前面配置的16mb,默认是256k。
                        return Mono.justOrEmpty(o);
                    }
                    return Mono.empty();
                });
    ​
                BodyInserter bodyInserter = BodyInserters.fromPublisher(modifyBody,String.class);
                HttpHeaders headers = new HttpHeaders();
                headers.putAll(exchange.getRequest().getHeaders());
                headers.remove(HttpHeaders.CONTENT_LENGTH);
                headers.set(HttpHeaders.CONTENT_TYPE,serverHttpRequest.getHeaders().getFirst("Content-Type"));
                CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange,headers);
                return bodyInserter.insert(outputMessage, new BodyInserterContext())
                        .then(Mono.defer(()->{
                            ServerHttpRequestDecorator requestDecorator = 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(requestDecorator).build());
                        }));
            }
    ​
            @Override
            public int getOrder() {
                return 0;
            }
        }
    ​
        public static class Config{
            private boolean enable;
            public Config(){
    ​
            }
    ​
            public boolean isEnable() {
                return enable;
            }
    ​
            public void setEnable(boolean enable) {
                this.enable = enable;
            }
        }
    }
     

总结

   希望可以帮助到遇到此问题的小伙伴,本文解决方法参考:https://github.com/spring-cloud/spring-cloud-gateway/issues/1658

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐