gin是一個 Web應用框架,擁有良好的性能和簡單明瞭的接口。同時支持中間件,類型綁定等實用功能。node
在實際開發中,不多會直接實用http.Server。而本身搭建框架有必定成本,同時沒有通過系統的校驗,容易出現問題。而現有的框架中,gin擁有良好的性能,更重要的是接口清晰明瞭,接入成本極低。同時,其支持的功能也是多種多樣,如中間件,類型綁定,日誌規範。git
如下是從官網拿到的性能對比指標表github
Benchmark name | (1) | (2) | (3) | (4) |
---|---|---|---|---|
BenchmarkGin_GithubAll | 30000 | 48375 | 0 | 0 |
BenchmarkAce_GithubAll | 10000 | 134059 | 13792 | 167 |
BenchmarkBear_GithubAll | 5000 | 534445 | 86448 | 943 |
BenchmarkBeego_GithubAll | 3000 | 592444 | 74705 | 812 |
BenchmarkBone_GithubAll | 200 | 6957308 | 698784 | 8453 |
接受/ping路徑的Get請求,並返回message:"pong"bash
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
router.Run() // listen and serve on 0.0.0.0:8080
}
複製代碼
router := gin.Default()
router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)
複製代碼
router := gin.Default()
// The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe
/**** example1: 解析Querystring ****/
router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest")
// shortcut for c.Request.URL.Query().Get("lastname")
lastname := c.Query("lastname")
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
/**** example2: 解析表單 ****/
router.POST("/form_post", func(c *gin.Context) {
message := c.PostForm("message")
nick := c.DefaultPostForm("nick", "anonymous")
c.JSON(200, gin.H{
"status": "posted",
"message": message,
"nick": nick,
})
})
複製代碼
支持以組爲單位的路由,下面栗子就是以/v1開頭,以/v2開頭的兩組配置。路由組能夠共享一樣的配置,好比路由組v1能夠使用中間件a。而v2能夠使用另外一箇中間件,互不影響。數據結構
router := gin.Default()
// Simple group: v1
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
// Simple group: v2
v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
複製代碼
中間件是對框架能力一個很是好的抽象。以組件的形式,爲路由或路由組提供插件式功能。也能夠本身實現中間件,加入到Use中來。app
// Creates a router without any middleware by default
r := gin.New()
// Global middleware
r.Use(gin.Logger())
r.Use(gin.Recovery())
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
複製代碼
中間件能夠很是方便的定義日誌格式框架
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
// your custom format
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
}))
複製代碼
使用 c.ShouldBind方法,能夠將參數自動綁定到 struct.該方法是會檢查 Url 查詢字符串和 POST 的數據,並且會根據 content-type類型,優先匹配JSON或者 XML,以後纔是 Form.ide
// 定義一個 Person 結構體,用來綁定數據
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}
func main() {
route := gin.Default()
route.GET("/testing", startPage)
route.Run(":8085")
}
func startPage(c *gin.Context) {
var person Person
// 綁定到 person
if c.ShouldBind(&person) == nil {
log.Println(person.Name)
log.Println(person.Address)
log.Println(person.Birthday)
}
c.String(200, "Success")
}
複製代碼
gin 能夠說全是在handler上作文章。下面咱們就以這三句話,一探gin。post
func main() {
r := gin.Default()
r.GET("/getb", GetDataB)
r.Run()
}
複製代碼
r := gin.Default() 的定義以下性能
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
複製代碼
Engine是gin中的一個很重要的概念。等下面對.r.Run分析時候,咱們會發現他的本質就是http.Server裏面的handler實例!
這裏看到engine.Use(Logger(), Recovery()) 直觀上就很像以前提早的http中間件的某種實現 Logger()是日誌中間件, Recovery()是針對panic的中間件(否則每一個handler都得寫個panic處理邏輯)
來分析一下Recover中間件
func Recovery() HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter)
}
func RecoveryWithWriter(out io.Writer) HandlerFunc {
var logger *log.Logger
if out != nil {
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
}
return func(c *Context) {
defer func() {
if err := recover(); err != nil {
// 省略非關鍵代碼
}
}()
c.Next()
}
}
複製代碼
c.Next()能將多箇中間件串聯起來調用
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
複製代碼
c.handlersc.index即當前索引位置對應的handler的調用
type HandlersChain []HandlerFunc
type HandlerFunc func(*Context)
複製代碼
因爲處理邏輯是放在了c.Next前面,因此中間件的處理順序是先入後出。中間件自己應該互相獨立。但若是由於特殊緣由,有先後依賴,就要注意這點。
r.GET("/getb", GetDataB) 實現以下
// 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)
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
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)
}
複製代碼
能夠看到,路由映射是加入了一顆樹中。這裏使用的是radix樹,Radix樹,即基數樹,也稱壓縮前綴樹,是一種提供key-value存儲查找的數據結構。與Trie不一樣的是,它對Trie樹進行了空間優化,只有一個子節點的中間節點將被壓縮。一樣的,Radix樹的插入、查詢、刪除操做的時間複雜度都爲O(k)。存儲原理示意圖以下:
看下r.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)
err = http.ListenAndServe(address, engine)
return
}
複製代碼
能夠看到,實際調用的,仍是http.ListenAndServe這個方法,engine做爲handler參數傳入。http.ListenAndServe的原理,在前文httpServer有過闡述,這裏不作過多分析。
gin有以下特色:
gin對外接口和代碼實現都很是優秀,不管是項目使用,仍是源碼學習,都值得推薦。