Соединение обрывается клиентом при обслуживании больших файлов для загрузки (Java, Jersey, HTTP, GET) - PullRequest
0 голосов
/ 19 февраля 2019

У меня есть HTTP-сервер, который обслуживает загружаемые файлы, и некоторые из этих файлов довольно большие (может быть 7 ГБ или более).Когда эти файлы загружаются из некоторых сетей, соединение обрывается, и мы обнаруживаем следующую ошибку в журнале 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;
}

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 ...)

Есть идеи о том, как преодолеть это ограничение?

1 Ответ

0 голосов
/ 25 февраля 2019

Я решил проблему просто (!), Указав размер файла для передачи в заголовке Content-Length, т. Е. Добавив следующую строку:

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