Golang gin-gonic/gin 的启动与 net/http 标准库的 HTTP/1.1, HTTP/2 协议
本文将介绍 gin 框架的启动流程与 net/http 标准库处理 HTTP/1.1, HTTP/2 的路径关系.
一. HTTP/1.1 协议与 gin 框架的启动
对于 HTTP/1.1 协议, gin 框架的启动流程入口如下:
go
func main() {
r := gin.New()
// 或 r := gin.Default(), Default() 方法本质是 gin.New() 的封装
r.Run(":8080")
}
在 gin.New() 方法中, gin 框架会创建一个 gin.Engine 对象, 并通过 Engine.Run(...) 方法启动 HTTP 服务. 以下是 Engine.Run(...) 方法的实现:
go
// github.com/gin-gonic/gin/gin.go
// "注意: 除非发生错误, 否则此方法将无限期阻塞调用的 goroutine."
func (engine *Engine) Run(addr ...string) (err error) {
// ...
engine.updateRouteTrees()
address := resolveAddress(addr)
// ...
server := &http.Server{ // #nosec G112
Addr: address,
Handler: engine.Handler(),
}
_ = server.ListenAndServe()
}
能够发现, gin 初始化了其路由树, 并在启动入口处构造了 http.Server 对象, 将 gin.Engine.Handler() 注入 Server.Handler, 也就是将 gin 框架的路由处理器注册到 HTTP 服务中.
二. net 标准库
在阅读 net/http 标准库之前, 需要先了解 net 标准库. net/http 的实现基于 net 标准库. 它的核心接口是 Listener 网络监听器.
go
// net/net.go
// "Conn 是一个通用的面向流的网络连接.
// 多个 goroutine 可能同时调用 Conn 上的方法."
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
Close() error
LocalAddr() Addr
RemoteAddr() Addr
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
// "Listener 是一个用于面向流协议的通用网络监听器,
// 多个 goroutine 可能同时调用 Listener 上的方法."
type Listener interface {
Accept() (Conn, error) // Accept 等待并返回监听器的下一个连接。
Close() error
Addr() Addr
}
Listener 接口定义了三个方法: Accept, Close, Addr. 其中 Accept 方法用于等待并返回 Conn, 即连接对象, Close 方法用于关闭监听器, Addr 方法用于获取监听器的地址信息.
已知 HTTP 是基于 TCP 连接的协议, 使用 net.Listen(...) 获取 TCP Listener 对象.
go
// net/dial.go
// ListenConfig 包含了监听地址的配置选项。
type ListenConfig struct {
Control func(network, address string, c syscall.RawConn) error
KeepAlive time.Duration
KeepAliveConfig KeepAliveConfig
mptcpStatus mptcpStatusListen
}
// ListenConfig.Listen 会解析给定的网络和地址,并创建并返回对应网络监听器。
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
addrs, _ := DefaultResolver.resolveAddrList(...)
// ...
la := addrs.first(isIPv4)
switch la := la.(type) {
case *TCPAddr:
if sl.MultipathTCP() {
l, err = sl.listenMPTCP(ctx, la)
} else {
l, err = sl.listenTCP(ctx, la)
}
case *UnixAddr: // ...
}
//...
}
func Listen(network, address string) (Listener, error) {
var lc ListenConfig
return lc.Listen(context.Background(), network, address)
}
例如使用 net.Listen("tcp", ":8080") 可以创建一个 TCPListener 对象, 监听本地的 "8080" 端口的连接请求.
三. net/http 标准库
1. Server 结构体
net/http 是基于 net 标准库构建的 HTTP 标准库.
在 net/http 标准库中, 最重要的结构体是 Server.
go
// net/http/server.go
// Handler 是 HTTP 请求的处理器接口。它会处理并响应 HTTP 请求。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// Server 是 HTTP 服务器的实现。
type Server struct {
Addr string
Handler Handler
TLSConfig *tls.Config
// ...
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
// ...
Protocols *Protocols
}
在
net/http标准库中,Server结构体是 HTTP 服务器面向对象的实现方式, 你也可以通过http.ListenAndServe(...)函数式的方式传入必要参数, 启动 HTTP 服务. 不过它仍然会构造Server结构体, 并调用对应方法启动 HTTP 服务.
2. Server 的最短启动入口
在 net/http 的实现中, Server 的 TLS 启动流程是基本启动流程的二次封装, 因此先介绍 TLS 启动流程, 再介绍基本启动流程.
Server 提供了两个便捷启动入口: ListenAndServe 和 ListenAndServeTLS.
go
// net/http/server.go
func (s *Server) ListenAndServe() error {
// ...
ln, _ := net.Listen("tcp", addr)
return s.Serve(ln)
}
func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
// ...
ln, _ := net.Listen("tcp", addr)
return s.ServeTLS(ln, certFile, keyFile)
}
接下来深入 ServeTLS(...) 和 Serve(...) 方法的实现, 了解 Server 的启动流程.
3. TLS 启动 (Server.ServeTLS(...)方法的实现)
带 TLS 安全层的 Server 启动, 其 TLS Listener 本质是 TCPListener + TLS Config 对二次封装 (或理解为嵌套Listener). 同理 *Server.ServeTLS(...) 也是对 *Server.Serve(...) 的二次封装.
下面是 tls.Listener 与 tls.NewListener 的定义:
go
// crypto/tls/tls.go
// "listener 实现了 net.Listener 接口, 用于 TLS 连接."
type listener struct {
net.Listener
config *Config
}
// ...
// "NewListener 创建一个 Listener,
// 它从 inner Listener 接受连接, 并使用 [Server] 包装每个连接.
// ...
func NewListener(inner net.Listener, config *Config) net.Listener {
l := new(listener)
l.Listener = inner
l.config = config
return l
}
下面是启动 TLS 服务器的入口:
go
// net/http/server.go
// ServeTLS ...
func (s *Server) ServeTLS(l net.Listener, certFile, keyFile string) error {
_ = s.setupHTTP2_ServeTLS()
// config: *tls.Config
config := cloneTLSConfig(s.TLSConfig)
config.NextProtos = adjustNextProtos(config.NextProtos, s.protocols())
// ...
tlsListener := tls.NewListener(l, config)
return s.Serve(tlsListener)
}
func (s *Server) protocols() Protocols {
//...
// "历史中, 禁用 HTTP/2 的方式是将 TLSNextProto 设置为非 nil 的 map,
// 但没有 "h2" 条目."
_, hasH2 := s.TLSNextProto["h2"]
http2Disabled := s.TLSNextProto != nil && !hasH2
// ...
var p Protocols
p.SetHTTP1(true) // default always includes HTTP/1
if !http2Disabled { p.SetHTTP2(true) }
return p
}
注意到 *Server.ServeTLS(...) 方法中, 通过 tls.NewListener(...) 方法将 ServeTLS(...) 方法的 Listener 对象二次封装为 TLS Listener 对象, 并传入 Serve(...) 方法中.
同时, adjustNextProtos(...) 方法, 接受 protocols() 返回的协议封装, 进行 config.NextProtos 的命名适配.
继续深入 *Server 的 Serve(...) 方法实现, 了解启动流程.
4. 基本启动 (Server.Serve(...) 方法的实现)
go
// net/http/server.go
// "HTTP/2 仅当 Listener 设置为 Accept() 返回 [*tls.Conn],
// 同时 TLS Listener Config.NextProtos 被设置了 "h2" 时启用."
func (s *Server) Serve(l net.Listener) error {
// ...
if err := s.setupHTTP2_Serve(); err != nil { return err }
// ...
for {
rw, err := l.Accept()
// ...
c := s.newConn(rw)
c.setState(...)
go c.serve(...)
}
}
可以发现服务器启动后, 在循环中监听到来的连接请求, 并通过 *Server.newConn(rw) 方法返回 *Conn 对象, 然后通过 go *Conn.serve(...) 方法处理请求.
注释中提到, HTTP/2 协议的支持需要接受 TLS Listener, 并且需要设置 Config.NextProtos 为 "h2", 这与上述 protocols() 与 adjustNextProtos(...) 方法的功能介绍已有体现.
*Server.ServeTLS(...) 方法内部, 调用 *Server.Serve(net.Listener) 之前进行了三步主要操作:
- TLS 配置:
setupHTTP2_ServeTLS()在*Server.Serve(...),tls.NewListener(...)前初始化*Server.TLSConfig. adjustNextProtos(config.NextProtos, *Server.protocols()):adjustNextProtos根据 protos 中的协议, 添加或删除tls.Config.NextProtos列表中的 "http/1.1" 和 "h2" 条目.tls.NewListener(l, config)*Server.Serve(tlsListener)
5. HTTP/2 协议的启动条件
观察 setupHTTP2_ServeTLS() 与 setupHTTP2_Serve() 方法中都做了什么, 并了解 HTTP/2 协议的启动条件.
go
// net/http/server.go
// "setupHTTP2_Serve 从 (*Server).Serve 调用,
// 并条件地使用比 setupHTTP2_ServeTLS 更保守的策略配置 s 上的 HTTP/2,
// 因为 Serve 在 tls.Listen 之后调用, 并且可能会被并发调用.
// 参见 shouldConfigureHTTP2ForServe."
func (s *Server) setupHTTP2_Serve() error {
s.nextProtoOnce.Do(s.onceSetNextProtoDefaults_Serve)
return s.nextProtoErr
}
func (s *Server) onceSetNextProtoDefaults_Serve() {
if s.shouldConfigureHTTP2ForServe() {
s.onceSetNextProtoDefaults()
}
}
func (s *Server) setupHTTP2_ServeTLS() error {
s.nextProtoOnce.Do(s.onceSetNextProtoDefaults)
return s.nextProtoErr
}
setupHTTP2_ServeTLS() 方法中:
- 直接调用
onceSetNextProtoDefaults()方法.
setupHTTP2_Serve() 方法中:
- 调用
onceSetNextProtoDefaults_Serve()方法, - 通过
shouldConfigureHTTP2ForServe()判断后 - 间接调用
onceSetNextProtoDefaults()方法.
以下是相关代码的定义:
go
// net/http/server.go
// "shouldConfigureHTTP2ForServe 报告 Server.Serve 是否应该自动配置 HTTP/2.
// (它设置了 s.TLSNextProto map)"
func (s *Server) shouldConfigureHTTP2ForServe() bool {
// 兼容 Go 1.6 : ...
if s.TLSConfig == nil { return true }
if s.protocols().UnencryptedHTTP2() { return true }
// "用户在 http.Server 上指定了 TLSConfig.
// 在这种情况下, 只有当他们的 tls.Config 明确提到 "h2" 时才配置 HTTP/2.
// 否则 http2.ConfigureServer 会修改 tls.Config 来添加它,
// 但他们可能已经将此 tls.Config 传递给 tls.NewListener.
// 如果他们确实这样做了, 那么现在修复它也为时已晚. 它可能只是潜在的竞争条件.
// 参见 Issue 15908."
return slices.Contains(s.TLSConfig.NextProtos, http2NextProtoTLS)
}
// 如果用户没有另行配置(通过设置 *Server.TLSNextProto 非 nil)则配置 HTTP/2.
func (s *Server) onceSetNextProtoDefaults() {
if omitBundledHTTP2 { return }
p := s.protocols()
if !p.HTTP2() && !p.UnencryptedHTTP2() { return }
if http2server.Value() == "0" {
http2server.IncNonDefault()
return
}
if _, ok := s.TLSNextProto["h2"]; ok {
// "TLSNextProto 已经包含了 HTTP/2 的实现.
// 用户可能调用了 golang.org/x/net/http2.ConfigureServer 来添加它."
return
}
conf := &http2Server{}
s.nextProtoErr = http2ConfigureServer(s, conf)
}
方法主要判断两件事, 以决定是否配置 HTTP/2 协议:
Protocols.HTTP2()和Protocols.UnencryptedHTTP2()判断是否指定 HTTP/2 协议. (本质也是提取*Server.TLSNextProto)*Server.TLSNextProto["h2"]判断 TLS 协议配置包含 h2, 前提是用户未自定义*Server.TLSNextProto.
其中 TLSNextProto 在 Server 结构体部分定义如下:
go
// net/http/server.go
type Server struct {
// "TLSConfig 选择性提供一个 TLS 配置, 用于 ServeTLS 和 ListenAndServeTLS.
// 注意, 该值会被 ServeTLS 和 ListenAndServeTLS 克隆, 因此无法使用 tls.Config.SetSessionTicketKeys 等方法修改配置.
// 要使用 SetSessionTicketKeys, 请改用 Server.Serve 与 TLS Listener."
TLSConfig *tls.Config
// "TLSNextProto 可选地指定一个函数, 当 ALPN 协议升级发生时, 接管提供的 TLS 连接.
// map 的 key 是协商的协议名称. Handler 参数应该用于处理 HTTP 请求, 并在未设置时初始化 Request 的 TLS 和 RemoteAddr. 当函数返回时, 连接会自动关闭.
// 如果 TLSNextProto 不为 nil, 则不会自动启用 HTTP/2 支持.
// 历史上, TLSNextProto 用于禁用 HTTP/2 支持. 现在 Server.Protocols 字段提供了更简单的方法来实现这一点."
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
// ...
}
进入到 http2ConfigureServer(...) 方法, 主要作用是, 初始化 *Server.TLSNextProto map, 并按照特定的键名(也就是 "h2" 和 "unencrypted_http2")将 HTTP/2 协议处理函数注册到 *Server.TLSNextProto 中.
go
// net/http/h2_bundle.go
const http2NextProtoTLS = "h2"
const http2nextProtoUnencryptedHTTP2 = "unencrypted_http2"
func http2ConfigureServer(s *Server, conf *http2Server) error {
// ...
if !http2strSliceContains(s.TLSConfig.NextProtos, http2NextProtoTLS) {
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, http2NextProtoTLS)
}
if !http2strSliceContains(s.TLSConfig.NextProtos, "http/1.1") {
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "http/1.1")
}
if s.TLSNextProto == nil {
s.TLSNextProto = map[string]func(*Server, *tls.Conn, Handler){}
}
protoHandler := func(hs *Server, c net.Conn, h Handler, sawClientPreface bool) {
// ...
conf.ServeConn(c, &http2ServeConnOpts{
Context: ctx,
Handler: h,
BaseConfig: hs,
SawClientPreface: sawClientPreface,
})
}
s.TLSNextProto[http2NextProtoTLS] = func(hs *Server, c *tls.Conn, h Handler) {
protoHandler(hs, c, h, false)
}
// "unencrypted_http2" TLSNextProto key 用于传递非 TLS 的 HTTP/2 连接.
// 传入此方法的连接已经读取了 HTTP/2 的 preface(prior knowledge).
s.TLSNextProto[http2nextProtoUnencryptedHTTP2] = func(
hs *Server, c *tls.Conn, h Handler,
) {
nc, err := http2unencryptedNetConnFromTLSConn(c)
// ...
protoHandler(hs, nc, h, true)
}
return nil
}
可以看出此处, http2ConfigureServer(...) 方法将 HTTP/2 协议的处理函数按照特定的键名("h2", "unencrypted_http2")注册到 s.TLSNextProto["h2"] 中, 以便在 TLS Listener 接收到 HTTP/2 连接时, 调用对应的处理函数.
综上, 以 gin 框架为例, 使用 RunTLS(...) 方法启动 HTTPS 服务器时, 配置并启动 HTTP/2 协议服务器的流程与条件如下:
-
在
Engine.RunTLS(...)方法中, 构造http.Server对象, 并将gin.Engine.Handler()注入Server.Handler, 也就是将 gin 框架的路由处理器注册到 HTTP 服务中. -
调用
Server.ListenAndServeTLS(...)方法, 该方法内部会调用net.Listen("tcp", addr)创建一个 TCP Listener, 并调用Server.ServeTLS(...)方法启动 HTTPS 服务. -
在
Server.ServeTLS(...)方法中, 通过tls.NewListener(...)方法将ServeTLS(...)方法的Listener对象二次封装为 TLS Listener 对象, 并传入Serve(...)方法中. -
在
Server.ServeTLS(...)方法中, 调用setupHTTP2_ServeTLS()方法, 该方法会调用onceSetNextProtoDefaults()方法, 该方法会调用http2ConfigureServer(...)方法, 将 HTTP/2 协议的处理函数注册到s.TLSNextProto["h2"]中.
因为我们暂时不知道他会在哪里被调用, 所以我们先搁置 protoHandler(...) 注册函数的作用, 但它可以通过 *Server.TLSNextProto["h2"] 获取到, 也就是在 TLS Listener 接收到 HTTP/2 连接时, 调用对应的处理函数.
因此默认情况下(未通过
GODEBUG=http2server=0, 或设置不含h2的TLSNextProto/Protocols显式禁用 HTTP/2), HTTP/2 协议的支持不需要开发者额外配置, 在 gin 框架下只需要传入Engine.RunTLS(...)方法的证书和私钥即可启动 HTTP/2 协议的 HTTPS 服务.
6. Accept() 方法
接下来应该进入 Serve(...) 方法的核心循环.
go
// net/http/server.go
// "HTTP/2 当且仅当 Listener 设置为 Accept() 返回 [*tls.Conn],
// 同时 TLS Listener Config.NextProtos 被设置了 "h2" 时启用."
func (s *Server) Serve(l net.Listener) error {
// ...
if err := s.setupHTTP2_Serve(); err != nil { return err }
// ...
for {
rw, err := l.Accept()
// ...
c := s.newConn(rw)
c.setState(...)
go c.serve(...)
}
}
简单来说 Listener.Accept() 方法用于等待并返回监听器的下一个连接. Accept() 方法在有新的连接到来前是阻塞的, 会返回一个 Conn 对象.
7. serve() 方法的处理流程
Serve() 与 ServeTLS(...), 最后都会调用 Serve() 方法, 通过 Listener.Accept() 方法获取连接请求, 创建 Conn 对象后, 通过 go Conn.serve(...) 方法处理请求.
以下是 Conn.serve(...) 方法
go
// net/http/server.go
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
// ...
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
// ...
if tlsConn, ok := c.rwc.(*tls.Conn); ok {
_ := tlsConn.HandshakeContext(ctx)
// ...
// NegotiateProtocol, 通常为 "h2"
if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
if fn := c.server.TLSNextProto[proto]; fn != nil {
h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
// ...
c.setState(c.rwc, StateActive, skipHooks)
fn(c.server, tlsConn, h)
}
return
}
}
// HTTP/1.x from here on.
// ...
ctx, cancelCtx := context.WithCancel(ctx)
protos := c.server.protocols()
if c.tlsState == nil && protos.UnencryptedHTTP2() {
if c.maybeServeUnencryptedHTTP2(ctx) { return }
}
if !protos.HTTP1() { return }
for {
w, _ := c.readRequest(ctx)
// ...
serverHandler{c.server}.ServeHTTP(w, w.req)
// ...
}
}
在 Conn.serve(...) 方法中, 首先判断是否是 TLS 连接, 如果是 TLS 连接, 则进行 TLS 握手, 也就是 tlsConn.HandshakeContext(), 随后进入 ALPN 协议协商:
-
如果协商的协议是 HTTP/2, 则使用
c.server.TLSNextProto[proto]获取处理函数进行处理. -
如果判断是非加密的 HTTP/2 协议, 如果支持则调用
c.maybeServeUnencryptedHTTP2(ctx)方法处理请求.
值得注意的是, 和 ALPN 协议协商类似, c.maybeServeUnencryptedHTTP2(ctx) 方法内, 也会使用 c.server.TLSNextProto[nextProtoUnencryptedHTTP2] 获取处理函数.
根据上面的解析, 在 http2ConfigureServer(...) 方法中, 已经将 HTTP/2 协议的处理函数注册到 s.TLSNextProto["h2"] 和 s.TLSNextProto["unencrypted_http2"] 中.
即在 [5. HTTP/2 协议的启动条件](#5. HTTP/2 协议的启动条件 "#5-http2-%E5%8D%8F%E8%AE%AE%E7%9A%84%E5%90%AF%E5%8A%A8%E6%9D%A1%E4%BB%B6") 末尾被搁置的 protoHandler(...) 函数, 我们看看被注册的函数到底做了什么:
go
func (s *Server) onceSetNextProtoDefaults() {
// ...
conf := &http2Server{}
s.nextProtoErr = http2ConfigureServer(s, conf)
}
go
// net/http/h2_bundle.go
func http2ConfigureServer(s *Server, conf *http2Server) error {
// ...
protoHandler := func(hs *Server, c net.Conn, h Handler, sawClientPreface bool) {
if http2testHookOnConn != nil {
http2testHookOnConn()
}
var ctx context.Context
type baseContexter interface {
BaseContext() context.Context
}
if bc, ok := h.(baseContexter); ok {
ctx = bc.BaseContext()
}
conf.ServeConn(c, &http2ServeConnOpts{
Context: ctx,
Handler: h,
BaseConfig: hs,
SawClientPreface: sawClientPreface,
})
}
// ...
}
func (s *http2Server) ServeConn(c net.Conn, opts *http2ServeConnOpts) {
if opts == nil { opts = &http2ServeConnOpts{} }
s.serveConn(c, opts, nil)
}
func (s *http2Server) serveConn(
c net.Conn,
opts *http2ServeConnOpts,
newf func(*http2serverConn),
) {
// ...
sc := &http2serverConn{ ... }
sc.serve(conf)
}
func (sc *http2serverConn) serve(conf http2http2Config) {
// ...
settings := http2writeSettings{ ... }
if !http2disableExtendedConnectProtocol {
settings = append(settings, http2Setting{http2SettingEnableConnectProtocol, 1})
}
sc.writeFrame(http2FrameWriteRequest{ write: settings })
// "任意的连接都从 initialWindowSize 流入令牌开始.
// 如果配置了更高的值, 我们会添加更多的令牌."
if diff := conf.MaxUploadBufferPerConnection - http2initialWindowSize; diff > 0 {
sc.sendWindowUpdate(nil, int(diff)) // 更新 HTTP/2 流量控制窗口
}
// "readPreface 读取来自对等方的 ClientPreface 问候,
// 或者在超时的情况下返回 errPrefaceTimeout,
// 或者在问候无效时返回错误。"
if err := sc.readPreface(); err != nil {
sc.logf("http2: failed to read client preface from %v: %v", sc.conn.RemoteAddr(), err)
return
}
// ...
// "readFrames 是读取传入帧的循环. 它一次只读取一个帧, 直到消费者处理完该帧前阻塞."
// 它在自己的 goroutine 中运行."
go sc.readFrames() // closed by defer sc.conn.Close above
// ...
for {
select {
case wr := <-sc.wantWriteFrameCh: sc.writeFrame(...)
case res := <-sc.wroteFrameCh: sc.wroteFrame(...)
case res := <-sc.readFrameCh: res.readMore()
case m := <-sc.bodyReadCh: sc.noteBodyRead(...)
case msg := <-sc.serveMsgCh:
switch v := msg.(type) {
case *http2serverMessage: // ...
case *http2startPushRequest: sc.startPush(...)
case func(*http2serverConn): v(sc)
default: panic( ... )
}
}
// ...
}
}
protoHandler(...) 方法, 主要是调用 http2Server.ServeConn(...) 方法, 该方法内部会创建 http2serverConn 对象, 并调用 http2serverConn.serve(...) 方法处理请求.
那么在 http2serverConn.serve(...) 内部主要做做三件事:
- 协商: SETTINGS
- 验证身份: preface
- 拉起辅助协程: 帧读写
好吧, 在文章里纯看代码又多又复杂, 通过下面的流程图来理解 protoHandler 层层包装的 Conn.serve(...) 方法大致的处理流程.

总之 protoHandler(...) 方法, 包装了 HTTP/2 协议连接的处理流程 *http2serverConn.serve(...), 进入 HTTP/2 协议的处理流程后, 主要做三件事: 协商、验证身份、拉起辅助协程, 执行 HTTP/2 的主要处理逻辑.
以下是 HTTP/1.x 协议的处理流程.
go
// net/http/server.go
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
// ...
ctx, cancelCtx := context.WithCancel(ctx)
protos := c.server.protocols()
if c.tlsState == nil && protos.UnencryptedHTTP2() {
if c.maybeServeUnencryptedHTTP2(ctx) { return }
}
if !protos.HTTP1() { return }
for {
w, _ := c.readRequest(ctx)
// ...
serverHandler{c.server}.ServeHTTP(w, w.req)
// ...
}
}
服务器在处理 HTTP/1.x 的循环中, 通过 c.readRequest(ctx) 方法读取请求, 并传递至 serverHandler{c.server}.ServeHTTP(w, w.req) 方法处理.
其中 serverHandler 结构体及其 ServeHTTP 方法的定义如下:
go
// net/http/server.go
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if (!sh.srv.DisableGeneralOptionsHandler) &&
(req.RequestURI == "*") &&
(req.Method == "OPTIONS") {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
可知, serverHandler.ServeHTTP(...) 方法会调用 Server.Handler.ServeHTTP(...) 方法处理请求, 如果 Server.Handler 为 nil, 则使用默认的 DefaultServeMux 处理请求.
因此回头来看 gin 框架的启动流程, 在 Engine.Run(...) 方法中的:
go
func (engine *Engine) Handler() http.Handler {
if !engine.UseH2C {
return engine
}
h2s := &http2.Server{}
return h2c.NewHandler(engine, h2s)
}
func (engine *Engine) Run(addr ...string) (err error) {
// ...
engine.updateRouteTrees()
address := resolveAddress(addr)
// ...
server := &http.Server{
Addr: address,
Handler: engine.Handler(),
}
err = server.ListenAndServe()
return
}
gin 框架在启动入口处构造了 http.Server 对象, 并将 gin.Engine 的 Handler() 作为 Server.Handler 传入, 也就是将 gin 框架的路由处理器注册到 HTTP 服务中.
以
gin-gonic/gin@v1.12.0版本为例, gin 框架的启动入口如Run(...),RunTLS(...),RunUnix(...), 甚至RunQuic,RunListener(...)方法均使用engine.Handler()方法作为Server.Handler传入, 也就是将 gin 框架的路由处理器注册到 HTTP 服务中.
总之, gin 框架的启动入口方法都会构造 http.Server 对象, 并注入 gin 框架的路由处理器到 HTTP 服务器对象中, 最后通过 Server.Serve(Listener) 方法启动 HTTP 服务, 监听并处理请求.