gin,beego等底層都用的是net/http模塊,上篇文章中對(duì)一個(gè)基本的http請(qǐng)求作了分析,這篇文章就gin怎么用的http模塊的流程進(jìn)行梳理。
gin的github地址
來(lái)看一個(gè)基本的gin項(xiàng)目代碼:
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong",})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
可以看到一個(gè)基本的啟動(dòng)過(guò)程:
(1)首先生成一個(gè)gin.Default
(2)設(shè)置路由r.GET...
(3)啟動(dòng)listen and serve
例行先提出問(wèn)題:
(q1)gin是怎么使用到net/http的模塊的?
(q2)gin的路由處理流程?
通過(guò)每個(gè)流程的分析來(lái)解答:
一、gin.Default
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault() // go版本的校驗(yàn)(>1.8.x),debugPrint,debug模式的時(shí)候會(huì)有相關(guān)打印
engine := New() // 生成一個(gè)Engine結(jié)構(gòu)體
engine.Use(Logger(), Recovery()) // 用了Logger和Recovery兩個(gè)middleware,同時(shí)也可以自己定義middleware
return engine // 返回結(jié)構(gòu)體
}
1.1 debugPrintWARNINGDefault:
關(guān)于版本的校驗(yàn)等信息,debug模式下的啟動(dòng)日志打印
func debugPrintWARNINGDefault() {
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { // 版本的校驗(yàn),不能小于1.8
debugPrint(`[WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon.
`)}
debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
`)
}
func getMinVer(v string) (uint64, error) {
first := strings.IndexByte(v, '.')
last := strings.LastIndexByte(v, '.')
fmt.Println(first)
fmt.Println(last)
if first == last {
return strconv.ParseUint(v[first+1:], 10, 64)
}
return strconv.ParseUint(v[first+1:last], 10, 64)
}
1.2 engine := New()
New函數(shù)用于生成一個(gè)新的Engine結(jié)構(gòu)體,可以看到各種參數(shù)配置
首先我們來(lái)關(guān)注Engine結(jié)構(gòu)體的幾個(gè)參數(shù):
(1)RouterGroup:RouterGroup 描述的一個(gè)路由組,其中包含了這個(gè)路由組的相關(guān)公共屬性,比如后面可以通過(guò)(group *RouterGroup) Group方法修改basePath字段,用于加一些公共的路由前綴(eg:"api/v1"),。通過(guò)(group *RouterGroup) Use(middleware ...HandlerFunc)方法改變Handlers字段,用于添加公共的中間件。
貼一段項(xiàng)目代碼:
// InsecureRouterGroup returns router group without auth
func InsecureRouterGroup() *gin.RouterGroup {
lock.Lock()
r := gEngine.Group(constants.APIVERSION)
r.Use(RequestID)
lock.Unlock()
return r
}
(2)各種bool參數(shù),用于對(duì)重定向(redirect),轉(zhuǎn)發(fā)(ForwardedByClientIP)等一些屬性的控制
(3)trees:類型為methodTrees,是每個(gè)不同方法的路由集合,原理參考go的http/router框架。大致就是一個(gè)路由查找樹,用到了radix tree(前綴樹)或者說(shuō)是壓縮檢索樹的數(shù)據(jù)結(jié)構(gòu),來(lái)進(jìn)行路由匹配,可以大大的提高路由查找的效率。
type methodTrees []methodTree
type methodTree struct {
method string
root *node
}
type node struct {
path string // 當(dāng)前節(jié)點(diǎn)的對(duì)應(yīng)的子查找路徑
indices string // 子節(jié)點(diǎn)的首字母的組成的字符串
children []*node // 子節(jié)點(diǎn)
handlers HandlersChain // 這個(gè)node對(duì)應(yīng)要處理的Handlers
priority uint32
nType nodeType // 當(dāng)前節(jié)點(diǎn)類型(eg: root(根節(jié)點(diǎn))/default(普通的)/param(參數(shù)類型)/catchAll(通配符))
maxParams uint8
wildChild bool // 當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)是否是參數(shù)節(jié)點(diǎn)
}
(4)pool:sync.Pool類型,New方法定義為return engine.allocateContext(),返回的結(jié)構(gòu)體是gin.Context結(jié)構(gòu)體,便于對(duì)結(jié)構(gòu)體的復(fù)用,減少gc。
拋出問(wèn)題
(qa6):gin.Context結(jié)構(gòu)體在哪里被用到的?具體的代碼處理邏輯?
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil, // 這個(gè)路由組的公共hanlers,后面會(huì)給其加上middleware
basePath: "/", // 路由地址為"/"
root: true, // 表明這個(gè)為根路由
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
AppEngine: defaultAppEngine,
UseRawPath: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9), // 給methodTrees定義一個(gè)methodTree的數(shù)組
delims: render.Delims{Left: "{{", Right: "}}"},
secureJsonPrefix: "while(1);",
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
拋出問(wèn)題:
(q3)路由查找的時(shí)候RouterGroup和trees都是在哪里被用到了?
1.3 engine.Use(Logger(), Recovery())
這里用了gin的中間件Logger和Recovery
// 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...) // use中間件,把加入的middleware加入到engine.RouterGroup.Handlers中,在后面加入具體的業(yè)務(wù)代碼的時(shí)候會(huì)combine這個(gè)Handlers和具體的業(yè)務(wù)handler,解釋了(qa3)的問(wèn)題。
engine.rebuild404Handlers() // 用于重寫noroute的handler,具體的方法實(shí)現(xiàn)在func (engine *Engine) NoRoute(handlers ...HandlerFunc)中,用于用戶自己定義
engine.rebuild405Handlers() // 同上,用于重寫nomethod的handler
return engine
}
// 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()
}
至此對(duì)于gin.Default的代碼分析完畢
二、r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message":"pong",}) })
先貼源碼:
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath) // 合并相對(duì)路徑和routerGroup的basePath
handlers = group.combineHandlers(handlers) // 把業(yè)務(wù)的handler和共用的handler(middleware)結(jié)合到一起,返回一個(gè)新的HandlersChain
group.engine.addRoute(httpMethod, absolutePath, handlers) // 把path加入到相應(yīng)的method的檢索樹中去,關(guān)聯(lián)相應(yīng)的handlers
return group.returnObj()
}
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
return joinPaths(group.basePath, relativePath)
}
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
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) // 查看engine.trees是否已經(jīng)有了此method,沒有的話創(chuàng)建出一個(gè)新的method的node
if root == nil {
root = new(node)
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers) // 把handlers加入到method的node中
}
總結(jié):r.GET方法工作(1)合并拿到絕對(duì)路徑 (2)將業(yè)務(wù)handler和middle handler合并為mergeHandler(3)將路徑,mergeHandler,method(httprouter的節(jié)點(diǎn)相關(guān)元素)加入到methodTrees中。
總的來(lái)說(shuō)處理過(guò)程符合httprouter那一套邏輯。
三、r.Run()
現(xiàn)在我們已經(jīng)有了一個(gè)類型為Engine的handler(實(shí)現(xiàn)了ServeHTTP的方法),以及一個(gè)Engine里的路由檢索樹已經(jīng)建立好了。
拋出問(wèn)題
(qa4):對(duì)于進(jìn)來(lái)的請(qǐng)求怎么進(jìn)行監(jiān)聽的?
(qa5):對(duì)于路由的匹配是在哪里進(jìn)行的?
// 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) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}
// 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()
}
可以看到這里的Run即為將engine這個(gè)handler放入到http.ListenAndServe中去,然后啟用監(jiān)聽。。。。,監(jiān)聽流程和net/http的處理流程一致。(解答了qa4)
對(duì)于qa5:
首選回顧一下,net/http中的路由查找是怎么執(zhí)行的嗎?看下面的代碼,其實(shí)發(fā)現(xiàn)net/http用的是ServeMux這個(gè)handler,也是實(shí)現(xiàn)了ServeHTTP這個(gè)方法,具體的路由匹配在mux.Handler(r) 中
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r) // handler的路由處理
h.ServeHTTP(w, r) // ServeHTTP的實(shí)現(xiàn),對(duì)應(yīng)到用戶的業(yè)務(wù)邏輯處理
}
而engine即類似于ServeMux,也實(shí)現(xiàn)了一個(gè)ServeHTTP方法,也是在方法里進(jìn)行了路由的匹配以及具體邏輯的處理,詳細(xì)見下面代碼。
// ServeHTTP conforms to the http.Handler interface。
// 這個(gè)方法的實(shí)現(xiàn)是在一個(gè)goroutine中實(shí)現(xiàn)的,就是說(shuō)每對(duì)一個(gè)新的HTTP請(qǐng)求,都會(huì)生成一個(gè)新的goroutine去處理(并發(fā)處理詳見net/http的處理過(guò)程)。
//對(duì)于這個(gè)HTTP請(qǐng)求,會(huì)先觸發(fā)ServeHTTP方法得到一個(gè)Context請(qǐng)求上下文,然后調(diào)用engine.handleHTTPRequest(C),處理這個(gè)請(qǐng)求,同時(shí)將處理的內(nèi)容寫入到http.ResponseWriter中。
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context) // 從池子里拿出來(lái),要是沒有了的話自己new一個(gè)出來(lái)。有并發(fā)請(qǐng)求進(jìn)來(lái)的時(shí)候,會(huì)產(chǎn)生多個(gè)context
c.writermem.reset(w) // 對(duì)context的ResponseWriter賦為w
c.Request = req // 對(duì)context的Request賦為req
c.reset() // 對(duì)拿到的context進(jìn)行reset,類似于清空,然后才進(jìn)行使用
engine.handleHTTPRequest(c) // 這里就是最后的邏輯處理,包括(1)通過(guò)context .Request.path找到對(duì)應(yīng)的handler (2)middleware的執(zhí)行 (3)業(yè)務(wù)代碼的執(zhí)行 (4)寫入結(jié)果到context. ResponseWriter中去
engine.pool.Put(c) // 用完了之后放回到engine.pool中去
}
注釋解釋了qa6。
對(duì)于engine.handleHTTPRequest(c)中的處理邏輯,涉及到了middleware的處理,下面拉出來(lái)分析一下:
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
}
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 { // 遍歷methodTrees,找到當(dāng)前方法的methodTree
continue
}
root := t[i].root // 找到methodTree的root
// Find route in tree
handlers, params, tsr := root.getValue(rPath, c.Params, unescape) // 拿到當(dāng)前tree下的這個(gè)路徑下的handler和參數(shù)等
if handlers != nil {
c.handlers = handlers
c.Params = params
c.Next() // 這里是真正的進(jìn)行handler的執(zhí)行,包括middleware和業(yè)務(wù)handle
c.writermem.WriteHeaderNow() // 寫入header
return
}
if httpMethod != "CONNECT" && rPath != "/" { // 跨域等操作
if 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 handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
// 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) // 會(huì)進(jìn)行遞歸執(zhí)行middleware,直到執(zhí)行到業(yè)務(wù)代碼。一般的middleware會(huì)有個(gè)c.Next方法。像cors等沒有寫c.Next的,其實(shí)在for c.index < int8(len(c.handlers))遍歷中也會(huì)被執(zhí)行到。
c.index++
}
}
可以看到在handleHTTPRequest執(zhí)行過(guò)程中,都是通過(guò)context結(jié)構(gòu)體進(jìn)行上下文傳遞的,傳遞的內(nèi)容包括Request,Writer,handlers,Params等等。
總結(jié):
至此可以看到gin作為一個(gè)web框架,其實(shí)就是重寫了原生的ServeMux的實(shí)現(xiàn)。Engine就是一個(gè)類似于ServeMux的handler。
基本的web實(shí)現(xiàn)的時(shí)候,重新實(shí)現(xiàn)的邏輯主要有:
(1)對(duì)于路由的查找,用了http/router的路由框架,并在ServeHTTP方法中進(jìn)行了路由的查找。
(2)用了中間件的處理邏輯,并在路由添加的時(shí)候進(jìn)行了路由的合并等操作,并且在handleHTTPRequest的時(shí)候也會(huì)對(duì)每個(gè)中間件進(jìn)行處理。
后面有新的發(fā)現(xiàn)進(jìn)行補(bǔ)充。。。