Spring Cloud Gateway获取requestBody异常,DataBufferLimitException:Exceeded limit on max bytes to buffer :
Spring Cloud Gateway获取requestBody异常,DataBufferLimitException:Exceeded limit on max bytes to buffer : 262144问题描述gateway网关层进行了参数的加解密操作,但是由于加解密比较复杂,造成参数过多,参数值也比较长,导致网关报错,查询日志发现报org.springframework.core.i
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
分析过程
-
查看报错位置的源码,发现里面有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); } }
-
查找调用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); }); } }
-
通过上面的源码,可以发现AbstractDataBufferDecoder类中maxInMemorySize = 262144为默认值,但是有set方法,所以应该可以通过set方法来设置新值。
解决过程
论坛上找了一些方法,但是自己测试发现都不生效,可能是我的Spring版本问题。遇到该问题的小伙伴可以先参考下面链接:
解决方案
-
自定义WebFluxConfiguration配置类,配置默认maxInMemorySize为16Mb。
@Configuration public class WebFluxConfiguration implements WebFluxConfigurer { @Override public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024); } }
-
在新建的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
更多推荐
所有评论(0)