Go Gin原始碼學習(一)
Gin的基本使用
Gin是一個比較輕量級的http框架,主要是提供了幾個便於使用的功能:
- 簡單的中介軟體註冊,可以很方便的實現通用中介軟體的使用註冊
- 提供了比較方便和全面的路由註冊,方便的實現RESTful介面的實現
- 提供了便捷的獲取引數的方法,包括get、post兵可以可以把資料直接轉換成物件
- 對路由的分組,Gin可以對一組路由做統一的中介軟體註冊等操作
- 可以手機所有錯誤,統一在統一的地方寫日誌
效能方面:
- 是路由的基礎資料格式為基數樹沒有使用反射,所以效能方面也是比較低消耗記憶體低
- 上下文context使用了物件池,fasthttp中也同樣使用了sync.pool
使用方便也是比較簡單的,下面有一個很簡單的例子
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"test/gin/middleware/model"
)
func main() {
//建立router
router := gin.Default()
//建立組
group := router.Group("/api")
//為組加中介軟體
group.Use(func(context *gin.Context) {
fmt.Println("api group url:", context.Request.URL.String())
})
//為組加路由方法
group.GET("/test", func(context *gin.Context) {
context.JSON(200, model.Message{Message:"ok"})
})
//執行
router.Run(":3333")
}
例子中是一個最簡單的Gin框架的應用。建立了一個engine,建立了組並且為組新增了中介軟體之後在這個group下的路由方法都將使用這個中介軟體,方便對api最系統的管理對不同的api做不同的處理。 在Terminal中訪問可以看到下面的結果
curl http://localhost:3333/api/test
{"Message":"ok"}panleiMacBook-Pro:test
主要流程的原始碼
首先我們先看例子中的gin.Default() 返回的engine物件,這是Gin的主要物件。只對最主要的屬性加了註釋
type Engine struct {
//路由組
RouterGroup
RedirectTrailingSlash bool
RedirectFixedPath bool
HandleMethodNotAllowed bool
ForwardedByClientIP bool
AppEngine bool
UseRawPath bool
UnescapePathValues bool
MaxMultipartMemory int64
delims render.Delims
secureJsonPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
// 物件池 用來建立上下文context
pool sync.Pool
//記錄路由方法的 比如GET POST 都會是陣列中的一個 每個方法對應一個基數樹的一個root的node
trees methodTrees
}
然後我們來看Default方法,其實很簡單就是建立一個engine物件並且新增預設的兩個中介軟體,一個是做log顯示,顯示每次請求可以再console中看到。每次建立engine物件的時候回預設的新增一個routergroup地址為預設的"/" 程式碼如下:
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
//new方法中預設的 新增了routergroup路由 “/”
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
AppEngine: defaultAppEngine,
UseRawPath: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJsonPrefix: "while(1);",
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
在看中介軟體之前還有一個重要的物件context 上下文物件 這個物件中存放了 engine指標、請求的request物件、返回的responsewriter物件還有一些引數等物件,這個context將在請求一開始就被建立一直貫穿整個執行過程,包括中介軟體,路由等。最後的返回值可以再responseWriter寫,最終就會返回給客戶端。
type Context struct {
writermem responseWriter
Request *http.Request
Writer ResponseWriter
Params Params
handlers HandlersChain
index int8
engine *Engine
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{}
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
}
接下來看得是新增中介軟體use方法
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
//呼叫routegroup的use方法
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
//為group的handlers新增中介軟體
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
新增為中介軟體之後就是新增正常路由,Gin中提供了GET、POST、DELETE更各種方法,我們就只看get方法其餘的都是相同的處理方式。總結下來就是把group和傳入的handler合併,並且計算出路徑存入到tree中等客戶端的呼叫。
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
//呼叫get方法
return group.handle("GET", relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
//計算路徑地址,比如group地址是 router.Group("/api")
//結果為/api/test/ 就是最終計算出來的結果 使用path.join 方法拼接 其中加了一些判斷
absolutePath := group.calculateAbsolutePath(relativePath)
//把group中的handler和傳入的handler合併
handlers = group.combineHandlers(handlers)
//把方法 路徑 和處理方法作為node 加入到基數樹種,基數樹在下次單獨學習分析
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
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
}
最後我們需要看run方法,之前的都是準備工作或者說是設定路由中介軟體。等run方法執行的時候則是服務真正啟動起來。 程式碼很簡單,幾乎不需要註釋是呼叫gohttp包的ListenAndServe方法把engine傳入然後http包中會有一個for邏輯不停的監聽這個埠號的所有請求。
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
}
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
那麼客戶端最終請求之後會走到那些程式碼,又是怎麼找到路由並且呼叫一個個中介軟體的呢?其實engine是繼承了Handler 這個介面(可以看下面程式碼),我們知道如果不適用Gin框架直接使用http包我們所有的路由就是直接繼承這個介面所以對這個介面我們是很熟悉的。 下面的serveHTTP 就是Gin的方法 最主要流程就是從tree中獲取到路由,然後依次執行handler最終處理完成返回給客戶端。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
//從物件池中獲取context物件,這就是最初我們看到的一種優化效能的一種方式
c := engine.pool.Get().(*Context)
//重置writer中的一些值
c.writermem.reset(w)
把request放到context中
c.Request = req
c.reset()
//呼叫處理方法
engine.handleHTTPRequest(c)
//處理完成把context物件放回到物件池
engine.pool.Put(c)
}
func (engine *Engine) handleHTTPRequest(c *Context) {
//獲取請求方法和路徑
httpMethod := c.Request.Method
path := c.Request.URL.Path
unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
path = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}
//根據基數樹的特性尋找方法發 並呼叫next方法 依次執行handler
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
handlers, params, tsr := root.getValue(path, c.Params, unescape)
if handlers != nil {
c.handlers = handlers
c.Params = params
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != "CONNECT" && path != "/" {
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(path, nil, unescape); handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
總結
從上面的流程我們可以看到,其實Gin框架就是對go http包的一次封裝,加入了group和tree。讓我們可以更簡單方便的使用http包。其實Gin還有很多其他功能的原始碼包括引數分析 json解析等等,這次學習的只是Gin的主要流程。Gin中tree是一個亮點,是的它在查詢路由效能方面有很大的優勢,下一篇文章會主要學習tree(基數樹)。
相關文章
- go原始碼學習Go原始碼
- go學習筆記——gin框架Go筆記框架
- Go學習【02】:理解Gin,搭一個web demoGoWeb
- Go學習筆記-Gin常用功能Go筆記
- Go語言Context包原始碼學習GoContext原始碼
- gin學習
- Vue 原始碼學習(一)Vue原始碼
- 學習RadonDB原始碼(一)原始碼
- go標準庫-log包原始碼學習Go原始碼
- 【菜鳥讀原始碼】halo✍原始碼學習 (一)原始碼
- Go Web輕量級框架Gin學習系列:路由分組GoWeb框架路由
- Vuex原始碼學習(一)功能梳理Vue原始碼
- jQuery原始碼學習筆記一jQuery原始碼筆記
- Moment.js學習(一)原始碼JS原始碼
- Go Web輕量級框架Gin學習系列:安裝與使用GoWeb框架
- Go Web輕量級框架Gin學習系列:資料繫結GoWeb框架
- 【go】【gin】【validator】Go
- Gin使用及原始碼簡析原始碼
- 原始碼學習原始碼
- gin原始碼閱讀之一 – net/http的大概流程原始碼HTTP
- React 原始碼學習(一):HTML 元素渲染React原始碼HTML
- 從bootstrap原始碼中學習Sass(一)boot原始碼
- 一個學習 Koa 原始碼的例子原始碼
- Go Web輕量級框架Gin學習系列:HTTP請求日誌GoWeb框架HTTP
- gin 原始碼閱讀(1) - gin 與 net/http 的關係原始碼HTTP
- Kubernetes原始碼學習之一:下載和編譯原始碼原始碼編譯
- fishhook原始碼學習Hook原始碼
- MMKV原始碼學習原始碼
- vue原始碼學習Vue原始碼
- 【原始碼學習】ThreadLocal原始碼thread
- EventBus原始碼學習原始碼
- ObjectMapper原始碼學習ObjectAPP原始碼
- express原始碼學習Express原始碼
- 學習HashMap原始碼HashMap原始碼
- Go Web輕量級框架Gin學習系列:中介軟體使用詳解GoWeb框架
- FFmpeg學習之一(FFmpeg原始碼編譯)原始碼編譯
- go學習--->開始編碼Go
- GoWeb框架Gin學習總結GoWeb框架