Netty:在客户端可用时向客户端发送响应

时间:2019-06-25 14:09:09

标签: java http stream netty

我有一个Netty服务器,它从客户端接收POST请求,该请求的JSON负载类似于:

{
  "url": [
    "https://www.google.com",
    "https://www.yahoo.com",
    "https://www.duckduckgo.com"
  ]
}

然后,Netty服务器将GET请求发送到“ url” json数组中的每个单独的URL,并将每个请求的响应正文发送给客户端。

以下代码正是这样做的。

private void handleJsonRequest(JsonNode requestNode, Channel channel) throws ParseRequestException {
    List<AuctionRequest> requestList = parseRequestJson(requestNode, getRequestStartTime(channel));

    requestList
      .stream()
      .map(auctionRequest -> {
          log.info("Going to send a request");
          CompletableFuture<AuctionRequestResult> response = getHttpClient().performRequest(auctionRequest);

          return response;
      })
      .forEach(future -> {
          onRequestComplete(channel, future);
      });
}

但是我想将发送到每个URL的GET请求的每个响应发送给客户端。示例:

  1. 客户端将URL列表(http://www.google.comhttp://www.yahoo.comhttp://www.duckduckgo.com)发送到Netty服务器
  2. Netty将GET请求发送到http://www.google.comhttp://www.yahoo.comhttp://www.duckduckgo.com
  3. 几毫秒后,Netty从其他两个URL收到GET http://www.google.com的响应,但尚未。因此,现在我想将GET http://www.google.com的响应发送到客户端,并且仍然等待其他两个URL的GET响应。
  4. Netty从GET https://www.duckduckgo.com获得响应后的一秒钟,我想使用与第2点中使用的相同的连接将来自GET https://www.duckduckgo.com的响应正文发送给客户端
  5. 两秒钟后,Netty从GET https://www.yahoo.com获取响应,并执行在步骤3中执行的相同操作,并关闭该通道并结束对客户端的响应。

下图显示了这种请求流

Data flux

我知道这是服务器端事件或Websocket的好用例,但目前尚不可能。

我尝试使用multi-part响应和octet/stream标头,但是只有第一个响应发送到客户端,客户端未接收到任何后继响应。现在,我尝试遵循these instructions from another StackOverflow question之后的分块响应方法,但没有成功。客户端仅收到Netty发出的3个(或更多)请求中的第一个响应。

这就是我实现onComplete方法的方式,该方法处理Netty收到的每个响应

private void onRequestComplete(Channel channel, CompletableFuture<AuctionRequestResult> future) {
    future.whenComplete((requestResult, throwable) -> {
        if (throwable != null) {
            log.debug("Writing error response for request {}", future.hashCode());

            sendError(throwable.getMessage(), throwable, channel);
            log.debug("Finished writing error response for request {}", future.hashCode());
            return;
        }

        log.info("Writing response for request {}", future.hashCode());
        writeRequestResult(requestResult, channel);
        log.info("Finished writing response for request {}", future.hashCode());
    });
}


private void writeRequestResult(AuctionRequestResult requestResult, Channel channel) {
    ByteBuf buf = getByteBufAllocator().directBuffer(512000);
    JsonWriter jsonWriter = getJsonWriter();
    try {
        // Create DTO and serialize it
        ProxyResponseDto proxyResponseDto = new ProxyResponseDto("ok", requestResult);
        dslJson.serialize(jsonWriter, proxyResponseDto);

        // Write response to the client
        writeResponse(channel, jsonWriter.getByteBuffer(), jsonWriter.size(), false);
    } 
    catch (Exception e) {
        log.warn(e.getMessage());
        closeChannel(channel);
    } 
    finally {
        buf.release();
        jsonWriter.reset();
        jsonWriters.offer(jsonWriter);
    }
}


private void writeResponse(Channel channel, byte[] responseJsonUtf8, int length) {
    String responseJson = new String(responseJsonUtf8);
    ByteBuf buf = Unpooled.copiedBuffer(responseJsonUtf8, 0, responseJsonUtf8.length);
    FullHttpRequest request = getRequestObject(channel);

    log.info("Writing response back in the channel");

    HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
    response.headers().set(TRANSFER_ENCODING, CHUNKED);
    response.headers().set(HttpHeaderNames.CONNECTION, "keep-alive");

    ChunkedInput<ByteBuf> chunkedInput = new ChunkedStream(new ByteBufInputStream(buf));
    HttpChunkedInput httpChunkedInput = new HttpChunkedInput(chunkedInput);

    // Write the response.
    if (channel.isActive()) {
        channel.write(response);
        ChannelFuture future = channel.writeAndFlush(httpChunkedInput);

        future.addListener(listener -> {
            if(listener.isSuccess()) {
                log.info("Wrote data to channel with success!!!");
            }
            else {
                log.error("Error when writting data to channel");
                Throwable cause = listener.cause();

                if(cause != null) {
                    log.error("Error reason: ", cause);
                }
            }
        });
    }
    else {
        log.warn("Exchange connection went dead. Ms from start: {}", (System.currentTimeMillis() - getRequestStartTime(channel)));
        closeChannel(channel);
    }
}

这是日志输出。如您所见,第一个响应已从Netty成功发送到客户端,但是任何进一步的尝试都将引发异常

Calling onComplete
End of calling onComplete
Writing response for request 1897031616
Writing response back in the channel
Finished writing response for request 1897031616
Wrote data to channel with success!!!
Writing response for request -990888115
Writing response back in the channel
Error when writting data to channel
Error reason: 
io.netty.handler.codec.EncoderException: java.lang.IllegalStateException: unexpected message type: io.netty.handler.codec.http.DefaultHttpContent (expected: HttpResponse)
    at io.netty.handler.codec.MessageToMessageEncoder.write(MessageToMessageEncoder.java:107) ~[netty-codec-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.handler.codec.MessageToMessageCodec.write(MessageToMessageCodec.java:116) ~[netty-codec-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:716) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:708) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:791) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:701) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:696) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.handler.stream.ChunkedWriteHandler.doFlush(ChunkedWriteHandler.java:276) ~[netty-handler-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.handler.stream.ChunkedWriteHandler.flush(ChunkedWriteHandler.java:135) ~[netty-handler-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:749) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:741) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.access$2100(AbstractChannelHandlerContext.java:56) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext$WriteAndFlushTask.write(AbstractChannelHandlerContext.java:1150) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext$AbstractWriteTask.run(AbstractChannelHandlerContext.java:1073) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163) [netty-common-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:405) [netty-common-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:338) [netty-transport-native-epoll-4.1.36.Final-linux-x86_64.jar:4.1.36.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:906) [netty-common-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [netty-common-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [netty-common-4.1.36.Final.jar:4.1.36.Final]
    at java.lang.Thread.run(Thread.java:825) [?:?]
Caused by: java.lang.IllegalStateException: unexpected message type: io.netty.handler.codec.http.DefaultHttpContent (expected: HttpResponse)
    at io.netty.handler.codec.http.HttpContentEncoder.ensureHeaders(HttpContentEncoder.java:241) ~[netty-codec-http-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.handler.codec.http.HttpContentEncoder.encode(HttpContentEncoder.java:97) ~[netty-codec-http-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.handler.codec.http.HttpContentEncoder.encode(HttpContentEncoder.java:52) ~[netty-codec-http-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.handler.codec.MessageToMessageCodec$1.encode(MessageToMessageCodec.java:67) ~[netty-codec-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.handler.codec.MessageToMessageEncoder.write(MessageToMessageEncoder.java:89) ~[netty-codec-4.1.36.Final.jar:4.1.36.Final]
    ... 20 more

我的问题是,有人可以在实现推送/流方法时为我指明正确的方向,并在不使用websocket或服务器端事件的情况下将数据从Netty服务器发送到客户端吗?

0 个答案:

没有答案