HTTP2: netty http2 server demo

netty http2 server

http2的编解码类和Http2MultiplexHandler

与netty 的http1.1类似,http2也需要相应的编解码器,另外还需要一个处理http2连接通道复用的Handler。如下:

ChannelHandler Desc
io.netty.handler.codec.http2.Http2FrameCodec 负责http2帧和消息的编解码
io.netty.handler.codec.http2.Http2MultiplexHandler 负责流的连接通道复用,将Stream转换为Http2MultiplexHandlerStreamChannel

在构建Http2FrameCodec时,可以通过Http2Settings类对流和数据帧进行设置,覆盖默认其默认值。相应方法如下:

Method Key Desc
headerTableSize(long) HEADER_TABLE_SIZE 设置表头大小
maxConcurrentStreams(long) MAX_CONCURRENT_STREAMS 设置流并数数
initialWindowSize(int) INITIAL_WINDOW_SIZE 设置初始窗口大小
maxFrameSize(int) MAX_FRAME_SIZE 设置数据帧大小上限
maxHeaderListSize(long) MAX_HEADER_LIST_SIZE 设置header数据的大小上限(多个header数据帧的大小相加)
pushEnabled(boolean) ENABLE_PUSH 设置是否启用推送

demo

示例代码如下:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http2.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.ssl.*;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;

import javax.net.ssl.SSLException;
import java.net.InetSocketAddress;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadFactory;

@Slf4j
public class HttpServer {

    static final String NETTY_EPOLL_ENABLE_KEY = "netty.epoll.enable";

    static final String OS_NAME_KEY = "os.name";

    static final String OS_LINUX_PREFIX = "linux";

    static final String EVENT_LOOP_BOSS_POOL_NAME = "NettyServerBoss";

    static final String EVENT_LOOP_WORKER_POOL_NAME = "NettyServerWorker";


    private static final int DEFAULT_SETTING_HEADER_LIST_SIZE = 4096;
    private static final int MIB_8 = 1 << 23;
    private static final int DEFAULT_MAX_FRAME_SIZE = MIB_8;
    private static final int DEFAULT_WINDOW_INIT_SIZE = MIB_8;
    private static final int KIB_32 = 1 << 15;
    private static final int DEFAULT_MAX_HEADER_LIST_SIZE = KIB_32;

    public static final Http2FrameLogger SERVER_LOGGER = new Http2FrameLogger(LogLevel.DEBUG, "H2_SERVER");


    int DEFAULT_IO_THREADS = Math.min(Runtime.getRuntime().availableProcessors() + 1, 32);

    private ServerBootstrap bootstrap;
    /**
     * the boss channel that receive connections and dispatch these to worker channel.
     */
    private Channel channel;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;

    private List<HttpServerConfigurator> httpServerConfigurators = new ArrayList<>(Arrays.asList(new Http2ServerConfigurator(), new Http1ServerConfigurator()));
    private boolean enableSsl;
    private SslContext sslCtx;


    public static void main(String[] args) throws InterruptedException, CertificateException, SSLException {
        new HttpServer2(true).init();
    }


    public HttpServer2(boolean enableSsl) {
        this.enableSsl = enableSsl;
    }


    public void init() throws InterruptedException, CertificateException, SSLException {
        // 初始化ssl
        initSsl();

        //初始化ServerBootstrap
        initServerBootstrap();

        try {
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) {
                    // 判断是否启用ssl
                    if (sslCtx != null) {
                        ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()));
                    }

                    //构建http2消息帧编解码器
                    final Http2FrameCodec codec = Http2FrameCodecBuilder.forServer()
                            .gracefulShutdownTimeoutMillis(10000) //超时时间
                            .initialSettings(
                                    new Http2Settings()
                                            //设置表头大小
                                            .headerTableSize(DEFAULT_SETTING_HEADER_LIST_SIZE)
                                            //设置流并数数
                                            .maxConcurrentStreams(Integer.MAX_VALUE)
                                            //设置初始窗口大小
                                            .initialWindowSize(DEFAULT_WINDOW_INIT_SIZE)
                                            //设置数据帧大小上限
                                            .maxFrameSize(DEFAULT_MAX_FRAME_SIZE)
                                            //设置header数据的大小上限(多个header数据帧的大小相加)
                                            .maxHeaderListSize(DEFAULT_MAX_HEADER_LIST_SIZE * 2))
                            .frameLogger(SERVER_LOGGER)
                            .build();
                    final Http2MultiplexHandler handler = new Http2MultiplexHandler(
                            new ChannelInitializer<Channel>() {
                                @Override
                                protected void initChannel(Channel ch) throws Exception {
                                    final ChannelPipeline p = ch.pipeline();
                                    p.addLast(new CustHttp2Handler());
                                }
                            });

                    ch.pipeline()
                            .addLast(codec)
                            .addLast(handler);
                }
            });
            // bind

            String bindIp = "localhost";
            int bindPort = 8080;
            InetSocketAddress bindAddress = new InetSocketAddress(bindIp, bindPort);
            ChannelFuture channelFuture = bootstrap.bind(bindAddress).sync();
            if (channelFuture.isDone()) {
                log.info("http server start at port " + bindPort);
            }
            channel = channelFuture.channel();
            channel.closeFuture().sync();
            log.info("http server shutdown");
        } catch (Exception e) {
            log.error("http server start exception,", e);
        } finally {
            log.info("http server shutdown bossEventLoopGroup&workerEventLoopGroup gracefully");
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private void initServerBootstrap() {
        bootstrap = new ServerBootstrap();

        bossGroup = eventLoopGroup(1, EVENT_LOOP_BOSS_POOL_NAME);
        workerGroup = eventLoopGroup(DEFAULT_IO_THREADS, EVENT_LOOP_WORKER_POOL_NAME);

        bootstrap.group(bossGroup, workerGroup)
                .channel(shouldEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
                .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
    }


    private void initSsl() throws CertificateException, SSLException {
        if (!enableSsl) {
            return;
        }
        SelfSignedCertificate ssc = new SelfSignedCertificate();
        sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
                .sslProvider(SslProvider.JDK)
                .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
                .applicationProtocolConfig(
                        new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN, ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
                                ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolNames.HTTP_2, ApplicationProtocolNames.HTTP_1_1))
                .build();
    }


    public static EventLoopGroup eventLoopGroup(int threads, String threadFactoryName) {
        ThreadFactory threadFactory = new DefaultThreadFactory(threadFactoryName, true);
        return shouldEpoll() ? new EpollEventLoopGroup(threads, threadFactory) :
                new NioEventLoopGroup(threads, threadFactory);
    }


    private static boolean shouldEpoll() {
        if (Boolean.parseBoolean(System.getProperty(NETTY_EPOLL_ENABLE_KEY, "false"))) {
            String osName = System.getProperty(OS_NAME_KEY);
            return osName.toLowerCase().contains(OS_LINUX_PREFIX) && Epoll.isAvailable();
        }

        return false;
    }
}

http2请求处理器:

class CustHttp2Handler extends ChannelDuplexHandler {

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            if (msg instanceof Http2HeadersFrame) {
                Http2HeadersFrame msgHeader = (Http2HeadersFrame) msg;
                if (msgHeader.isEndStream()) {
                    System.out.println("-hhhhh");
                    writeData(ctx, msgHeader.stream());
                } else {
                    System.out.println("hhhhh");
                }
            } else if (msg instanceof Http2DataFrame) {
                Http2DataFrame msgData = (Http2DataFrame) msg;
                if (msgData.isEndStream()) {
                    System.out.println("-ddddd");
                    writeData(ctx, msgData.stream());
                } else {
                    System.out.println("ddddd");
                }
            } else {
                super.channelRead(ctx, msg);
            }
        }
        
        private static void writeData(ChannelHandlerContext ctx, Http2FrameStream stream) {
            ByteBuf content = ctx.alloc().buffer();
            content.writeBytes(RESPONSE_BYTES.duplicate());
            Http2Headers headers = new DefaultHttp2Headers().status(HttpResponseStatus.OK.codeAsText())
                    .add("t1", "tttt")
                    .add("t2", "tttt");
            ctx.write(
                    new DefaultHttp2HeadersFrame(headers)
                            .stream(stream)
            );
            ctx.write(
                    new DefaultHttp2DataFrame(content, true)
                            .stream(stream)
            );
        }
    }

运行上面的代码,8080端口将支持http2协议。运行下面的curl脚本,即可访问。

curl -k -v --http2 https://127.0.0.1:8080

注意:上面代码启用了ssl,需要https协议访问。

使用okhttp进行调用的代码如下:

import okhttp3.ConnectionPool;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;


public class OkHttpClient {

    public static String get(String url) throws IOException {
        List<Protocol> protocols = new ArrayList<>();
//        protocols.add(Protocol.HTTP_2);
        protocols.add(Protocol.H2_PRIOR_KNOWLEDGE);
        okhttp3.OkHttpClient.Builder builder = new okhttp3.OkHttpClient.Builder()
                .readTimeout(Duration.ofSeconds(10))
                .connectTimeout(Duration.ofSeconds(10))
                .writeTimeout(Duration.ofSeconds(10))
                .connectionPool(new ConnectionPool())
                .protocols(protocols);
        okhttp3.OkHttpClient httpClient = builder.build();
        Request request = new Request.Builder()
                .get()
                .url(url)
                .build();
        Response response = httpClient.newCall(request).execute();
        return response.body().string();
    }


    public static void main(String[] args) throws IOException {
        String body = OkHttpClient.get("http://localhost:8080/test/get?q=fd");
        System.out.println("http2 response:" + body);
    }
}

参考

HTTP/2 in Netty
netty系列之:搭建客户端使用http1.1的方式连接http2服务器
apn服务器源码,使用Netty实现HTTP2服务器/客户端的源码和教程 - Baeldung
netty系列之:使用netty实现支持http2的服务器

你可能感兴趣的:(http2,netty)