SpringCloudGateway上传文件EOF错误
基础环境描述所有的请求统一发送至 网关(SpringCloudGateway) 由网关处理完数据后通过nacos分发到子服务问题描述普通的接口请求没有什么大毛病,但是上传大文件时候,上传服务会报EOF错误(如下图)上传服务的错误:网关的报错:问题分析原因1: 大家都知道springcloud的gateway并不是使用mvc那套servlet处理的,而是使用webflux处理请求, webflux属
基础环境描述
所有的请求统一发送至 网关(SpringCloudGateway) 由网关处理完数据后通过nacos分发到子服务
问题描述
普通的接口请求没有什么大毛病,但是上传大文件时候,上传服务会报EOF错误(如下图)
上传服务的错误:
网关的报错:
问题分析
- 原因1: 大家都知道springcloud的gateway并不是使用mvc那套servlet处理的,而是使用webflux处理请求, webflux属于响应式, 请求分发完成后就会等待callback回调, 自己分析可能是这个原因(后面分析并不是)
- 原因2: 在org.springframework.web.reactive.socket.adapter.ReactorNettyWebSocketSession 类中, 构造方法指定了单包最大传输数据量是65536(64kb), 数据来源是继承至父类的一个常量
不知道大家有没有主意到NettyWebSocketSessionSupport还有一个构造方法可以指定数据量的大小, 但是这个构造方法我没有找到在哪里可以调用(后面甚至想到了复写这个类, 但是调用太复杂, 复写后还需要覆盖很多关联类, 导致项目启动失败, 放弃这个想法了)
另外除了这个类, 还有一个netty的websocket握手工厂类指定了单包传输量大小io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory
和NettyWebSocketSessionSupport不同的是,这个参数是可以通过yml文件指定的,配置方法如下:
spring:
cloud:
gateway:
httpclient:
websocket:
max-frame-payload-length: 1024*1024*10
该参数会通过org.springframework.cloud.gateway.config.HttpClientProperties这个类中的Websocket.setMaxFramePayloadLength方法初始化
- 原因3: 由于webflux存在大的响应包会拆分返回, 因此猜测请求包是不是也会进行拆分发送 , 导致后台上传服务未正确读到文件结束标志? 因此在gateway增加一个filter处理请求,将请求流读到一个byte[]中, 再重新构建一个reques进行发送
ServerRequest serverRequest = new DefaultServerRequest(exchange, messageReaders);
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
Mono<String> rawBody = serverRequest.bodyToMono(String.class).map(s -> s);
BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(rawBody, String.class);
HttpHeaders tempHeaders = new HttpHeaders();
tempHeaders.putAll(exchange.getRequest().getHeaders());
tempHeaders.remove(HttpHeaders.CONTENT_LENGTH);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, tempHeaders);
AtomicReference<ResponseResult> flag = new AtomicReference<>(ResponseResult.ok());
//读取请求流
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
Flux<DataBuffer> body = outputMessage.getBody();
DataBufferHolder holder = new DataBufferHolder();
body.subscribe(dataBuffer -> {
try {
int len = dataBuffer.readableByteCount();
holder.length = len;
byte[] bytes = new byte[len];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
DataBuffer data = bufferFactory.allocateBuffer();
data.write(bytes);
holder.dataBuffer = data;
}catch (BusinessException e){
flag.set(new ResponseResult(e.getCode(),e.getErrorMessage()));
}
});
ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public HttpHeaders getHeaders() {
long contentLength = tempHeaders.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 Flux.just(holder.dataBuffer);
}
};
if(flag.get().getCode().equals(ResponseResult.CodeStatus.OK)){
return chain.filter(exchange.mutate().request(requestDecorator).build());
}else{
return gatewayResponse(flag.get(),exchange);
}
}));
代码更正(上面的代码会导致byte数据长度翻倍, 设置的content-length和数据源相差巨大, 导致上传的文件内容失效,上传后无法访问)
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
DataBufferUtils.retain(buffer);
return Mono.just(buffer);
});
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
return ServerRequest.create(mutatedExchange, MESSAGE_READERS)
.bodyToMono(byte[].class)
.then(chain.filter(mutatedExchange));
});
重启后解决, 至于到底是哪个导致的上传失败, 只怪自己学艺不精, 害 有时间看看源码才知道
更多推荐
所有评论(0)