前言
近期接到一个任务,把netty http2引入到项目里面。听说过http2,还真没有自己玩过。对看过这篇博客的大家说句: 抱歉。本来想很好的说说http2的。写着写着,发现要写的东西太多了,有一些内容根本就不好写。但是netty http2.0的主要内容,本章博客已经全面的讲述了,需要读者有使用经历,阅读点源码。
了解下http2.0
时代在发展,使用http协议的人越来越多。http1.1的弊端慢慢都被显现出来。
- 浏览器方式一些网站频繁发送请求,造成一家独大其他网站无法使用。或者所有网站都频发发送请求造成用户体验差等等问题。限制每个url同时并发数量
- 提高请求的响应速度。只有一个连接,只有一次tcp三次握手或者tls的7次握手。一个http1.1请求所用的时间,http2.0可以处理三到四个请求。
- 提高服务端与客服端的性能(尤其是大型互联网公司流量很大,如果使用http2.0,可以减少一半的http服务器)
协商
原因 一
http客服端不知道http服务端是否支持http2.0。反过来 http服务端也不知道http客服端是否支持http2.0。为什么出现这种现象,让所有的http服务端与http客服端直接从http1.1过度到http2.0是不可能的事情。甚至在大点的公司内部直接从http1.1直接过度到http2.0也是一件不现实的事情,那么出现一件麻烦的事情有http1客服端,也有http2客服端。有http2服务端,也有http1服务端。这种两个维度,四种情况的共存现象。
原因二
有人会问,只支持http1.1不好吗? 已经支持http2,.0的client肯定不会放弃http2.0优秀的性能与特性,能使用使用http2.0,就要使用。
解决
那么http2.0的设计者为了解决这种麻烦的东西。推出了解决方案:协商。
https 1.1 与https.20的协商
https1.1与https2.0的协商是基于ALPN机制。ALPNS是基于TLS实现。在建立TLS链接的时候,客服端会 在TLS协议里面加入自己支持的协议,服务端在客服端支持的协议里面选中一个自己最合适的。然后把选中的协议通知给客服端。如果客户端没有发送支持的http协议,服务端会默认使用http1.1
http1.1与http2.0的协商
http没有TLS协议,无法基于TLS传递协议。协议制定者使用了Upgrade机制。客户端发送一个空请求,请求里面包含该Upgrade,Connection,HTTP2-Settings请求头。服务端从Upgrade取出支持的协议然后响应请求,在响应的请求头里面包含Upgrade,Connection。这样协商就成功了。下面是http1.1与http2.0的协商流程
请求头示例:
GET / HTTP/1.1
Host: example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
如果服务端不支持 HTTP/2,它会忽略 Upgrade 字段,直接返回 HTTP/1.1 响应,例如:
HTTP/1.1 200 OK
Content-Length: 243
Content-Type: text/html
如果服务端支持 HTTP/2,那就可以回应 101 状态码及对应头部:
HTTP/1.1 100 Switching Protocols
Connection: Upgrade
Upgrade: h2c
小结
- https 1.1 与https.2.0的协商 与 http1.1与http2.0的协商 是两套设计方案。https 1.1 与https.2.0 TLS帮你做了。http1.1与http2.0的协商需要自己做。
- 现在的趋势,客服端与服务端都需要同时支持http1.1,http2.0,https1.1,https2.0。真是一件很麻烦的事情
netty http2
netty 的http2模块设计的非常好,实在是很绕,就一个http2的包。实在有点乱。按照功能划分的话http2应该有四个模块。
- http2核心模块
- http1.1与http2协商模块
- http2帧处理模块
- http2协议转http1协议模块
http2核心模块
作为核心的模块主要是负责http2协议的解码与编码,帧的解析与处理,http2请求头子模块,streamId的管理,http2链接管理
Http2ConnectionHandler
Http2ConnectionHandler 是 netty核心设计hadler的实现,也是http2模块的出发点。
负责http2模块的组合
protected Http2ConnectionHandler(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder,
Http2Settings initialSettings) {
this.initialSettings = checkNotNull(initialSettings, "initialSettings");
this.decoder = checkNotNull(decoder, "decoder");
this.encoder = checkNotNull(encoder, "encoder");
if (encoder.connection() != decoder.connection()) {
throw new IllegalArgumentException("Encoder and Decoder do not share the same connection object");
}
}
负责http2协议下handler生命周期处理
@Override
public void flush(ChannelHandlerContext ctx) {
try {
// Trigger pending writes in the remote flow controller.
encoder.flowController().writePendingBytes();
ctx.flush();
} catch (Http2Exception e) {
onError(ctx, true, e);
} catch (Throwable cause) {
onError(ctx, true, connectionError(INTERNAL_ERROR, cause, "Error flushing"));
}
}
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);
}
@Override
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
if (byteDecoder != null) {
byteDecoder.handlerRemoved(ctx);
byteDecoder = null;
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
if (byteDecoder == null) {
byteDecoder = new PrefaceDecoder(ctx);//当链接创建的时候创建PrefaceDecoder对象
}
byteDecoder.channelActive(ctx);
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// Call super class first, as this may result in decode being called.
super.channelInactive(ctx);
if (byteDecoder != null) {
byteDecoder.channelInactive(ctx);
byteDecoder = null;
}
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
// Writability is expected to change while we are writing. We cannot allow this event to trigger reentering
// the allocation and write loop. Reentering the event loop will lead to over or illegal allocation.
try {
if (ctx.channel().isWritable()) {
flush(ctx);
}
encoder.flowController().channelWritabilityChanged();
} finally {
super.channelWritabilityChanged(ctx);
}
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
byteDecoder.decode(ctx, in, out);
}
负责http2 Lifecycle Manager(http2生命周期的管理)
public interface Http2LifecycleManager {
void closeStreamLocal(Http2Stream stream, ChannelFuture future);
void closeStreamRemote(Http2Stream stream, ChannelFuture future);
void closeStream(Http2Stream stream, ChannelFuture future);
ChannelFuture resetStream(ChannelHandlerContext ctx, int streamId, long errorCode,
ChannelPromise promise);
ChannelFuture goAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
ByteBuf debugData, ChannelPromise promise);
closeStreamLocal
关闭本地stream。local stream是指客服端发送headers帧与data帧到服务端,客服端会创建一个local stream。同样服务端发送headers帧与data帧给客服端,服务端也会创建一个 local stream
closeStreamRemote
关闭远程stream。 remote stream是值当客服端接受服务端发的headers帧与data帧 ,客服端会创建一个remote stream。同样服务端接受到客服端发送的headers帧与data帧,服务端也会创建一个 remote stream
closeStream
当接受到 resetStream 帧的时候就用调用改方法。发送方发送一个错误的流,想后悔的时候,就发送resetStream帧这个后悔药
resetStream
对resetStream帧进行处理
负责校验协议行为
public void onHttpClientUpgrade() throws Http2Exception {
if (connection().isServer()) {
throw connectionError(PROTOCOL_ERROR, "Client-side HTTP upgrade requested for a server");
}
if (!prefaceSent()) {
// If the preface was not sent yet it most likely means the handler was not added to the pipeline before
// calling this method.
throw connectionError(INTERNAL_ERROR, "HTTP upgrade must occur after preface was sent");
}
if (decoder.prefaceReceived()) {