Я пытаюсь создать POC Netty (4.1), который может пересылать кадры h2c (HTTP2 без TLS) на сервер h2c, то есть, по сути, создавать прокси-сервис Netty h2c.Wireshark показывает, что Netty отправляет кадры, и h2c-сервер отвечает (например, с заголовком ответа и данными), хотя у меня возникают некоторые проблемы с получением / обработкой HTTP-фреймов ответа внутри самой Netty.
В качестве отправной точки я адаптировал пример 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] WARN inchannel.DefaultChannelPipeline - было вызвано событие exceptionCaught (), и оно достигнуто прихвост трубопровода.Обычно это означает, что последний обработчик в конвейере не обработал исключение.java.lang.IllegalStateException: Потоковый объект требуется для идентификатора: 1 в io.netty.handler.codec.http2.Http2FrameCodec $ FrameListener.requireStream (Http2FrameCodec.java:587) в io.netty.handler.codec.http2.Lodeer $ra.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:882nel).HeadContext.write (DefaultChannelPipeline.java:1365)
Если я добавлю HTTPКодек с 2 кадрами (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 при получении ответа.
У кого-нибудь есть какие-либо идеи / предложения, пожалуйста?