使用泽西流式传输大型响应,异步

时间:2016-11-22 13:51:25

标签: java servlets asynchronous jersey jax-rs

我想允许客户端(包括非常慢的客户端)从JAX-RS(泽西岛)Web服务下载大文件而且我被卡住了。似乎JAX-RS中的异步胖子不支持这一点。

    如果您必须等待资源在服务器端可用,
  • AsyncResponse解决了该问题,但您只能拨打AsyncResponse.resume(Object)一次。之后,响应正常处理。慢速或恶意客户端将阻塞工作线程,直到传输完所有字节。这里没有异步IO。
  • 泽西中的
  • ChunkedOutput将块存储在无限的内存中队列中,并且不提供任何公共接口来检查该队列的大小。它设计用于缓慢的小块流。足够慢的客户端最终会导致OutOfMemoryError
  • StreamingOutput根本不是异步的。 StreamingOutput.write(OutputStream)方法应该阻塞,直到写入所有字节为止。
  • Servlet 3.x API确实支持我需要的东西,但我找不到从JAX-RS请求处理程序中获取servlet级别(HttpServletRequest.startAsync)的方法,而不会破坏jerseys内部。 - > IllegalStateException

我没有看到明显的解决方案吗?

2 个答案:

答案 0 :(得分:4)

对于合适的新版球衣和码头,以下作品:

  • @Suspended AsyncResponse注入您的jax-rs请求处理程序方法。这告诉球衣进入异步模式并保持请求打开。
  • 注入@Context HttpServletRequest以访问servlet级API。
  • 调用HttpServletRequest.getAsyncContext()而不是HttpServletRequest.startAsync(),因为泽西已经切换到异步模式并再次执行此操作会导致IllegalStateException(这是我上面的问题)。
  • 在servlet环境中使用此AsyncContext。泽西岛不抱怨。
  • 完成后,请致电AsyncContext.complete(),然后致电AsyncResponse.cancel()。我认为后者是可选的。

我设法以这种方式向100个并发客户端提供10GB文件。线程数从未超过~40个线程,内存消耗很低。我的笔记本电脑的吞吐量约为3GB / s,这有点令人印象深刻。

@GET
public void doAsync(@Suspended final AsyncResponse asyncResponse,
                    @Context HttpServletRequest servletRequest)
        throws IOException {
    assert servletRequest.isAsyncStarted();
    final AsyncContext asyncContext = servletRequest.getAsyncContext();
    final ServletOutputStream s = asyncContext.getResponse().getOutputStream();

    s.setWriteListener(new WriteListener() {

        volatile boolean done = false;

        public void onWritePossible() throws IOException {
            while (s.isReady()) {
                if(done) {
                    asyncContext.complete();
                    asyncResponse.isCancelled();
                    break;
                } else {
                    s.write(...);
                    done = true;
                }
            }
        }
    });
}

答案 1 :(得分:0)

我遇到了类似你的问题。我需要在我的应用程序的两个实例之间传输大量数据。最初我使用简单的StreamingOutput方法,但很快我就明白这不会起作用,因为与服务器方相比,客户端方案速度相当慢,而且我一直在获得TimeOutException。我能够通过设置我的Grizzly服务器来解决这个问题。通过这种方式,我可以使用StreamingOutput方法传输数百兆字节。我设置超时的代码是这样的:

Collection<NetworkListener> listeners = server.getListeners(); 
for(NetworkListener listener : listeners) {
    final TCPNIOTransport transport = listener.getTransport();
    transport.setKeepAlive(true);
    transport.setWriteTimeout(0, TimeUnit.MINUTES);
}