我正在尝试创建一个Netty(4.1)POC,该POC可以将h2c(不带TLS的HTTP2)帧转发到h2c服务器上-即本质上创建一个Netty h2c代理服务。 Wireshark显示Netty发出了帧,并且h2c服务器进行了回复(例如,带有响应标头和数据),尽管那时我在Netty本身中接收/处理响应HTTP帧时遇到了一些问题。
作为起点,我改编了multiplex.server示例(io.netty.example.http2.helloworld.multiplex.server
),以便在HelloWorldHttp2Handler
中,我不连接伪消息,而是连接到远程节点:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel remoteChannel = null;
// create or retrieve the remote channel (one to one mapping) associated with this incoming (client) channel
synchronized (lock) {
if (!ctx.channel().hasAttr(remoteChannelKey)) {
remoteChannel = this.connectToRemoteBlocking(ctx.channel());
ctx.channel().attr(remoteChannelKey).set(remoteChannel);
} else {
remoteChannel = ctx.channel().attr(remoteChannelKey).get();
}
}
if (msg instanceof Http2HeadersFrame) {
onHeadersRead(remoteChannel, (Http2HeadersFrame) msg);
} else if (msg instanceof Http2DataFrame) {
final Http2DataFrame data = (Http2DataFrame) msg;
onDataRead(remoteChannel, (Http2DataFrame) msg);
send(ctx.channel(), new DefaultHttp2WindowUpdateFrame(data.initialFlowControlledBytes()).stream(data.stream()));
} else {
super.channelRead(ctx, msg);
}
}
private void send(Channel remoteChannel, Http2Frame frame) {
remoteChannel.writeAndFlush(frame).addListener(new GenericFutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
future.cause().printStackTrace();
}
}
});
}
/**
* If receive a frame with end-of-stream set, send a pre-canned response.
*/
private void onDataRead(Channel remoteChannel, Http2DataFrame data) throws Exception {
if (data.isEndStream()) {
send(remoteChannel, data);
} else {
// We do not send back the response to the remote-peer, so we need to release it.
data.release();
}
}
/**
* If receive a frame with end-of-stream set, send a pre-canned response.
*/
private void onHeadersRead(Channel remoteChannel, Http2HeadersFrame headers)
throws Exception {
if (headers.isEndStream()) {
send(remoteChannel, headers);
}
}
private Channel connectToRemoteBlocking(Channel clientChannel) {
try {
Bootstrap b = new Bootstrap();
b.group(new NioEventLoopGroup());
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.remoteAddress("localhost", H2C_SERVER_PORT);
b.handler(new Http2ClientInitializer());
final Channel channel = b.connect().syncUninterruptibly().channel();
channel.config().setAutoRead(true);
channel.attr(clientChannelKey).set(clientChannel);
return channel;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
在初始化通道管道(在Http2ClientInitializer
中)时,如果执行以下操作:
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(Http2MultiplexCodecBuilder.forClient(new Http2OutboundClientHandler()).frameLogger(TESTLOGGER).build());
ch.pipeline().addLast(new UserEventLogger());
}
然后我可以看到在Wireshark中正确转发了帧,并且h2c服务器使用标头和帧数据进行了答复,但是Netty则通过GOAWAY [INTERNAL_ERROR]进行了答复,原因是:
14:23:09.324 [nioEventLoopGroup-3-1]警告 i.n.channel.DefaultChannelPipeline-一个exceptionCaught()事件是 发射,它到达管道的尾部。通常意味着 管道中的最后一个处理程序未处理异常。 java.lang.IllegalStateException:所需的流对象 标识符:1个 io.netty.handler.codec.http2.Http2FrameCodec $ FrameListener.requireStream(Http2FrameCodec.java:587) 在 io.netty.handler.codec.http2.Http2FrameCodec $ FrameListener.onHeadersRead(Http2FrameCodec.java:550) 在 io.netty.handler.codec.http2.Http2FrameCodec $ FrameListener.onHeadersRead(Http2FrameCodec.java:543)...
如果我改为尝试使其具有http2客户端示例中的管道配置,例如:
@Override
public void initChannel(SocketChannel ch) throws Exception {
final Http2Connection connection = new DefaultHttp2Connection(false);
ch.pipeline().addLast(
new Http2ConnectionHandlerBuilder()
.connection(connection)
.frameLogger(TESTLOGGER)
.frameListener(new DelegatingDecompressorFrameListener(connection, new InboundHttp2ToHttpAdapterBuilder(connection)
.maxContentLength(maxContentLength)
.propagateSettings(true)
.build() ))
.build());
}
然后我反而得到:
java.lang.UnsupportedOperationException:不支持的消息类型: DefaultHttp2HeadersFrame(预期:ByteBuf,FileRegion)位于 io.netty.channel.nio.AbstractNioByteChannel.filterOutboundMessage(AbstractNioByteChannel.java:283) 在 io.netty.channel.AbstractChannel $ AbstractUnsafe.write(AbstractChannel.java:882) 在 io.netty.channel.DefaultChannelPipeline $ HeadContext.write(DefaultChannelPipeline.java:1365)
如果我随后添加HTTP2帧编解码器(Http2MultiplexCodec
或Http2FrameCodec
):
@Override
public void initChannel(SocketChannel ch) throws Exception {
final Http2Connection connection = new DefaultHttp2Connection(false);
ch.pipeline().addLast(
new Http2ConnectionHandlerBuilder()
.connection(connection)
.frameLogger(TESTLOGGER)
.frameListener(new DelegatingDecompressorFrameListener(connection, new InboundHttp2ToHttpAdapterBuilder(connection)
.maxContentLength(maxContentLength)
.propagateSettings(true)
.build() ))
.build());
ch.pipeline().addLast(Http2MultiplexCodecBuilder.forClient(new Http2OutboundClientHandler()).frameLogger(TESTLOGGER).build());
}
然后Netty发送两个连接序帧,导致h2c服务器拒绝GOAWAY [PROTOCOL_ERROR]:
这就是我遇到的问题-即配置远程通道管道,以使其无错误地发送Http2Frame
对象,但在收到响应后又在Netty中接收/处理它们。 / p>
请问有人有什么想法/建议吗?
答案 0 :(得分:0)
我最终开始了这项工作;以下Github问题包含一些有用的代码/信息:
我需要进一步研究一些注意事项,尽管该方法的要点是您需要将频道包装在Http2StreamChannel中,这意味着我的connectToRemoteBlocking()
方法最终以:
private Http2StreamChannel connectToRemoteBlocking(Channel clientChannel) {
try {
Bootstrap b = new Bootstrap();
b.group(new NioEventLoopGroup());
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.remoteAddress("localhost", H2C_SERVER_PORT);
b.handler(new Http2ClientInitializer());
final Channel channel = b.connect().syncUninterruptibly().channel();
channel.config().setAutoRead(true);
channel.attr(clientChannelKey).set(clientChannel);
// TODO make more robust, see example at https://github.com/netty/netty/issues/8692
final Http2StreamChannelBootstrap bs = new Http2StreamChannelBootstrap(channel);
final Http2StreamChannel http2Stream = bs.open().syncUninterruptibly().get();
http2Stream.attr(clientChannelKey).set(clientChannel);
http2Stream.pipeline().addLast(new Http2OutboundClientHandler()); // will read: DefaultHttp2HeadersFrame, DefaultHttp2DataFrame
return http2Stream;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
然后防止“标识符所需的Stream对象:1” 错误(实际上是说:'此(客户端)HTTP2请求是新的,因此为什么要使用此特定流?-由于我们隐式地重用了最初接收到的“服务器”请求中的流对象),因此当在以下位置转发数据时,我们需要更改为使用远程通道的流:
private void onHeadersRead(Http2StreamChannel remoteChannel, Http2HeadersFrame headers) throws Exception {
if (headers.isEndStream()) {
headers.stream(remoteChannel.stream());
send(remoteChannel, headers);
}
}
然后,已配置的通道入站处理程序(由于其用法我称为Http2OutboundClientHandler
)将以正常方式接收传入的HTTP2帧:
@Sharable
public class Http2OutboundClientHandler extends SimpleChannelInboundHandler<Http2Frame> {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
cause.printStackTrace();
ctx.close();
}
@Override
public void channelRead0(ChannelHandlerContext ctx, Http2Frame msg) throws Exception {
System.out.println("Http2OutboundClientHandler Http2Frame Type: " + msg.getClass().toString());
}
}