我的图像文件存储在数据库中(我知道它们不应该存在,但无法帮助)。 为了能够在客户端上呈现它们,我实现了一个异步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();
}
}
响应数据如下所示:
我做错了什么?
答案 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),则不同的实现可能会忽略写入或抛出异常。无论哪种方式,您的输出数据都会丢失中间的某些写入或截断。