Java 如何配置Netty 4 HTTP/2客户端以获取单个帧

Java 如何配置Netty 4 HTTP/2客户端以获取单个帧,java,netty,http2,Java,Netty,Http2,我正在编写一个基于Netty的HTTP/2服务,它执行“服务器发送事件”(SSE),我需要编写一个HTTP/2测试客户端类,用于集成测试,但我很难确定如何设置客户端管道,以便从服务器获取单个事件 我第一次尝试使用HTTP/1.1↔ HTTP/2适配器类(InboundHttp2ToHttpAdapter+HttpToHttp2ConnectionHandler),但使用此组合,我仅在流关闭后获得完整的HttpResponse,而不是单个HttpContent对象 接下来,我尝试设置一个管道,在该

我正在编写一个基于Netty的HTTP/2服务,它执行“服务器发送事件”(SSE),我需要编写一个HTTP/2测试客户端类,用于集成测试,但我很难确定如何设置客户端管道,以便从服务器获取单个事件

我第一次尝试使用HTTP/1.1↔ HTTP/2适配器类(
InboundHttp2ToHttpAdapter
+
HttpToHttp2ConnectionHandler
),但使用此组合,我仅在流关闭后获得完整的HttpResponse,而不是单个HttpContent对象

接下来,我尝试设置一个管道,在该管道中发送单个传出的HTTP/2帧并侦听传入的HTTP/2帧,但传出的HTTP/2帧编码器似乎没有正确安装,因为我遇到以下异常:

Exception in thread "main" java.lang.RuntimeException: io.netty.handler.codec.UnsupportedMessageTypeException: io.netty.handler.codec.http2.DefaultHttp2HeadersFrame (expected: io.netty.buffer.ByteBuf)
    at example.AsyncHttpClient.run(AsyncHttpClient.java:116)
    at example.AsyncHttpClient.main(AsyncHttpClient.java:49)
Caused by: io.netty.handler.codec.UnsupportedMessageTypeException: io.netty.handler.codec.http2.DefaultHttp2HeadersFrame (expected: io.netty.buffer.ByteBuf)
    at io.netty.handler.ssl.SslHandler.write(SslHandler.java:694)
    at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:738)
    at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:730)
    at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:816)
    at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:723)
    at io.netty.handler.codec.http2.Http2ConnectionHandler.write(Http2ConnectionHandler.java:498)
    at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:738)
    at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:730)
    at io.netty.channel.AbstractChannelHandlerContext.access$1900(AbstractChannelHandlerContext.java:38)
    at io.netty.channel.AbstractChannelHandlerContext$AbstractWriteTask.write(AbstractChannelHandlerContext.java:1081)
    at io.netty.channel.AbstractChannelHandlerContext$WriteAndFlushTask.write(AbstractChannelHandlerContext.java:1128)
    at io.netty.channel.AbstractChannelHandlerContext$AbstractWriteTask.run(AbstractChannelHandlerContext.java:1070)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:403)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:463)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
    at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)
    at java.lang.Thread.run(Thread.java:745)
你知道我做错了什么吗?我发现包io.netty.handler.codec.http2对于类的组成方式非常混乱。我曾尝试使用Github上的Netty示例作为指导,但从我所能找到的情况来看,大多数使用HTTP/1.1 HTTP/2适配器类,这在我的情况下不适用。如果有更好的例子如何使用这些类,请让我知道

完整源代码(另请参阅):

包示例;
导入io.netty.bootstrap.bootstrap;
导入io.netty.buffer.ByteBuf;
导入io.netty.channel.channel;
导入io.netty.channel.ChannelFuture;
导入io.netty.channel.ChannelFutureListener;
导入io.netty.channel.ChannelHandlerContext;
导入io.netty.channel.ChannelInitializer;
导入io.netty.channel.ChannelPipeline;
导入io.netty.channel.nio.NioEventLoopGroup;
导入io.netty.channel.socket.SocketChannel;
导入io.netty.channel.socket.nio.NioSocketChannel;
导入io.netty.handler.codec.http2.defaulthttp2连接;
导入io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
导入io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder;
导入io.netty.handler.codec.http2.DefaultHttp2FrameReader;
导入io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
导入io.netty.handler.codec.http2.DefaultHttp2Headers;
导入io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
导入io.netty.handler.codec.http2.Http2ConnectionHandler;
导入io.netty.handler.codec.http2.Http2ConnectionHandlerBuilder;
导入io.netty.handler.codec.http2.Http2Exception;
导入io.netty.handler.codec.http2.Http2FrameAdapter;
导入io.netty.handler.codec.http2.http2头文件;
导入io.netty.handler.codec.http2.Http2SecurityUtil;
导入io.netty.handler.ssl.ApplicationProtocolConfig;
导入io.netty.handler.ssl.ApplicationProtocolNames;
导入io.netty.handler.ssl.OpenSsl;
导入io.netty.handler.ssl.SslContext;
导入io.netty.handler.ssl.SslContextBuilder;
导入io.netty.handler.ssl.SslHandler;
导入io.netty.handler.ssl.SslProvider;
导入io.netty.handler.ssl.SupportedCipherSuiteFilter;
导入io.netty.handler.ssl.util.UnsecureTrustManagerFactory;
导入javax.net.ssl.SSLException;
导入java.util.Collections;
导入java.util.Map;
导入静态io.netty.channel.ChannelFutureListener.FIRE\u异常\u ON\u失败;
公共类AsyncHttpClient{
专用最终通道;
公共静态void main(字符串[]args)引发InterruptedException{
final AsyncHttpClient=new AsyncHttpClient(“google.com”,443);
run(“GET”,“/”,Collections.emptyMap());
睡眠(10000);
}
AsyncHttpClient(字符串主机,int端口)引发InterruptedException{
最终引导引导=新引导()
.group(新的NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(新的通道初始值设定项(){
@凌驾
受保护的void initChannel(SocketChannel ch)引发异常{
最终Http2FrameAdapter=新的Http2FrameAdapter(){
@凌驾
public void onHeadersRead(ChannelHandlerContext ctx、int streamId、Http2Headers、int padding、boolean endStream)引发Http2Exception{
System.out.println(“onHeadersRead(ctx,streamId,headers,padding,endStream)”;
super.onHeadersRead(ctx、streamId、header、padding、endStream);
}
@凌驾
public void onHeadersRead(ChannelHandlerContext ctx、int streamId、Http2Headers、int streamDependency、短权重、布尔排他、int padding、布尔endStream)引发Http2Exception{
System.out.println(“onHeadersRead(ctx、streamId、headers、streamDependency、weight、exclusive、padding、endStream)”;
super.onHeadersRead(ctx、streamId、headers、streamDependency、weight、exclusive、padding、endStream);
}
@凌驾
public int-onDataRead(ChannelHandlerContext ctx、int-streamId、ByteBuf数据、int-padding、boolean endOfStream)抛出Http2Exception{
System.out.println(“onDataRead(ctx、streamId、数据、填充、endOfStream)”;
返回super.onDataRead(ctx、streamId、data、padding、endOfStream);
}
};
最终DefaultHttp2Connection连接=新的DefaultHttp2Connection(false);
final DefaultHttp2FrameReader frameReader=新的DefaultHttp2FrameReader();
final DefaultHttp2FrameWriter frameWriter=新的DefaultHttp2FrameWriter();
最终DefaultHttp2ConnectionEncoder编码器=新的DefaultHttp2ConnectionEncoder(连接,frameWriter);
最终DefaultHttp2ConnectionDecoder解码器=新的DefaultHttp2ConnectionDecoder(连接,en
package example;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http2.DefaultHttp2Connection;
import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder;
import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
import io.netty.handler.codec.http2.DefaultHttp2Headers;
import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
import io.netty.handler.codec.http2.Http2ConnectionHandler;
import io.netty.handler.codec.http2.Http2ConnectionHandlerBuilder;
import io.netty.handler.codec.http2.Http2Exception;
import io.netty.handler.codec.http2.Http2FrameAdapter;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.OpenSsl;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;

import javax.net.ssl.SSLException;
import java.util.Collections;
import java.util.Map;

import static io.netty.channel.ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE;

public class AsyncHttpClient {

    private final Channel channel;

    public static void main(String[] args) throws InterruptedException {
        final AsyncHttpClient client = new AsyncHttpClient("google.com", 443);
        client.run("GET", "/", Collections.emptyMap());
        Thread.sleep(10000);
    }

    AsyncHttpClient(String host, int port) throws InterruptedException {
        final Bootstrap bootstrap = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        final Http2FrameAdapter adapter = new Http2FrameAdapter() {

                            @Override
                            public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endStream) throws Http2Exception {
                                System.out.println("onHeadersRead(ctx, streamId, headers, padding, endStream)");
                                super.onHeadersRead(ctx, streamId, headers, padding, endStream);
                            }

                            @Override
                            public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) throws Http2Exception {
                                System.out.println("onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream)");
                                super.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream);
                            }

                            @Override
                            public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception {
                                System.out.println("onDataRead(ctx, streamId, data, padding, endOfStream)");
                                return super.onDataRead(ctx, streamId, data, padding, endOfStream);
                            }
                        };

                        final DefaultHttp2Connection connection = new DefaultHttp2Connection(false);
                        final DefaultHttp2FrameReader frameReader = new DefaultHttp2FrameReader();
                        final DefaultHttp2FrameWriter frameWriter = new DefaultHttp2FrameWriter();
                        final DefaultHttp2ConnectionEncoder encoder = new DefaultHttp2ConnectionEncoder(connection, frameWriter);
                        final DefaultHttp2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, frameReader);
                        final Http2ConnectionHandler connectionHandler = new Http2ConnectionHandlerBuilder()
                                .codec(decoder, encoder)
                                .frameListener(adapter)
                                .build();

                        final ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(createSslHandler(ch));
                        pipeline.addLast(connectionHandler);
                    }
                });

        final ChannelFuture channelFuture = bootstrap.connect(host, port);
        this.channel = channelFuture.sync().channel();
    }

    void run(String method, String path, Map<String, String> headerMap) {
        final DefaultHttp2Headers headers = new DefaultHttp2Headers(true);
        headers.scheme("https");
        headers.method(method);
        headers.path(path);
        for (Map.Entry<String, String> header : headerMap.entrySet()) {
            headers.add(header.getKey(), header.getValue());
        }

        final DefaultHttp2HeadersFrame frame = new DefaultHttp2HeadersFrame(headers, false);
        try {
            channel.writeAndFlush(frame)
                    .addListener(FIRE_EXCEPTION_ON_FAILURE)
                    .sync();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private SslHandler createSslHandler(SocketChannel channel) {
        try {
            final SslProvider provider = OpenSsl.isAlpnSupported() ? SslProvider.OPENSSL : SslProvider.JDK;
            final SslContext sslCtx = SslContextBuilder.forClient().sslProvider(provider)
            /* NOTE: the cipher filter may not include all ciphers required by the HTTP/2 specification.
            * Please refer to the HTTP/2 specification for cipher requirements. */
                    .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
                    .trustManager(InsecureTrustManagerFactory.INSTANCE)
                    .applicationProtocolConfig(new ApplicationProtocolConfig(
                            ApplicationProtocolConfig.Protocol.ALPN,
                            // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers.
                            ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
                            // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers.
                            ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
                            ApplicationProtocolNames.HTTP_2))
                    .build();
            return sslCtx.newHandler(channel.alloc());
        } catch (SSLException e) {
            throw new RuntimeException(e);
        }
    }
}