SpringCloudGateway上传文件EOF错误

基础环境描述

所有的请求统一发送至 网关(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
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值