如何使用异步servlet +非阻塞IO进行文件下载?

时间:2016-09-22 11:35:01

标签: io java-ee-7 servlet-3.1

我的图像文件存储在数据库中(我知道它们不应该存在,但无法帮助)。 为了能够在客户端上呈现它们,我实现了一个异步servlet,它有助于从数据库列读取二进制流并写入Servlet响应的输出流。传统IO在这里工作得很好。

当我考虑使用异步servlet尝试非阻塞IO(以测试性能)时,响应中返回的二进制数据不断被破坏。

Oracle Blog开始,我已经看到了使用异步NIO servlet上传文件的各种示例,但对我的问题没有帮助。

这是servlet代码:

@WebServlet(asyncSupported = true, urlPatterns = "/myDownloadServlet")
public class FileRetrievalServletAsyncNIO extends HttpServlet
{
    private static final long serialVersionUID = -6914766655133758332L;

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        Queue<byte[]> containerQueue = new LinkedList<byte[]>();

        AsyncContext asyncContext = request.startAsync();
        asyncContext.addListener(new AsyncListenerImpl());
        asyncContext.setTimeout(120000);

        try
        {
            long attachmentId = Long.valueOf(request.getParameter("id"));
            MyAttachmentDataObject retObj = ServletUtils.fetchAttachmentHeaders(attachmentId);

            response = (HttpServletResponse) asyncContext.getResponse();
            response.setHeader("Content-Length", String.valueOf(retObj.getContentLength()));
            if (Boolean.valueOf(request.getParameter(ServletConstants.REQ_PARAM_ENABLE_DOWNLOAD)))
                response.setHeader("Content-disposition", "attachment; filename=" + retObj.getName());
            response.setContentType(retObj.getContentType());
            ServletOutputStream sos = response.getOutputStream();
            ServletUtils.fetchContentStreamInChunks(attachmentId, containerQueue); // reads from database and adds to the queue in chunks
            sos.setWriteListener(new WriteListenerImpl(sos, containerQueue, asyncContext));
        }
        catch (NumberFormatException | IOException exc)
        {
            exc.printStackTrace();
            request.setAttribute("message", "Failed");
        }
    }
}

这是写侦听器实现

public class WriteListenerImpl implements WriteListener
{

    private ServletOutputStream output = null;
    private Queue<byte[]> queue = null;
    private AsyncContext asyncContext = null;
    private HttpServletRequest request = null;
    private HttpServletResponse response = null;

    public WriteListenerImpl(ServletOutputStream sos, Queue<byte[]> q, AsyncContext aCtx)
    {
        output = sos;
        queue = q;
        asyncContext = aCtx;
        request = (HttpServletRequest) asyncContext.getRequest();
    }

    @Override
    public void onWritePossible() throws IOException
    {
        while(output.isReady())
        {
            while (!queue.isEmpty())
            {
                byte[] temp = queue.poll();
                output.write(temp, 0, temp.length);
            }

            asyncContext.complete();
            request.setAttribute("message", "Success");
        }
    }

    @Override
    public void onError(Throwable t)
    {
        System.err.println(t);
        try
        {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        catch (IOException exc)
        {
            exc.printStackTrace();
        }
        request.setAttribute("message", "Failure");
        asyncContext.complete();
    }
}

响应数据如下所示:

Response Data

我做错了什么?

1 个答案:

答案 0 :(得分:0)

不确定您期望输出看起来如何,但就异步i / o而言,您应该在每次写入之前检查output.isReady()。所以你的onWritePossible代码应该是:

while(output.isReady() && !queue.isEmpty())
 {
       byte[] temp = queue.poll();
       output.write(temp, 0, temp.length);
 }

 if (queue.isEmpty()) {
      asyncContext.complete();
      request.setAttribute("message", "Success");
 }

这允许onWritePossible()在写入被阻止时返回,这基本上是异步I / O的要点。

如果在写入被阻止时写入(output.isReady()将返回false),则不同的实现可能会忽略写入或抛出异常。无论哪种方式,您的输出数据都会丢失中间的某些写入或截断。