內容:golang 的gin框架是一個比原生更高性能的web開發框架
Hello world
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
}
g.H
gin.H 是 map[string]interface{} 的一個快捷名稱
type H map[string]interface{}
g.Engine
Engine是框架的入口,通過Engine物件來定義服務路由資訊、組裝插件、運行服務,
正如Engine的中文意思「引擎」一樣,它就是框架的核心發動機,整個Web服務的都是由它來驅動的,
Engine 的本質只是對內置的HTTP服務器的包裝,讓它使用起來更加便捷,
gin.Default() 函式會生成一個默認的 Engine 物件,里面包含了 2 個默認的常用插件,
分別是Logger和Recovery,Logger用于輸出請求日志,Recovery確保單個請求發生panic時
記錄例外堆疊日志,輸出統一的錯誤回應,
func Default() *Engine {
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
route tree:
路由樹:
在 Gin 框架中,路由規則被分成了最多9棵前綴樹,每一個 HTTP Method對應一棵「前綴樹」,
樹的節點按照 URL 中的 / 符號進行層級劃分,URL 支持 :name 形式的名稱匹配,
還支持 *subpath 形式的路徑通配符 ,
每個節點都會掛接若干請求處理函式構成一個請求處理鏈 HandlersChain,
當一個請求到來時,在這棵樹上找到請求 URL 對應的節點,拿到對應的請求處理鏈來執行
type Engine struct {
...
trees methodTrees
...
}
type methodTrees []methodTree
type methodTree struct {
method string
root *node // 樹根
}
type node struct {
path string // 當前節點的路徑
...
handlers HandlersChain // 請求處理鏈
...
}
type HandlerFunc func(*Context)
type HandlersChain []HandlerFunc
Engine 物件包含一個 addRoute 方法用于添加URL請求處理器,它會將對應的路徑和處理器
掛接到相應的請求樹中:
func (e *Engine) addRoute(method, path string, handlers HandlersChain)
g.RouterGroup
RouterGroup 是對路由樹的包裝,所有的路由規則最終都是由它來進行管理,
Engine結構體繼承了RouterGroup ,所以Engine直接具備了RouterGroup所有的路由管理功能,
這是為什么在 Hello World 的例子中,可以直接使用 Engine 物件來定義路由規則,
同時 RouteGroup 物件里面還會包含一個 Engine 的指標,這樣Engine和RouteGroup就成了
「你中有我我中有你」的關系,
type Engine struct {
RouterGroup
...
}
type RouterGroup struct {
...
engine *Engine
...
}
RouterGroup 實作了 IRouter 介面,暴露了一系列路由方法,這些方法最終都是通過呼叫 Engine.addRoute 方法將請求處理器掛接到路由樹中,
GET(string, ...HandlerFunc) IRoutes
POST(string, ...HandlerFunc) IRoutes
DELETE(string, ...HandlerFunc) IRoutes
PATCH(string, ...HandlerFunc) IRoutes
PUT(string, ...HandlerFunc) IRoutes
OPTIONS(string, ...HandlerFunc) IRoutes
HEAD(string, ...HandlerFunc) IRoutes
// 匹配所有 HTTP Method
Any(string, ...HandlerFunc) IRoutes
RouterGroup 內部有一個前綴路徑屬性,它會將所有的子路徑都加上這個前綴再放進路由樹中,
有了這個前綴路徑,就可以實作URL分組功能,Engine 物件內嵌的RouterGroup物件的前綴路徑是/,
它表示根路徑,RouterGroup 支持分組嵌套,使用Group方法就可以讓分組下面再掛分組
func main() {
router := gin.Default()
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
}
Engine物件里面的RouterGroup物件就是第一層分組,也就是根分組,v1和v2都是根分組的子分組,
g.Context
這個物件里保存了請求的背景關系資訊,它是所有請求處理器的入口引數,
type HandlerFunc func(*Context)
type Context struct {
...
Request *http.Request // 請求物件
Writer ResponseWriter // 回應物件
Params Params // URL匹配引數
...
Keys map[string]interface{} // 自定義背景關系資訊
...
}
Context 物件提供了非常豐富的方法用于獲取當前請求的背景關系資訊,如果需要獲取請求中的URL引數、
Cookie、Header 都可以通過Context物件來獲取,這一系列方法是對http.Request物件的包裝,
// 獲取 URL 匹配引數 /book/:id
func (c *Context) Param(key string) string
// 獲取 URL 查詢引數 /book?id=123&page=10
func (c *Context) Query(key string) string
// 獲取 POST 表單引數
func (c *Context) PostForm(key string) string
// 獲取上傳的檔案物件
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
// 獲取請求Cookie
func (c *Context) Cookie(name string) (string, error)
...
Context 物件提供了很多內置的回應形式,JSON、HTML、Protobuf 、MsgPack、Yaml 等,
它會為每一種形式都單獨定制一個渲染器,通常這些內置渲染器已經足夠應付絕大多數場景,
如果你覺得不夠,還可以自定義渲染器,
func (c *Context) JSON(code int, obj interface{})
func (c *Context) Protobuf(code int, obj interface{})
func (c *Context) YAML(code int, obj interface{})
...
// 自定義渲染
func (c *Context) Render(code int, r render.Render)
// 渲染器通用介面
type Render interface {
Render(http.ResponseWriter) error
WriteContentType(w http.ResponseWriter)
}
所有的渲染器最侄訓是需要呼叫內置的http.ResponseWriter(Context.Writer)將回應物件
轉換成位元組流寫到套接字中,
type ResponseWriter interface {
// 容納所有的回應頭
Header() Header
// 寫Body
Write([]byte) (int, error)
// 寫Header
WriteHeader(statusCode int)
}
插件與請求鏈:
我們撰寫業務代碼時一般也就是一個處理函式,為什么路由節點需要掛接一個函式鏈呢?
type node struct {
path string // 當前節點的路徑
...
handlers HandlersChain // 請求處理鏈
...
}
type HandlerFunc func(*Context)
type HandlersChain []HandlerFunc
這是因為Gin提供了插件,只有函式鏈的尾部是業務處理,前面的部分都是插件函式,
在Gin中插件和業務處理函式形式是一樣的,都是 func(*Context),
當我們定義路由時,Gin會將插件函式和業務處理函式合并在一起形成一個鏈條結構,
type Context struct {
...
index uint8 // 當前的業務邏輯位于函式鏈的位置
handlers HandlersChain // 函式鏈
...
}
// 挨個呼叫鏈條中的處理函式
func (c *Context) Next() {
c.index++
for s := int8(len(c.handlers)); c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
Gin 在接收到客戶端請求時,找到相應的處理鏈,構造一個 Context 物件,
再呼叫它的 Next() 方法就正式進入了請求處理的全流程,

Gin還支持 Abort() 方法中斷請求鏈的執行,它的原理是將 Context.index調整到一個比較大
的數字,這樣 Next() 方法中的呼叫回圈就會立即結束,
需要注意的 Abort() 方法并不是通過 panic 的方式中斷執行流,執行 Abort() 方法之后,
當前函式內后面的代碼邏輯還會繼續執行,
const abortIndex = 127
func (c *Context) Abort() {
c.index = abortIndex
}
func SomePlugin(c *Context) {
...
if condition {
c.Abort()
// continue executing
}
...
}
如果在插件中顯示呼叫 Next() 方法,那么它就改變了正常的順序執行流,換個角度來理解,
正常的執行流就是后續的處理器是在前一個處理器的尾部執行,而嵌套執行流是讓后續的處理器
在前一個處理器進行到一半時執行,待后續處理器完成執行后,再回到前一個處理器繼續往下執行,

RouterGroup 提供了 Use() 方法來注冊插件,因為 RouterGroup 是一層套一層,
不同層級的路由可能會注冊不一樣的插件,最終不同的路由節點掛接的處理函式鏈也不盡相同,
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
// 注冊 Get 請求
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers)
}
func (g *RouterGroup) handle(method, path string, handlers HandlersChain)
IRoutes {
// 合并URL (RouterGroup有URL前綴)
absolutePath := group.calculateAbsolutePath(relativePath)
// 合并處理鏈條
handlers = group.combineHandlers(handlers)
// 注冊路由樹
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
HTTP ERROR:
當URL請求對應的路徑不能在路由樹里找到時,就需要處理404 NotFound錯誤,
當URL的請求路徑在路由樹里找到,但是Method不匹配,就需要處理405 MethodNotAllowed錯誤,
Engine 物件為這兩個錯誤提供了處理器注冊的入口
func (engine *Engine) NoMethod(handlers ...HandlerFunc)
func (engine *Engine) NoRoute(handlers ...HandlerFunc)
例外處理器和普通處理器一樣,也需要和插件函陣列合在一起形成一個呼叫鏈,
如果沒有提供例外處理器,Gin 就會使用內置的簡易錯誤處理器,
注意這兩個錯誤處理器是定義在 Engine 全域物件上,而不是 RouterGroup,
對于非 404 和 405 錯誤,需要用戶自定義插件來處理,對于 panic 拋出來的例外需要也
需要使用插件來處理,
表單:
當請求引數數量比較多時,使用 Context.Query() 和 Context.PostForm() 方法來獲取
引數就會顯得比較繁瑣,Gin 框架也支持表單處理,將表單引數和結構體欄位進行直接映射,
package main
import (
"github.com/gin-gonic/gin"
)
type LoginForm struct {
User string `form:"user" binding:"required"`
Password string `form:"password" binding:"required"`
}
func main() {
router := gin.Default()
router.POST("/login", func(c *gin.Context) {
var form LoginForm
if c.ShouldBind(&form) == nil {
if form.User == "user" && form.Password == "password" {
c.JSON(200, gin.H{"status": "you are logged in"})
} else {
c.JSON(401, gin.H{"status": "unauthorized"})
}
}
})
router.Run(":8080")
}
Context.ShouldBind 方法遇到校驗不通過時,會回傳一個錯誤物件告知呼叫者校驗失敗的原因,
它支持多種資料系結型別,如 XML、JSON、Query、Uri、MsgPack、Protobuf等,根據請求的
Content-Type 頭來決定使用何種資料系結方法,
func (c *Context) ShouldBind(obj interface{}) error {
// 獲取系結器
b := binding.Default(c.Request.Method, c.ContentType())
// 執行系結
return c.ShouldBindWith(obj, b)
}
默認內置的表單校驗功能很強大,它通過結構體欄位 tag 標注來選擇相應的校驗器進行校驗,
Gin還提供了注冊自定義校驗器的入口,支持用戶自定義一些通用的特殊校驗邏輯,
Context.ShouldBind 是比較柔和的校驗方法,它只負責校驗,并將校驗結果以回傳值的形式
傳遞給上層,Context 還有另外一個比較暴力的校驗方法 Context.Bind,它和ShouldBind
的呼叫形式一摸一樣,區別是當校驗錯誤發生時,它會呼叫 Abort() 方法中斷呼叫鏈的執行,
向客戶端回傳一個 HTTP 400 Bad Request 錯誤,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/283073.html
標籤:其他
上一篇:反碎片技術和虛擬可移動區域
