Netty分块输入流

时间:2018-02-07 16:40:22

标签: java multithreading stream netty inbound

我已经看到很多关于netty中的分块流的问题,但大多数是关于出站流的解决方案,而不是入站流。

我想了解如何从通道获取数据并将其作为InputStream发送到我的业务逻辑,而不首先将所有数据加载到内存中。 这就是我想要做的事情:

public class ServerRequestHandler extends MessageToMessageDecoder<HttpObject> {

  private HttpServletRequest request;
  private PipedOutputStream os;
  private PipedInputStream is;

  @Override
  public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    super.handlerAdded(ctx);
    this.os = new PipedOutputStream();
    this.is = new PipedInputStream(os);
  }

  @Override
  public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    super.handlerRemoved(ctx);
    this.os.close();
    this.is.close();
  }

  @Override
  protected void decode(ChannelHandlerContext ctx, HttpObject msg, List<Object> out)
      throws Exception {
    if (msg instanceof HttpRequest) {
      this.request = new CustomHttpRequest((HttpRequest) msg, this.is);
      out.add(this.request);
    }
    if (msg instanceof HttpContent) {
      ByteBuf body = ((HttpContent) msg).content();

      if (body.readableBytes() > 0)
        body.readBytes(os, body.readableBytes());

      if (msg instanceof LastHttpContent) {
        os.close();
      }
    }

  }

}

然后我有另一个Handler将获取我的CustomHttpRequest并发送到我称之为ServiceHandler的地方,我的业务逻辑将从InputStream中读取。

public class ServiceRouterHandler extends SimpleChannelInboundHandler<CustomHttpRequest> {
...
    @Override
    public void channelRead0(ChannelHandlerContext ctx, CustomHttpRequest request) throws IOException {
...
        future = serviceHandler.handle(request, response);
...

这不起作用,因为当我的Handler将CustomHttpRequest转发给ServiceHandler,并且它尝试从InputStream读取时,线程阻塞,并且HttpContent永远不会在我的Decoder中处理。

我知道我可以尝试为我的商业逻辑创建一个单独的线程,但我的印象是我在这里过于复杂。
我查看了ByteBufInputStream,但它说明了

  

请注意,它只读取可读字节数   在施工时确定。

所以我认为它不适用于Chunked Http请求。另外,我看到了ChunkedWriteHandler,这对于Oubound块来说似乎很好,但我找不到像ChunkedReadHandler那样的东西......

所以我的问题是:最好的方法是什么?我的要求是:

- 在发送ServiceHandler之前,请勿将数据保存在内存中;
- ServiceHandlers API应该是netty不可知的(这就是我使用CustomHttpRequest而不是Netty的HttpRequest的原因);

更新 我在CustomHttpRequest上使用更具反应性的方法来实现这一点。现在,请求没有向ServiceHandlers提供一个InputStream,因此它们可以读取(阻塞),而CustomHttpRequest现在有一个返回Future的readInto(OutputStream)方法,所有的服务处理程序都将被执行当此Outputstream已满时。这是它的样子

public class CustomHttpRequest {
  ...constructors and other methods hidden...
  private final SettableFuture<Void> writeCompleteFuture = SettableFuture.create();

  private final SettableFuture<OutputStream> outputStreamFuture = SettableFuture.create();

  private ListenableFuture<Void> lastWriteFuture = Futures.transform(outputStreamFuture, x-> null);

  public ListenableFuture<Void> readInto(OutputStream os) throws IOException {
    outputStreamFuture.set(os);
    return this.writeCompleteFuture;
  }

  ListenableFuture<Void> writeChunk(byte[] buf) {
    this.lastWriteFuture = Futures.transform(lastWriteFuture, (AsyncFunction<Void, Void>) (os) -> {
      outputStreamFuture.get().write(buf);
      return Futures.immediateFuture(null);
    });
    return lastWriteFuture;
  }


  void complete() {
    ListenableFuture<Void> future =
        Futures.transform(lastWriteFuture, (AsyncFunction<Void, Void>) x -> {
          outputStreamFuture.get().close();
          return Futures.immediateFuture(null);
        });
    addFinallyCallback(future, () -> {
      this.writeCompleteFuture.set(null);
    });

  }
}

我更新的ServletRequestHandler看起来像这样:

public class ServerRequestHandler extends MessageToMessageDecoder<HttpObject> {

  private NettyHttpServletRequestAdaptor request;

  @Override
  public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    super.handlerAdded(ctx);
  }

  @Override
  public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    super.handlerRemoved(ctx);
  }


  @Override
  protected void decode(ChannelHandlerContext ctx, HttpObject msg, List<Object> out)
      throws Exception {
    if (msg instanceof HttpRequest) {
      HttpRequest request = (HttpRequest) msg;

      this.request = new CustomHttpRequest(request, ctx.channel());

      out.add(this.request);
    }
    if (msg instanceof HttpContent) {
      ByteBuf buf = ((HttpContent) msg).content();
      byte[] bytes = new byte[buf.readableBytes()];
      buf.readBytes(bytes);

      this.request.writeChunk(bytes);

      if (msg instanceof LastHttpContent) {
        this.request.complete();
      }
    }
  }
}

这很好用,但是请注意,这里的所有内容都是在一个线程中完成的,也许对于大数据我可能想要生成一个新线程来为其他通道释放该线程。

1 个答案:

答案 0 :(得分:0)

您正走在正确的轨道上 - 如果您的serviceHandler.handle(request, response);来电正在执行阻止读取,则需要为其创建新线程。请记住,应该只有少量的Netty工作线程,所以你不应该在工作线程中进行任何阻塞调用。

要问的另一个问题是,您的服务处理程序是否需要阻止?它有什么作用?如果它无论如何都要通过网络挖掘数据,你能否以非阻塞的方式将它整合到Netty管道中?这样,一切都是异步的,不需要阻塞调用和额外的线程。