提供大型文件供下载(Java,Jersey,HTTP,GET)时,客户端断开连接 [英] Connection dropped by client when serving large files for download (Java, Jersey, HTTP, GET)

查看:378
本文介绍了提供大型文件供下载(Java,Jersey,HTTP,GET)时,客户端断开连接的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a HTTP server which serves files in download, and some of these files are pretty large (can be 7 GB or more). When these files are downloaded from some networks, the connection is dropped and we find the following error in the tomcat catalina log:

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

...

(让我知道是否需要更多日志行)

(let me know if you need more log lines)

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

We believe these errors are due to some firewall/proxy/NAT configuration: for some networks we have also determined a threshold over which the connection drops deterministically). We need to find a way to overcome these issues without asking the client to change their configurations (it can be done, as the same clients can download the same file via HTTP from Dropbox). Also, clients must be authenticated (i.e., have a valid session).

这是我的最新代码:

@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之前,我尝试直接使用OutputStreamFile构建响应.结果相同.

Before using StreamingOutput I tried building the response with an OutputStream or with a File directly. Same result.

其他要求:我们无法引入其他框架(例如Spring ...)

Additional requirements: we cannot introduce other frameworks (e.g. Spring...)

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

Any ideas on how to overcome this limitation?

推荐答案

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

I solved the problem simply (!) by specifying the size of the file to be transferred in the Content-Length header, i.e., by adding the following line:

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

这篇关于提供大型文件供下载(Java,Jersey,HTTP,GET)时,客户端断开连接的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆