Go小課03:Gin Simple Demo解讀

南華Coder發表於2019-09-22

圖片

一、概述

1、簡介
  • Go官方為了支援Web開發,提供了net/http工具包;但是在實際專案中,團隊還是會選擇更加高效,更便捷的Web框架,如GinEcho,Beego等;
  • 在這些團隊中,很多團隊選擇了Gin這個框架,它在net/http 的基礎上做了優化;對比其他主流框架,它更好的效能更快的路由
2、Gin的優點
  • 快速:基於Radix樹的路由,效能非常強大。

  • 支援中介軟體:內建許多中介軟體,如Logger,Gzip,Authorization等。

  • 崩潰恢復:可以捕捉panic引發的程式崩潰,使Web服務可以一直執行。

  • JSON驗證:可以驗證請求中JSON資料格式。

  • 路由分組:支援路由分組(RouteGroup),可以更方便組織路由。

  • 錯誤管理機制:可以收集程式中的錯誤

  • 多種資料渲染方式:支援HTMLJSONYAMLXML等資料格式的響應。

  • 擴充套件性:非常簡單擴充套件中介軟體。

  • 資料驗證器:支援資料驗證器且可以自定義。

3、Gin Simple Demo
package main

import (
	"github.com/gin-gonic/gin"
    "net/http"
)

func setupRouter() *gin.Engine {

	r := gin.Default()
	// Ping test
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"status" : 0,
			"msg" 	 : "success",
			"data"   :  gin.H {
				"content" : "pong",
			},
		})
	})
	return r
}

func main() {
   r := setupRouter()
   r.Run() 
}
複製程式碼

二、Gin Simple Demo 解讀

從Demo Code可以看出,使用Gin的體驗非常順滑,定義處理Web請求就四步:匯入包定義路由編寫 Handler監聽埠

1. 匯入包
import "github.com/gin-gonic/gin"
複製程式碼
2. 定義路由
  • gin.Engine是路由引擎,一般使用gin.Default()方法建立並返回gin.Engine例項.
r := gin.Default() //r預設使用了Logger和Recovery兩個中介軟體
複製程式碼

說明:可以用gin.New()方法建立並返回一個不包含任何中介軟體的gin.Engine例項

3. 編寫 Handler

通過預設路由,我們可以建立處理HTTP請求的方法,示例中使用GET方法:

// Ping test
r.GET("/ping", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
			"status" : 0,
			"msg" 	 : "success",
			"data"   :  gin.H {
				"content" : "pong",
		},
	})
})
複製程式碼
  • Get方法底層實現是呼叫RouterGrouphandle方法, 請求的處理是使用HandlerFunc型別方法
// 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)
}

// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
複製程式碼
  • 說明:Gin支援所有通用的HTTP請求方法,如GET,POST,PUT,PATCH,OPTIONS,HEAD,DELETE
4. 監聽埠
  • 定義好請求之後,使用Run()方法便可監聽埠,開始接受HTTP請求,如果Run()方法沒有傳入引數的話,則預設監聽8080埠。
r.Run() 
複製程式碼
  • Run方法的底層實現關鍵是:http.ListenAndServe,其中:http來自net/http包。
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
}
複製程式碼

三、Gin中重要資料結構

​ 在Gin Simple Demo Code中,我們發現了三個重要的Go資料結構:gin.Enginegin.Contextgin.RouterGroup

1、gin.Engine
  • gin.Engine 是框架的入口;我們通過 Engine 物件來定義服務路由資訊、組裝外掛、執行服務,整個 Web 服務的都是由它來驅動的。
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
// Create an instance of Engine, by using New() or Default()
type Engine struct {
	RouterGroup
	
	//....
}
複製程式碼
  • gin.Engine本質是對內建的 HTTP 伺服器的包裝,讓它使用起來更加便捷。
  • gin.Default() 函式會生成一個預設的 Engine 物件,裡面包含了 2 個預設的常用外掛,分別是 Logger 和 Recovery,Logger 用於輸出請求日誌,Recovery 確保單個請求發生 panic 時記錄異常堆疊日誌,輸出統一的錯誤響應(推薦使用)。
func Default() *Engine {
   debugPrintWARNINGDefault()
   engine := New()
   engine.Use(Logger(), Recovery())
   return engine
}
複製程式碼
  • gin.New()函式會生成一個預設的 Engine 物件,不包含任何中介軟體的gin.Engine例項。
2、gin.Context
  • 請求的上下文資訊物件,它是所有請求Handler的入口引數。gin.Context的結構定義如下:
// Context允許我們在中介軟體之間傳遞變數,管理流程,驗證請求的JSON,並返回JSON
type Context struct {
	//請求物件
	Request   *http.Request
    
    // 用來響應 
    writermem responseWriter
	Writer    ResponseWriter
 	// URL裡面的引數,比如:/xx/:id  
	Params   Params
	
     // 參與的處理者(中介軟體 + 請求處理者列表)
    handlers HandlersChain
    // 當前處理到的handler的下標
    index    int8

	fullPath string
	
    // Engine單例
	engine *Engine

	// 在context可以設定的值
	Keys map[string]interface{}

	// 一系列的錯誤
	Errors errorMsgs

	//為內容協商定義一組手動接受的格式。
	Accepted []string

	// queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
	queryCache url.Values

	// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
	// or PUT body parameters.
	formCache url.Values
}
複製程式碼
  • Context這個上下文物件不是每次生成,而是從物件池裡面取出來的;而請求的真正處理核心在handleHTTPRequest函式中。
  • handleHTTPRequest函式中的核心邏輯:根據請求方法和請求的URI,找到並呼叫 處理的函式陣列(包括我們定義的處理函式,還有中間層的處理函式)。
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {

	// 從物件池中獲取一個context物件
	c := engine.pool.Get().(*Context)

	// 初始化上下文物件,因為從物件池取出來的資料,有髒資料,故要初始化。
	c.writermem.reset(w)
	c.Request = req
	c.reset()

    //處理web請求 (http請求處理)
	engine.handleHTTPRequest(c)

	//將Context物件扔回物件池了
	engine.pool.Put(c)
}
複製程式碼
3、gin.RouterGroup
  • RouterGroup是路由分組,是對路由樹的包裝,所有的路由規則最終都是由它來進行管理。Engine 結構體繼承了 RouterGroup ,所以 Engine 直接具備了 RouterGroup 所有的路由管理功能。
// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {
	Handlers HandlersChain
	basePath string
	engine   *Engine
	root     bool
}

// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc
複製程式碼
  • RouteGroup 物件中主要包括:basePath(字首路徑)、Engine 指標Handlers(處理函式陣列)。

  • RouterGroup 實現了 IRouter 介面定義的一系列路由方法;這些方法最終都是通過呼叫 Engine.addRoute 方法將請求處理器掛接到路由樹中。

  • RouterGroup 內部有一個字首路徑屬性,它會將所有的子路徑都加上這個字首再放進路由樹中。有了這個字首路徑,就可以實現 URL 分組功能。Engine 物件內嵌的 RouterGroup 物件的字首路徑是 /,它表示根路徑。RouterGroup 支援分組巢狀,使用 Group 方法就可以讓分組下面再掛分組。

    v1 := r.Group("/api/v1")
    {
    	v1.POST("/submit",submit)
    	v1.GET("/list",list)
    }
    // Engine物件中RouterGroup物件是第一層分組(根分組),v1是根分組的子分組。
    複製程式碼
4、gin.H
  • gin.H 是 map[string]interface{} 的一個快捷名稱.
type H map[string]interface{}
複製程式碼

相關文章