与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 | 设置是否启用推送 |
示例代码如下:
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的服务器