提供大文件下载时客户端断开连接(Java、Jersey、HTTP、GET)
Connection dropped by client when serving large files for download (Java, Jersey, HTTP, GET)
我有一个用于下载文件的 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
...
(如果您需要更多日志行,请告诉我)
我们认为这些错误是由于某些 firewall/proxy/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;
}
FileStreamingOutput
是 javax.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...)
关于如何克服这个限制有什么想法吗?
我通过在 Content-Length
header 中指定要传输的文件的大小简单地 (!) 解决了这个问题,即添加以下行:
oResponseBuilder.header("Content-Length", oFile.length());
我有一个用于下载文件的 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
...
(如果您需要更多日志行,请告诉我)
我们认为这些错误是由于某些 firewall/proxy/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;
}
FileStreamingOutput
是 javax.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...)
关于如何克服这个限制有什么想法吗?
我通过在 Content-Length
header 中指定要传输的文件的大小简单地 (!) 解决了这个问题,即添加以下行:
oResponseBuilder.header("Content-Length", oFile.length());