gin的http服務(wù)啟動(dòng)
test := gin.Default()
test.Run()
上面簡(jiǎn)簡(jiǎn)單單的兩行代碼,就能開啟一個(gè)基于gin框架的http服務(wù)
下面復(fù)雜的這么多行代碼也是在啟動(dòng)一個(gè)基于gin的http服務(wù)贮尖。
了解過(guò)GOhttp包的應(yīng)該很好理解下面的代碼
//返回一個(gè)gin的engine對(duì)象
func InitRouter() *gin.Engine{...}
//構(gòu)建一個(gè)server對(duì)象液茎,傳入engine作為handler
s := &http.Server{
Addr: fmt.Sprintf(":%d", setting.ServerSetting.HttpPort),
Handler: router,
ReadTimeout: setting.ServerSetting.ReadTimeout,
WriteTimeout: setting.ServerSetting.WriteTimeout,
MaxHeaderBytes: 1 << 20,
}
//將http作為協(xié)程開啟
go func() {
if err := s.ListenAndServe(); err != nil {
log.Printf("Listen: %s\n", err)
}
}()
//監(jiān)聽系統(tǒng)的信號(hào)(crtl+c)
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<- quit
log.Println("Shutdown Server ...")
//利用contex來(lái)關(guān)閉http服務(wù)(防止服務(wù)意外關(guān)閉所以留5s的掃尾時(shí)間)
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
if err := s.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
Gin默認(rèn)的啟動(dòng)方式
我們先來(lái)看看兩行代碼的http服務(wù)是怎么開啟的
func Default() *Engine {
//檢驗(yàn)版本
debugPrintWARNINGDefault()
//生成一個(gè)engine對(duì)象
engine := New()
//默認(rèn)使用Logger票彪,Recovery中間件
engine.Use(Logger(), Recovery())
return engine
}
//參數(shù)就是返回一個(gè)Engine對(duì)象(如果一個(gè)函數(shù)太長(zhǎng)援所,先看參數(shù)和返回值了解大概是干什么的)
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
//路由組
// 給框架實(shí)例綁定上一個(gè)路由組
RouterGroup: RouterGroup{
// engine.Use 注冊(cè)的中間方法到這里
Handlers: nil,
basePath: "/",
// 是否是路由根節(jié)點(diǎn)
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
AppEngine: defaultAppEngine,
UseRawPath: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
// 路由樹
// 我們的路由最終注冊(cè)到了這里
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJsonPrefix: "while(1);",
}
engine.RouterGroup.engine = engine
// 綁定從實(shí)例池獲取上下文的閉包方法
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
還有Run方法
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
// 執(zhí)行http包的ListenAndServe方法 啟動(dòng)路由
// engine實(shí)現(xiàn)了http.Handler接口 所以在這里作為參數(shù)傳參進(jìn)去
// 后面我們?cè)倏磂ngine.ServeHTTP的具體邏
err = http.ListenAndServe(address, engine)
return
}
了解了這個(gè)其實(shí)就很容易理解第二個(gè)是啟動(dòng)gin http服務(wù)的揭朝。
那么engine
為什么能作為handler
傳入server呢廊营,那肯定是實(shí)現(xiàn)了ServeHTTP
方法即實(shí)現(xiàn)了handler接口
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 獲取一個(gè)上下文實(shí)例
// 從實(shí)例池獲取 性能高
c := engine.pool.Get().(*Context)
// 重置獲取到的上下文實(shí)例的http.ResponseWriter
c.writermem.reset(w)
// 重置獲取到的上下文實(shí)例*http.Request
c.Request = req
// 重置獲取到的上下文實(shí)例的其他屬性
c.reset()
//這里重置是應(yīng)為從對(duì)象池中拿出來(lái)的contex可能具有臟數(shù)據(jù)
//(也就是可能是其他請(qǐng)求的contex,有興趣可以試著注釋掉這里的重置)
//看看會(huì)發(fā)生什么
// 實(shí)際處理請(qǐng)求的地方
// 傳遞當(dāng)前的上下文
engine.handleHTTPRequest(c)
//歸還上下文實(shí)例
engine.pool.Put(c)
}
果然 engine實(shí)現(xiàn)了ServeHTTP
方法那么在處理請(qǐng)求時(shí),也是主要調(diào)用了這個(gè)方法萝勤。
接下來(lái)就好好分析engine以及engine的這個(gè)ServeHTTP方法做了什么事
engine
type Engine struct {
//這里的組合代表engine也實(shí)現(xiàn)了RouterGroup(就當(dāng)作繼承吧)
RouterGroup //路由組
....//去掉一些不太重要的結(jié)構(gòu)
pool sync.Pool//contex的對(duì)象池
trees methodTrees//handler樹
}
type methodTrees []methodTree
type methodTree struct {
method string
root *node
}
type node struct {
path string
indices string
children []*node //子節(jié)點(diǎn)
handlers HandlersChain //handler鏈
priority uint32
nType nodeType
maxParams uint8
wildChild bool
}
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
type HandlerFunc func(*Context) //Contex也算是gin的核心了,稍后探索
type HandlersChain []HandlerFunc
這些結(jié)構(gòu)稍后再探索呐伞,先從一個(gè)gin的流程開始
gin注冊(cè)路由
路由其實(shí)分為兩個(gè)部分敌卓,一個(gè)是路由設(shè)置部分,一個(gè)是路由匹配部分伶氢。
路由其實(shí)并不僅僅是url趟径,還包括HTTP的請(qǐng)求方法,而實(shí)現(xiàn)一個(gè)REST風(fēng)格的http請(qǐng)求癣防,需要支持REST支持的方法蜗巧,比如GET,PUT蕾盯,POST幕屹,DELETE,OPTION等级遭。
r.GET("/auth", api.GetAuth)
r.Handle(http方法,path,handler)
而gin中注冊(cè)路由的方式就是調(diào)用對(duì)應(yīng)的http方法望拖,或是使用r.Handle注冊(cè)這里的r是engine
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
//向路由組注冊(cè)路由
return group.handle("GET", relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
//合并中間件得handle和路由的handle
handlers = group.combineHandlers(handlers)
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)
//注意這兩個(gè)copy,是RouterGroup的handler在用戶自定義handler前面挫鸽。
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)
// 檢查有沒有對(duì)應(yīng)method集合的路由
//所以這里是每一個(gè)方法一個(gè)樹
root := engine.trees.get(method)
if root == nil {
// 沒有 創(chuàng)建一個(gè)新的路由節(jié)點(diǎn)
root = new(node)
root.fullPath = "/"
// 添加該method的路由tree到當(dāng)前的路由到路由樹里
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
}
可能這里有點(diǎn)暈说敏,但是只要記住handlers
或者handlerschain
都是func(*Contex)
這個(gè)方法,而這個(gè)Contex
里面有著GO原生http服務(wù)處理所有的的東西就好了丢郊,把它看成加強(qiáng)版的處理業(yè)務(wù)邏輯的對(duì)象
所以上述的這些函數(shù)就把路由與對(duì)應(yīng)的handlers設(shè)置好了盔沫。
那么來(lái)看看路由是如何匹配的
路由匹配
還記得前面engine
實(shí)現(xiàn)的ServeHTTP
方法嗎
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 獲取一個(gè)上下文實(shí)例
// 從實(shí)例池獲取 性能高
c := engine.pool.Get().(*Context)
// 重置獲取到的上下文實(shí)例的http.ResponseWriter
c.writermem.reset(w)
// 重置獲取到的上下文實(shí)例*http.Request
c.Request = req
// 重置獲取到的上下文實(shí)例的其他屬性
c.reset()
//這里重置是應(yīng)為從對(duì)象池中拿出來(lái)的contex可能具有臟數(shù)據(jù)
//(也就是可能是其他請(qǐng)求的contex,有興趣可以試著注釋掉這里的重置)
//看看會(huì)發(fā)生什么
// 實(shí)際處理請(qǐng)求的地方
// 傳遞當(dāng)前的上下文
engine.handleHTTPRequest(c)
//歸還上下文實(shí)例
engine.pool.Put(c)
}
真正處理的就是handleHTTPRequest
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++ {
// 這里尋找當(dāng)前請(qǐng)求method的路由樹節(jié)點(diǎn)
if t[i].method != httpMethod {
continue
}
// 找到節(jié)點(diǎn)
root := t[i].root
// Find route in tree
// 尋找當(dāng)前請(qǐng)求的路由
value := root.getValue(rPath, c.Params, unescape)
if value.handlers != nil {
// 把找到的handles賦值給上下文
c.handlers = value.handlers
// 把找到的入?yún)①x值給上下文
c.Params = value.params
c.fullPath = value.fullPath
// 執(zhí)行handle
c.Next()
// 處理響應(yīng)內(nèi)容
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)
}
這里就通過(guò)getValue找到了對(duì)應(yīng)的路由進(jìn)行處理
gin的中間件
我們看到tree中路由對(duì)應(yīng)的是HandlersChain医咨,實(shí)際就是[]HandlerFunc,所以一個(gè)路由架诞,實(shí)際上會(huì)對(duì)應(yīng)多個(gè)handlers拟淮。
所以是可以一個(gè)路由擁有多個(gè)handler的。這里的handler是怎么來(lái)的呢侈贷?
每個(gè)路由的handler有幾個(gè)來(lái)源惩歉,第一個(gè)來(lái)源是在engine.GET的時(shí)候調(diào)用增加的。第二個(gè)來(lái)源是RouterGroup.GET的時(shí)候增加的(而設(shè)置的方式上面已經(jīng)說(shuō)過(guò)了)俏蛮,而且group的handler高于自定義的handler(告訴我們中間件的回調(diào)要先于用戶定義的路徑處理函數(shù)撑蚌。),這里自定義的handler可以是多個(gè)。
router.GET("/before", MiddleWare(), func(c *gin.Context) {
request := c.MustGet("request").(string)
c.JSON(http.StatusOK, gin.H{
"middile_request": request,
})
})
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("before middleware")
c.Set("request", "clinet_request")
c.Next()
fmt.Println("before middleware")
}
}
第三種方法是使用Use增加中間件的方式:
apiv1.Use(jwt.JWT())
,apiv1 := r.Group("/api/v1")
這里的apiv1是一個(gè)路由組,gin一般使用一個(gè)use來(lái)載入一個(gè)中間件
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
這里會(huì)把這個(gè)中間件(實(shí)際上也是一個(gè)handler)存放到routerRroup上搏屑。所以中間件是屬于groupHandlers的争涌。
上文說(shuō)過(guò)handleHTTPRequest
將對(duì)應(yīng)路由的handler賦給了當(dāng)前請(qǐng)求的上下文,并且調(diào)用next()
函數(shù)執(zhí)行辣恋。
func (c *Context) Next() {
// 上下文處理之后c.index被執(zhí)為-1
c.index++
for c.index < int8(len(c.handlers)) {
// 遍歷執(zhí)行所有handle(其實(shí)就是中間件+路由handle)
c.handlers[c.index](c)
c.index++
}
}
之前也提到過(guò)亮垫,context是從對(duì)象池里取出來(lái)的,所以可能帶有臟數(shù)據(jù)伟骨,因此要重置
func (c *Context) reset() {
c.Writer = &c.writermem
c.Params = c.Params[0:0]
c.handlers = nil
c.index = -1
c.fullPath = ""
c.Keys = nil
c.Errors = c.Errors[0:0]
c.Accepted = nil
c.queryCache = nil
c.formCache = nil
}
這里的Next方法沒有使用局部變量去遍歷計(jì)數(shù)handlers的饮潦,它使用了和Context的成員變量index。這樣就可以保證某些情況下Next()函數(shù)不會(huì)觸發(fā)任何handler的調(diào)用携狭。
這里還有個(gè)細(xì)節(jié)继蜡,既然這里是調(diào)用c.Next()
來(lái)交出當(dāng)前Contex的控制權(quán),并執(zhí)行接下來(lái)的hanlder所以c.Next就可以視作是后面所有的handler都執(zhí)行了逛腿。那么整個(gè)handler執(zhí)行和返回的過(guò)程就可以看作一個(gè)棧稀并,先執(zhí)行的最后返回。
好了說(shuō)到這里单默,碘举,整個(gè)gin的流程應(yīng)該已經(jīng)明了了t
接下來(lái)探索一下前文提過(guò)多次的Context
Context
type Context struct {
writermem responseWriter
Request *http.Request
// 傳遞接口,使用各個(gè)處理函數(shù)搁廓,更加靈活引颈,降低耦合
Writer ResponseWriter
Params Params // 路徑當(dāng)中的參數(shù)
handlers HandlersChain // 處理函數(shù)數(shù)組
index int8 // 目前在運(yùn)行著第幾個(gè)處理函數(shù)
fullPath string
engine *Engine
// Keys is a key/value pair exclusively for the context of each request.
//意味著這里可以在中間件之間傳遞參數(shù)
Keys map[string]interface{}
......
}
可以看到Context里集成Request
和ResponseWriter
,就是說(shuō)整個(gè)業(yè)務(wù)邏輯處理的參數(shù)數(shù)據(jù)都封裝在這個(gè)Context里了
里面實(shí)現(xiàn)了幾乎所有的http里處理的功能函數(shù)(bind(獲取參數(shù))枚抵,render輸出參數(shù))
附上一份gin的源碼(自己學(xué)習(xí)的時(shí)候?qū)懙南哂斜容^詳細(xì)的注釋)