【问题标题】:Connection dropped by client when serving large files for download (Java, Jersey, HTTP, GET)提供大型文件以供下载(Java、Jersey、HTTP、GET)时,客户端断开连接
【发布时间】:2019-07-13 01:50:00
【问题描述】:

我有一个提供下载文件的 HTTP 服务器,其中一些文件非常大(可能是 7 GB 或更大)。从某些网络下载这些文件时,连接断开,我们在 tomcat catalina 日志中发现以下错误:

org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer
        at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:393)
        at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:426)
        at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:339)
        at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:418)
        at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:406)
        at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:97)
        at org.glassfish.jersey.message.internal.CommittingOutputStream.write(CommittingOutputStream.java:229)
        at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:2147)

...

SEVERE: org.glassfish.jersey.server.ServerRuntime$Responder: An I/O error has occurred while writing a response message entity to the container output stream.
org.glassfish.jersey.server.internal.process.MappableException: org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe

...

(如果您需要更多日志行,请告诉我)

我们认为这些错误是由于某些防火墙/代理/NAT 配置造成的:对于某些网络,我们还确定了连接确定性断开的阈值)。我们需要找到一种方法来克服这些问题,而无需要求客户端更改其配置(可以这样做,因为相同的客户端可以通过 HTTP 从 Dropbox 下载相同的文件)。此外,客户端必须经过身份验证(即具有有效会话)。

这是我最新的代码:

@GET
@Path("download")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response download(@HeaderParam("x-session-token") String sSessionId, @QueryParam("filename") String sFileName)
{            
        User oUser = MyLib.GetUserFromSession(sSessionId);

        if (oUser == null) {
            return Response.status(Status.UNAUTHORIZED).build();
        }

        File oFile = getEntryFile(sFileName);
        ResponseBuilder oResponseBuilder = null;
        if(oFile == null) {
            oResponseBuilder = Response.serverError();    
        } else {
            FileStreamingOutput oStream = new FileStreamingOutput(oFile);
            oResponseBuilder = Response.ok(oStream);
            oResponseBuilder.header("Content-Disposition", "attachment; filename="+ oFile.getName());
        }
        return oResponseBuilder.build();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

FileStreamingOutputjavax.ws.rs.core.StreamingOutput 的扩展:

public class FileStreamingOutput implements StreamingOutput {

    final File m_oFile;

    public FileStreamingOutput(File oFile){
        if(null==oFile) {
            throw new NullPointerException("FileStreamingOutput.FileStreamingOutput: passed a null File");
        }
        m_oFile = oFile;
    }

    @Override
    public void write(OutputStream oOutputStream) throws IOException, WebApplicationException {
        if(null == oOutputStream) {
            throw new NullPointerException("FileStreamingOutput.write: passed a null OutputStream");
        }
        InputStream oInputStream = null;
        try {
            oInputStream = new FileInputStream(m_oFile);
            long lThreshold = 2L*1024*1024*1024;
            long lSize = m_oFile.length(); 
            if(lSize > lThreshold) {
                IOUtils.copyLarge(oInputStream, oOutputStream);
            } else {
                IOUtils.copy(oInputStream, oOutputStream);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if( oOutputStream!=null ) {
                oOutputStream.flush();
                oOutputStream.close();
            }
            if( oInputStream !=null ) {
                oInputStream.close();
            }
        }
    }
}

在使用StreamingOutput 之前,我尝试使用OutputStream 或直接使用File 构建响应。结果一样。

附加要求:我们不能引入其他框架(例如 Spring...)

关于如何克服这个限制的任何想法?

【问题讨论】:

  • 你检查客户端和服务器端的请求超时了吗?错误是在没有任何规律的情况下发生的,还是在特定时间后发生的?
  • 服务器端超时(nginx 和 Tomcat),这是我们检查的第一件事,显然没有变化。
  • 客户端超时:我不知道如何检查类似的设置:想法?我们无法控制防火墙配置。如果我们直接调用 API,也会出现问题。
  • 我们有一个用于测试的顽固网络,并且在下载 1.2 GB 后确定性地发生错误,与过去的时间无关(我们尝试使用缓冲输出流并减小缓冲区大小和因此下载速度,没有变化)

标签: java rest http jersey large-files


【解决方案1】:

我通过在Content-Length 标头中指定要传输的文件的大小(即添加以下行)简单地解决了这个问题(!):

oResponseBuilder.header("Content-Length", oFile.length());

【讨论】:

    猜你喜欢
    • 2016-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-29
    • 2017-09-22
    • 2012-05-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多