借助jersey异步传输大型响应

def*_*ull 6 java asynchronous servlets jax-rs jersey

我想允许客户端(包括非常慢的客户端)从JAX-RS(球衣)Web服务下载大文件,但我遇到了麻烦。似乎JAX-RS中的异步功能不支持此功能。

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

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

def*_*ull 5

使用相当新版本的球衣和码头,以下工作:

  • 注入@Suspended AsyncResponse您的 jax-rs 请求处理程序方法。这告诉 jersey 进入异步模式并保持请求打开。
  • 注入@Context HttpServletRequest以访问 servlet 级 API。
  • 调用HttpServletRequest.getAsyncContext()而不是HttpServletRequest.startAsync(),因为 jersey 已经切换到异步模式并且再次这样做会导致一个IllegalStateException(这是我上面的问题)。
  • 使用这个AsyncContext作为你的servlet环境做。泽西岛没有抱怨。
  • 完成后,调用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;
                }
            }
        }
    });
}
Run Code Online (Sandbox Code Playgroud)