http/2.0 and http/2.0 in Go
1、准备
reference
Go源码:https://github.com/golang/go - tag:1.12.4
http/2.0 tools
Browser Indicators
chrome extension : HTTP/2 and SPDY indicator
curl
cmd:
curl -vso /dev/null --http2 https://www.cloudflare.com/
note:
--http2 option that causes it to use HTTP/2 (if it can).
chrome 只支持了基于ALPN 的 HTTP 2.0 服务。不再支持 NPN 的 HTTP 2.0 服务。
note
TLS 的扩展协议:SNI, NPN, ALPN : [通过扩展ClientHello/ServerHello消息为TLS增加新的功能]
SNI(Server Name Indication)
SNI指定了 TLS 握手时要连接的主机名。 SNI 协议是为了支持同一个 IP(和端口)支持多个域名。
因为在 TLS 握手期间服务器需要发送证书(Certificate)给客户端,为此需要知道客户请求的域名(因为不同域名的证书可能是不一样的)。
这时有同学要问了,要连接的主机名不就是发起 HTTP 时的 Host 么? TLS Handshake 时 HTTP 交互还没开始,自然 HTTP 头部还没到达服务器。
NPN(Next Protocol Negotiation)
NPN 是 Google 在 SPDY 中开发的一个 TLS 扩展。
NPN 是利用 TLS 握手中的 ServerHello 消息,在其中追加 ProtocolNameList 字段包含自己支持的应用层协议,客户端检查该字段,并在之后的 ClientKeyExChange 消息中以 ProtocolName 字段返回选中的协议。
ALPN(Application-Layer Protocol Negotiation)
ALPN 则是客户端先声明自己支持的协议 (ClientHello),服务器选择并确认协议 (ServerHello)。这样颠倒的目的主要是使 ALPN 与其他协议协商标准保持一致 (如 SSL)。
TLS(Transport Layer Security)/SSL
TLS的前身是SSL,TLS 1.0通常被标示为SSL 3.1
2、http/2.0 特性
3、Go: package net and net/http
开始http/2.0之前,我们先回顾下Go的两个网络包:net and net/http
3.1 net
net
note
net.conn : interface
implementation
UnixConn <--- DialUnix | ListenUnixgram
UDPConn <--- DialUDP | ListenMulticastUDP | ListenUDP
TCPConn <--- DialTCP
IPConn <--- DialIP | ListenIP
net.Listener : interface
implementation
UnixListener <--- ListenUnix
TCPListener <--- ListenTCP
net.Addr : interface
implementation
UnixAddr <--- ResolveUnixAddr
UDPAddr <--- ResolveUDPAddr
TCPAddr <--- ResolveTCPAddr
IPAddr <--- ResolveIPAddr
other
处理:DNS NS/MX/SRV 、HOST、CIDR、HardwareAddr 、IP/IPMask/IPNet
3.2 net.http
net.http.Client
dependency
interface: RoundTripper
method
RoundTrip(Request) (Response, error)
note
implementation
net.http.Transport
note
net.http.Server
dependency
net.http.Handler : interface
note
handler to invoke, http.DefaultServeMux if nil
method
ServeHTTP(w ResponseWriter, r *Request)
implementation
net.http.ServeMux
note:
ServeMux is an HTTP request multiplexer.
通过 ServeHTTP 接管请求,然后再根据 pattern 路由到具体的handler
net.http.HandlerFunc
将 func(ResponseWriter, *Request) 函数转换为 net.http.Handler 接口的实现
usage:
生成一些常用的 handler
net.http: func FileServer(root FileSystem) Handler
net.http: func NotFoundHandler() Handler
net.http: func RedirectHandler(url string, code int) Handler
net.http: func StripPrefix(prefix string, h Handler) Handler
net.http: func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler
net.http: func FileServer(root FileSystem) Handler
note
FileServer returns a handler that serves HTTP requests with the contents of the file system rooted at root.
dependency
interface : net.http.FileSystem
implementation
net.http.Dir
interface : net.http.File
net.http.Request
net.http.Response
net.http.ResponseWriter : interface
implemented
net.http.Pusher : interface
note: for HTTP/2 server push
net.http.Flusher : interface
net.http.Hijacker : interface
4、server使用http/2.0
4.1 使用 golang.org.x.net.http2.ConfigureServer
从上述代码可以看到核心在于:
将 NextProtoTLS 附加在 net.http.Server.TLSConfig.NextProtos 尾部
将 NextProtoTLS : protoHandler 插入到 net.http.Server.TLSNextProto 中
由此,我们可以推测,在 net.http.Server 接收到连接请求时,会在某个时机使用这两个对象:在真是 serve 时调用 protoHandler 进行处理。接下来,我们看看 net.http.Server 的工作流程。
在分析 net.http.Server 代码后,发现:服务启动的 ListenAndServe 和 ListenAndServeTLS 函数,最终都会走到 ServeTLS 和 Serve 函数。我们先来看看 ServeTLS 的实现:
这块代码有几个地方很重要:
通过 setupHTTP2_ServeTLS 安装http2支持,从函数名就可以看出,go 提供的库只支持了带 tls 的 http2.0
它会调用到 :
先看是否通过环境变量禁用了http2的支持,再在 srv.TLSNextProto == nil 时通过 http2ConfigureServer 添加 http2.0 的支持。
通过分析 http2ConfigureServer, 发现它和golang.org.x.net.http2.ConfigureServer的处理流程几乎完全一样,也就是说net/http默认就支持了http2.0, 只不过完全采用完全默认的 http2Server 配置。
另外,注意到:只有当 srv.TLSNextProto == nil 时才添加 http2.0 的支持,费解~
调用 cloneTLSConfig 拷贝 TLSConfig 配置
添加一个默认的处理协议 "http/1.1" 到 TLSConfig.NextProtos
调用 Serve 函数,并传递参数:通过 net.Listener 和 TLSConfig 构造的 tls.listener 对象 tlsListener
注意,最终也是调用了 Serve 函数,也就是说 Serve 函数既要处理 http 也要处理 https
接下来我们看看 Serve 的实现:
setupHTTP2_Serve 最终调用了 onceSetNextProtoDefaults_Serve,看看它的实现,what ? setupHTTP2_Serve 什么鬼~ 又调用一次 onceSetNextProtoDefaults,这个其实是为了处理:调用 ListenAndServe 但是配置了 TLSConfig 的情形
将 net.Conn 对象 rw 通过 srv.newConn 封装到一个新的 net.http.Server.conn 对象,并启动一个goroutine调用 conn.serve 处理新接收到的连接请求。
注意,每次 accept 一个连接请求,net.http 都会创建一个 goroutine,问题来了,如果突然间涌入大量连接请求会发生什么?
通过上述代码可以看出,如果是 tls 连接,则先处理 tls握手协议,然后根据 TLSNextProto 调用对应协议的serverHandler; 如果是普通连接,则进入 for 循环读取数据,并调用 ServeHTTP 进行处理。
此时,我们可以确认,http2.0 在连接建立之后,会调用 protoHandler 接管数据的处理。同时,需要注意,readRequest 处理了数据包转换为 http 请求的动作,同样的protoHandler 也会处理 frame 转换为 http 请求的动作,以兼容http1.1的语法。
4.2 使用 golang.org.x.net.http2.h2c
h2c 的主要目的是提供一个 non-TLS 版本的 http/2.0, 这个包只提供了一个函数:NewHandler。使用时,将其返回值当作普通的handler使用即可。
从上述代码可以看到,NewHandler 只是使用 http.Hander 和 http2.Server 封装了一个 h2cHandler。当 net/http 的连接建立后,会调用 h2cHandler 对象的 ServeHTTP 处理函数。
很明显,当满足条件时会调用 http2.Server 的 ServeConn 进行处理,进入 ServeConn 后就是常规的 http2 处理流程,这里就先不深入了。这里有两种升级方式:
r.Method == "PRI"
通过使用 PRI 标识当前发送的是一个前言,紧接着就会发送http/2.0的frame了。
前言数据以:PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n 开始
。go 的 http2.Transport 就是使用这种方式来构建http/2.0连接。
h2cUpgrade
使用 http/1.1 通过头部信息 Connection: Upgrade, HTTP2-Settings 和 Upgrade: h2c 标识需要升级以及通过HTTP2-Settings来设置http/2.0的连接信息。其中 HTTP2-Settings 是以 base64url 编码。
curl -k --http2 http://localhost:8972?data=curltest
就是使用这种方式。
我们来看看 h2cUpgrade 的实现方式:
h2cUpgrade 先通过 isH2CUpgrade 判断是否能够进行协议升级,然后再利用 http.Hijacker 完全接管连接,包括数据读写,并构成新的连接对象以供 http2.Server.ServerConn 进行 http2 协议处理数据成 net.http 的 ResponseWriter, *Request 对象。
5、Next
http2.0 in grpc
对 client 发起请求的核心 net.http.Transport 进行解读
对 websocket 的包 golang.org.x.net.websocket 进行解读
6、End
sample
Last updated