2

我的图像文件存储在数据库中(我知道它们不应该,但无能为力)。为了能够在客户端上呈现它们,我实现了一个异步 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();
    }
}

响应数据如下所示:

响应数据

我究竟做错了什么?

4

1 回答 1

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),则不同的实现可能会忽略写入或抛出异常。无论哪种方式,您的输出数据都会在中间丢失一些写入或被截断。

于 2016-09-27T17:57:36.673 回答