从源码理解Gin框架原理

Gin是一款高性能的Go语言Web框架,本文以一个小型示例项目为例,从源码解读Gin的服务启动过程、请求与响应过程的技术原理。

从源码理解Gin框架原理

1 概述

Gin Web Framework

Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to httprouter. If you need performance and good productivity, you will love Gin.

Gin是一款高性能的Go语言Web框架。

LearnGin

LearnGin仓库存储本文的示例代码。

本文所使用的软件版本是:

  • Gin的版本是:gin@v1.7.4
  • Go的版本是:1.17。

2 技术原理

2.1 Gin的启动过程

2.1.1 项目的main函数

主函数位于项目根目录下的main.go中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"github.com/LearnGin/handler"
"github.com/LearnGin/middleware"
"github.com/gin-gonic/gin"
)

func main() {
// init gin with default configs
r := gin.Default()

// append custom middle-wares
middleware.RegisterMiddleware(r)
// register custom routers
handler.RegisterHandler(r)

// run the engine
r.Run()
}

主要步骤:

  1. 初始化Gingin.Default()执行Gin的初始化过程,默认的初始化包含两个中间件,
    1. Logger:日志中间件,将Gin的启动与响应日志输出到控制台;
    2. Recovery:恢复中间件,将Gin遇到的无法处理的请求按HTTP 500状态码返回。
  2. 注册中间件:本例的middleware.RegisterMiddleware(r)用于将项目中开发的中间件注册到Gin Engine上;
  3. 注册事件处理:本例的handler.RegisterHandler(r)用于将项目中开发的对应于指定URL的事件处理函数注册到Gin Engine上;
  4. 启动Ginr.Run()负责启动Gin Engine,开始监听请求并提供HTTP服务。

2.1.2 初始化Gin

gin的Default函数
1
2
3
4
5
6
7
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}

Gin的默认初始化主要是创建Engine和注册默认的两款中间件。

2.1.3 注册中间件

1
2
3
4
5
6
7
8
9
10
package middleware

import (
"github.com/LearnGin/middleware/debug"
"github.com/gin-gonic/gin"
)

func RegisterMiddleware(r *gin.Engine) {
r.Use(debug.DebugMiddleWare())
}

gin.Engine的r.Use函数负责将gin.HandleFunc类型函数注册为中间件。此处的debug.DebugMiddleWare()是本例开发的一个简易的自定义中间件,用于在实际的事件处理前,输出详细的请求信息;在实际的事件处理后,输出结果状态码。

Engine.Use函数

Engine.Use函数用于将中间件添加到当前的路由上,位于gin\gin.go中,代码如下:

1
2
3
4
5
6
7
8
9
// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
RouterGroup.Use函数

实际上,还需要进一步调用engine.RouterGroup.Use(middleware...)完成实际的中间件注册工作,该函数位于gin\routergroup.go中,代码如下:

1
2
3
4
5
// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}

该函数也很简短,实际上就是把中间件(本质是一个函数)添加到HandlersChain类型(实质上为数组type HandlersChain []HandlerFunc)的group.Handlers中。换句话说,实际上是以函数数组的形式收集了一个有序的函数序列。

此后会介绍中间件中每次都会出现的c.Next()函数如何基于该数组进行流程控制。

2.1.4 注册事件处理

1
2
3
4
5
6
7
8
9
10
11
12
package handler

import (
"github.com/LearnGin/handler/person"
"github.com/gin-gonic/gin"
)

func RegisterHandler(r *gin.Engine) {
r.Handle("GET", "/ping", PingHandler())
r.Handle("POST", "/person/create", person.CreatePersonHandler())
}

gin.Engine的r.Handle函数用于将事件处理函数注册到指定的HTTP方法+相对路径上。

RouterGroup.Handle函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Handle registers a new request handle and middleware with the given path and method.
// The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes.
// See the example code in GitHub.
//
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
// functions can be used.
//
// This function is intended for bulk loading and to allow the usage of less
// frequently used, non-standardized or custom methods (e.g. for internal
// communication with a proxy).
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil {
panic("http method " + httpMethod + " is not valid")
}
return group.handle(httpMethod, relativePath, handlers)
}

Gin Engine的Handle函数调用实际上调用的是内部匿名属性RouterGroup的Handle函数。该函数的逻辑由handle函数进一步处理,代码为:

1
2
3
4
5
6
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}

可以看到,实际上handler是由group.engine.addRoute(httpMethod, absolutePath, handlers)注册路由的。

Engine.addRoute函数

如果持续追查下去,会发现addRoute函数实际上是将该方法添加到当前HTTP方法对应的那颗路由树中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
assert1(path[0] == '/', "path must begin with '/'")
assert1(method != "", "HTTP method can not be empty")
assert1(len(handlers) > 0, "there must be at least one handler")

debugPrintRoute(method, path, handlers)

root := engine.trees.get(method)
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)

// Update maxParams
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
}

每个HTTP方法(如:GET,POST)的路由信息都各自由一个树结构来维护,该树结构的模型与函数实现位于gin/tree.go中,此处不再继续展开。

2.1.5 启动Gin

Engine.Run函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()

trustedCIDRs, err := engine.prepareTrustedCIDRs()
if err != nil {
return err
}
engine.trustedCIDRs = trustedCIDRs
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}

可以看到,Engine.Run函数主要是:

  1. 解析监听地址传参;
  2. 启动监听与服务。

其中,最核心的监听与服务实质上是调用Go语言内置库net/http的http.ListenAndServe函数实现的。

net/http的ListenAndServe函数

Gin框架网络编程的底层实际上是基于Go语言的内置net/http网络库实现的。

1
2
3
4
5
6
7
8
9
10
11
// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}

该函数实例化Sever,并调用其ListenAndServe函数实现监听与服务功能。

注意:此时,输入的Gin Engine对象以Handler接口的对象的形式被传入给了net/http库的Server对象,作为后续Serve对象处理网络请求时调用的函数。

net/http的Handler接口

net/http的Server结构体类型中有一个Handler接口类型的Handler。

1
2
3
4
5
6
7
8
9
10
11
12
13
// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
// Addr optionally specifies the TCP address for the server to listen on,
// in the form "host:port". If empty, ":http" (port 80) is used.
// The service names are defined in RFC 6335 and assigned by IANA.
// See net.Dial for details of the address format.
Addr string

Handler Handler // handler to invoke, http.DefaultServeMux if nil

// ...
}

而该Handler接口的唯一特点就是有且仅有一个ServeHTTP函数声明,该接口定义代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// A Handler responds to an HTTP request.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter or read from the
// Request.Body after or concurrently with the completion of the
// ServeHTTP call.
//
// Depending on the HTTP client software, HTTP protocol version, and
// any intermediaries between the client and the Go server, it may not
// be possible to read from the Request.Body after writing to the
// ResponseWriter. Cautious handlers should read the Request.Body
// first, and then reply.
//
// Except for reading the body, handlers should not modify the
// provided Request.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

Handler接口的意义就在于,任何类型,只需要实现了该ServeHTTP函数,就实现了Handler接口,就可以用作Server的Handler,供HTTP处理时调用。

显然,gin.Engine实现了net/http的Handler接口的ServeHTTP函数(gin/gin.go)。具体的实现原理在接下来介绍。

2.2 请求与响应过程

2.2.1 监听与接受请求

net/http的Server.ListenAndServe函数

上文介绍到,gin实际上调用了net/http的ListenAndServe函数实现网络监听与处理,具体由Server.ListenAndServe实现,位于net/http/server.go中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// If srv.Addr is blank, ":http" is used.
//
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}

可以看到,net/http的Server.ListenAndServe函数实际上主要完成两项工作:

  1. 设置监听net.Listen("tcp", addr)负责设置监听地址;
  2. 接受并处理网络请求srv.Serve(ln)负责在监听位置上接受网络请求,建立连接并做出响应。
net/http的Server.Serve函数

Server.Serve函数用于监听、接受和处理网络请求,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
//
// HTTP/2 support is only enabled if the Listener returns *tls.Conn
// connections and they were configured with "h2" in the TLS
// Config.NextProtos.
//
// Serve always returns a non-nil error and closes l.
// After Shutdown or Close, the returned error is ErrServerClosed.
func (srv *Server) Serve(l net.Listener) error {
if fn := testHookServerServe; fn != nil {
fn(srv, l) // call hook with unwrapped listener
}

origListener := l
l = &onceCloseListener{Listener: l}
defer l.Close()

if err := srv.setupHTTP2_Serve(); err != nil {
return err
}

if !srv.trackListener(&l, true) {
return ErrServerClosed
}
defer srv.trackListener(&l, false)

baseCtx := context.Background()
if srv.BaseContext != nil {
baseCtx = srv.BaseContext(origListener)
if baseCtx == nil {
panic("BaseContext returned a nil context")
}
}

var tempDelay time.Duration // how long to sleep on accept failure

ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
}

Server.Serve函数的实现中,启动了一个无条件的for循环以便持续监听、接受和处理网络请求,主要流程为:

  1. 接受请求l.Accept()调用在无请求时保持阻塞,直到接收到请求时,接受请求并返回建立的连接;
  2. 处理请求:启动一个goroutine,使用conn的serve函数进行处理(go c.serve(connCtx));
net/http的conn.serve函数

已接受的请求会建立连接,对连接的后续处理由conn.serve函数实现,该函数实现较长,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
defer func() {
if err := recover(); err != nil && err != ErrAbortHandler {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
}
if !c.hijacked() {
c.close()
c.setState(c.rwc, StateClosed, runHooks)
}
}()

if tlsConn, ok := c.rwc.(*tls.Conn); ok {
if d := c.server.ReadTimeout; d > 0 {
c.rwc.SetReadDeadline(time.Now().Add(d))
}
if d := c.server.WriteTimeout; d > 0 {
c.rwc.SetWriteDeadline(time.Now().Add(d))
}
if err := tlsConn.HandshakeContext(ctx); err != nil {
// If the handshake failed due to the client not speaking
// TLS, assume they're speaking plaintext HTTP and write a
// 400 response on the TLS conn's underlying net.Conn.
if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
re.Conn.Close()
return
}
c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
return
}
c.tlsState = new(tls.ConnectionState)
*c.tlsState = tlsConn.ConnectionState()
if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
if fn := c.server.TLSNextProto[proto]; fn != nil {
h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
// Mark freshly created HTTP/2 as active and prevent any server state hooks
// from being run on these connections. This prevents closeIdleConns from
// closing such connections. See issue https://golang.org/issue/39776.
c.setState(c.rwc, StateActive, skipHooks)
fn(c.server, tlsConn, h)
}
return
}
}

// HTTP/1.x from here on.

ctx, cancelCtx := context.WithCancel(ctx)
c.cancelCtx = cancelCtx
defer cancelCtx()

c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

for {
w, err := c.readRequest(ctx)
if c.r.remain != c.server.initialReadLimitSize() {
// If we read any bytes off the wire, we're active.
c.setState(c.rwc, StateActive, runHooks)
}
if err != nil {
const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"

switch {
case err == errTooLarge:
// Their HTTP client may or may not be
// able to read this if we're
// responding to them and hanging up
// while they're still writing their
// request. Undefined behavior.
const publicErr = "431 Request Header Fields Too Large"
fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
c.closeWriteAndWait()
return

case isUnsupportedTEError(err):
// Respond as per RFC 7230 Section 3.3.1 which says,
// A server that receives a request message with a
// transfer coding it does not understand SHOULD
// respond with 501 (Unimplemented).
code := StatusNotImplemented

// We purposefully aren't echoing back the transfer-encoding's value,
// so as to mitigate the risk of cross side scripting by an attacker.
fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)
return

case isCommonNetReadError(err):
return // don't reply

default:
if v, ok := err.(statusError); ok {
fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s: %s%s%d %s: %s", v.code, StatusText(v.code), v.text, errorHeaders, v.code, StatusText(v.code), v.text)
return
}
publicErr := "400 Bad Request"
fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
return
}
}

// Expect 100 Continue support
req := w.req
if req.expectsContinue() {
if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
// Wrap the Body reader with one that replies on the connection
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
w.canWriteContinue.setTrue()
}
} else if req.Header.get("Expect") != "" {
w.sendExpectationFailed()
return
}

c.curReq.Store(w)

if requestBodyRemains(req.Body) {
registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
} else {
w.conn.r.startBackgroundRead()
}

// HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another,
// so we might as well run the handler in this goroutine.
// [*] Not strictly true: HTTP pipelining. We could let them all process
// in parallel even if their responses need to be serialized.
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
if !w.shouldReuseConnection() {
if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
c.closeWriteAndWait()
}
return
}
c.setState(c.rwc, StateIdle, runHooks)
c.curReq.Store((*response)(nil))

if !w.conn.server.doKeepAlives() {
// We're in shutdown mode. We might've replied
// to the user without "Connection: close" and
// they might think they can send another
// request, but such is life with HTTP/1.1.
return
}

if d := c.server.idleTimeout(); d != 0 {
c.rwc.SetReadDeadline(time.Now().Add(d))
if _, err := c.bufr.Peek(4); err != nil {
return
}
}
c.rwc.SetReadDeadline(time.Time{})
}
}

不难发现,conn.serve函数的代码实现较长,其中,对连接的主要处理由serverHandler{c.server}.ServeHTTP(w, w.req)函数调用实现。

这一步调用实质上时首先实例化了一个Server实例,然后调用实例的ServeHTTP函数对连接的请求与响应进行具体的处理。上文讲到,实现了ServeHTTP函数就实现了Handler接口。Gin就是通过实现接口的方式,利用系统的net/http库执行自身的功能。

gin的Engine.ServeHTTP函数

gin在gin.go中实现了ServeHTTP函数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()

engine.handleHTTPRequest(c)

engine.pool.Put(c)
}

主要步骤为:

  1. 建立连接上下文:从缓存池中提取上下文对象,填入当前连接的http.ResponseWriter实例与http.Request实例;
  2. 处理连接:以上下文对象的形式将连接交给函数处理,由engine.handleHTTPRequest(c)封装实现了;
  3. 回收连接上下文:处理完毕后,将上下文对象回收进缓存池中。

值得注意的是,Gin中对每个连接都需要的上下文对象进行缓存化存取,通过缓存池节省连接高并发时上下文对象频繁生灭造成内存频繁分配与释放的代价。

gin的Engine.handleHTTPRequest函数

handleHTTPRequest函数封装了对请求进行处理的具体过程,位于gin/gin.go中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}

if engine.RemoveExtraSlash {
rPath = cleanPath(rPath)
}

// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != "CONNECT" && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
}

if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}

Engine.handleHTTPRequest函数的主要处理位于中间的for循环中,主要为:

  1. 遍历查找engine.trees以找出当前请求的HTTP Method对应的处理树;
  2. 从该处理树中,根据当前请求的路径与参数查询出对应的处理函数value
  3. 将查询出的处理函数链(gin.HandlerChain)写入当前连接上下文的c.handlers中;
  4. 执行c.Next(),调用handlers链上的下一个函数(中间件/业务处理函数),开始形成LIFO的函数调用栈;
  5. 待函数调用栈全部返回后,c.writermem.WriteHeaderNow()根据上下文信息,将HTTP状态码写入响应头。

2.2.2 中间件与handler

请求发来时,被中间件与业务逻辑的handler处理,Gin的中间件与业务逻辑函数实质上都是gin.HandlerFunc函数。

例如,为gin.Engine添加了两款中间件(MiddeWareA与MiddleWareB)并为GET方法的/hello路径注册了一个 Hello函数作为路由处理函数,那么执行过程为:

  1. 上述handleHTTPRequest函数执行到c.Next(),调用MiddleWareA;
  2. MiddleWareA执行到c.Next(),调用MiddleWareB;
  3. MiddleWareB执行到c.Next(),调用Hello;
  4. Hello函数返回,MiddleWareB继续执行至函数返回;
  5. MiddleWareA函数继续执行至函数返回。
gin的Context.Next函数

中间件中屡屡调用的c.Next()函数时gin提供的中间件流程控制函数之一,位于gin/context.go中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/************************************/
/*********** FLOW CONTROL ***********/
/************************************/

// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}

不难理解,Next函数起到的作用是,在当前中间件函数中,调用下一个HandlerFunc。依序调用HandlerChain中的HandlerFunc的过程中,形成了一个函数调用栈,调用时函数依序入栈,至最后一个函数调用返回,此后按LIFO的顺序出栈,自然就形成了上述中间件的LIFO的执行顺序。

2.2.3 请求处理与响应

在本例中,我写了一个简易的创建Person的API,其涉及到模型定义与业务逻辑。

模型定义

模型定义位于/model/person.go中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package model

import "time"

type Person struct {
Name string `json:"name"`
Phone string `json:"phone"`
Age uint64 `json:"age"`
}

type CreatePersonRequest struct {
Person Person `json:"person"`
}

type CreatePersonResponse struct {
Person Person `json:"person"`
Elapse time.Duration `json:"elapse"` // nano seconds
BaseResp BaseResp `json:"baseresp"`
}

其中,BaseResp位于/model/base.go中,代码如下:

1
2
3
4
5
6
7
package model

type BaseResp struct {
Code int64 `json:"code"`
Message string `json:"message"`
}

业务逻辑

业务逻辑函数位于handler/person/create_person.go中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package person

import (
"fmt"
"time"

"github.com/LearnGin/model"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)

func CreatePersonHandler() gin.HandlerFunc {
return func(c *gin.Context) {
// parse request
tic := time.Now()
req := new(model.CreatePersonRequest)
err := c.ShouldBindWith(req, binding.JSON)
if err != nil {
fmt.Errorf("Can not bind with model.Person, err: %+v\n", err)
resp := new(model.CreatePersonResponse)
resp.Elapse = time.Since(tic)
resp.BaseResp = model.BaseResp{
Code: 1,
Message: fmt.Sprintf("create person failed in binding json, err: %s", err.Error()),
}
c.JSON(200, resp)
return
}

// process request
fmt.Printf("Creating Person: %+v\n", req.Person)

// jsonify response
resp := new(model.CreatePersonResponse)
resp.Person = req.Person
resp.Elapse = time.Since(tic)
resp.BaseResp = model.BaseResp{
Code: 0,
Message: "success",
}
c.JSON(200, resp)
}
}

处理上主要分三步:

  1. 解析请求c.ShouldBindWith(req, binding.JSON)负责解析请求中发来的JSON数据,并将解析结果绑定到指定的结构体对象上;
  2. 业务处理:此处只做print显示;
  3. 发送响应:实例化响应结构体,并将其序列化为JSON作为响应。

值得注意的是:此处的序列化与反序列化会参照结构体的类型tag(如有)。

3 总结

结合对Gin框架主干代码以及其调用的部分Go源码的阅读,可以体会到:

  1. Gin框架实质上实现的网络通信层以上的框架搭建,而网络通信功能完全采用Go语言的net/http库实现;
  2. Gin通过实现Go语言提供的接口快捷地接入Go的内置库功能,使得上层应用与底层实现之间互不依赖,充分体现了SOLID中的依赖倒置原则;
  3. Gin在性能上针对HTTP Web框架常见的高并发问题进行了优化,例如:通过上下文对象的缓存池节省连接高并发时内存频繁申请与释放的代价;
  4. Gin在设计上将中间件与业务逻辑都抽象为gin.HandleFunc函数,中间件与业务逻辑的执行过程实际上就是函数序列依序调用形成的函数调用栈的执行过程。