连接过程
HTTP/2连接建立过程可以分为两大步:
- 协议协商(Http1.x升级到Http2.0);
- 连接的初始化;
协议协商
协议流程
Netty实现
Client发送升级请求
pipeline中添加handler:
private void configureClearText(SocketChannel ch) {
HttpClientCodec sourceCodec = new HttpClientCodec();
Http2ClientUpgradeCodec upgradeCodec = new Http2ClientUpgradeCodec(connectionHandler);
HttpClientUpgradeHandler upgradeHandler = new HttpClientUpgradeHandler(sourceCodec, upgradeCodec, 65536);
ch.pipeline().addLast(sourceCodec,
upgradeHandler,
new UpgradeRequestHandler(),
new UserEventLogger());
}
使用Http1.x发送协议升级请求:
private final class UpgradeRequestHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
DefaultFullHttpRequest upgradeRequest =
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
ctx.writeAndFlush(upgradeRequest);
ctx.fireChannelActive();
// Done with this handler, remove it from the pipeline.
ctx.pipeline().remove(this);
configureEndOfPipeline(ctx.pipeline());
}
}
HttpClientUpgradeHandler.write 添加升级相关的请求头:
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception {
if (!(msg instanceof HttpRequest)) {
ctx.write(msg, promise);
return;
}
if (upgradeRequested) {
promise.setFailure(new IllegalStateException(
"Attempting to write HTTP request with upgrade in progress"));
return;
}
upgradeRequested = true;
setUpgradeRequestHeaders(ctx, (HttpRequest) msg);
// Continue writing the request.
ctx.write(msg, promise);
// Notify that the upgrade request was issued.
ctx.fireUserEventTriggered(UpgradeEvent.UPGRADE_ISSUED);
// Now we wait for the next HTTP response to see if we switch protocols.
}
Server处理升级请求
返回101响应,添加Http2ConnectionHandler,实现逻辑在HttpServerUpgradeHandler.decode -> upgrade链路中。
添加handler,触发创建PrefaceDecoder,进而触发Server端发送SETTING帧,如下:
Http2连接初始化
初始化流程
- 对于Client端,收到101响应后,必须先发送Connection Preface,并且紧跟着一个SETTING帧;
- 对于Server端,发送101响应后,必须先发送一个SETTING帧;
Netty实现
Client处理Server升级响应
HttpClientUpgradeHandler.decode:升级失败,继续使用Http1.x相关handler;升级成功,添加Http2.0相关handler,删除Http1.x相关handler。
sourceCodec.prepareUpgradeFrom(ctx);
upgradeCodec.upgradeTo(ctx, response);
// Notify that the upgrade to the new protocol completed successfully.
ctx.fireUserEventTriggered(UpgradeEvent.UPGRADE_SUCCESSFUL);
// We guarantee UPGRADE_SUCCESSFUL event will be arrived at the next handler
// before http2 setting frame and http response.
sourceCodec.upgradeFrom(ctx);
public void upgradeTo(ChannelHandlerContext ctx, FullHttpResponse upgradeResponse)
throws Exception {
// Add the handler to the pipeline.
ctx.pipeline().addAfter(ctx.name(), handlerName, upgradeToHandler);
// Reserve local stream 1 for the response.
connectionHandler.onHttpClientUpgrade();
}
如果为101响应,则添加handler,触发Http2ConnectionHandler.handlerAdded,如下:
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// Initialize the encoder, decoder, flow controllers, and internal state.
encoder.lifecycleManager(this);
decoder.lifecycleManager(this);
encoder.flowController().channelHandlerContext(ctx);
decoder.flowController().channelHandlerContext(ctx);
byteDecoder = new PrefaceDecoder(ctx);
}
创建PrefaceDecoder时,发送Connection Preface和SETTINGS帧,如下:
public PrefaceDecoder(ChannelHandlerContext ctx) throws Exception {
clientPrefaceString = clientPrefaceString(encoder.connection());
// This handler was just added to the context. In case it was handled after
// the connection became active, send the connection preface now.
sendPreface(ctx);
}
private void sendPreface(ChannelHandlerContext ctx) throws Exception {
if (prefaceSent || !ctx.channel().isActive()) {
return;
}
prefaceSent = true;
final boolean isClient = !connection().isServer();
if (isClient) {
// Clients must send the preface string as the first bytes on the connection.
ctx.write(connectionPrefaceBuf()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
// Both client and server must send their initial settings.
encoder.writeSettings(ctx, initialSettings, ctx.newPromise()).addListener(
ChannelFutureListener.CLOSE_ON_FAILURE);
if (isClient) {
// If this handler is extended by the user and we directly fire the userEvent from this context then
// the user will not see the event. We should fire the event starting with this handler so this class
// (and extending classes) have a chance to process the event.
userEventTriggered(ctx, Http2ConnectionPrefaceAndSettingsFrameWrittenEvent.INSTANCE);
}
}
参考: