我有一个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请求的每个响应发送给客户端。示例:
下图显示了这种请求流
我知道这是服务器端事件或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服务器发送到客户端吗?