gin:gin是一個用golang編寫的web框架,它采用了類Martini的api豪治,有更好的性能谆级,速度快40倍烤礁,如果你需要非常好的性能讼积,就使用gin吧
功能列表:
- 鑒權(quán)(auth)
- 上下文(context)、參數(shù)綁定(binding)脚仔、渲染(render)
- 中間件(middleware)
- 路由(routergroup勤众、tree)
- gin engine(Engine)
核心文件及目錄結(jié)構(gòu):
gin/
auth.go # 鑒權(quán)
binding/ # 包含參數(shù)綁定的各種實現(xiàn),比如form表單鲤脏、header頭们颜、query參數(shù),json猎醇、xml窥突、toml、yaml硫嘶、protobuf等格式的body數(shù)據(jù)解析格式
context.go # gin自定義的context實現(xiàn)波岛,
gin.go # gin引擎的核心文件
routergroup.go # 路由組的實現(xiàn),不再使用httprouter中的router音半,自己實現(xiàn)了routergroup
tree.go # 基于radix tree的數(shù)據(jù)結(jié)構(gòu)则拷,用于保存路由信息,基于httprouter的實現(xiàn)(https://github.com/julienschmidt/httprouter)
render/ # 數(shù)據(jù)渲染相關曹鸠,包含html煌茬、json、text彻桃、xml坛善、yaml、protobuf等格式的數(shù)據(jù)渲染
其他文件及目錄
gin/
deprecated.go # 不推薦使用的內(nèi)容放到這個文件下
ginS # gin engine的單例實現(xiàn)邻眷,一般不用于線上眠屎,在編寫腳本時可能用得上
internal # 內(nèi)部的一些實現(xiàn),包含多個json庫的封裝肆饶,如jsoniter改衩、sonic json等,bytes轉(zhuǎn)string驯镊、string轉(zhuǎn)bytes的函數(shù)封裝葫督。比較簡單
debug.go # debug相關
errors.go # errors相關定義
logger.go # 日志輸出實現(xiàn)
mode.go # 多種運行模式
recover.go # 異常恢復的捕捉
response_writer.go # 封裝http ResponseWriter
utils.go # 工具包
gin架構(gòu)設計
image.png
接下來分別從RouterGroup板惑、Context橄镜、Engine來分析gin的核心源碼實現(xiàn)
RouterGroup.go
// 路由組、實現(xiàn)了IRouter接口
type RouterGroup struct {
// Handlers 存儲了調(diào)用Use函數(shù)的中間件
Handlers HandlersChain
// 存儲路由組的url前綴
basePath string
// 存儲了engine的地址冯乘,在路由組中可以直接調(diào)用engine的addRoute方法和noRoute洽胶、noMethod HandlerChain
engine *Engine
root bool
}
type IRouter interface {
IRoutes
Group(string, ...HandlerFunc) *RouterGroup
}
// IRoutes defines all router handle interface.
type IRoutes interface {
Use(...HandlerFunc) IRoutes
Handle(string, string, ...HandlerFunc) IRoutes
Any(string, ...HandlerFunc) IRoutes
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
Match([]string, string, ...HandlerFunc) IRoutes
StaticFile(string, string) IRoutes
StaticFileFS(string, string, http.FileSystem) IRoutes
Static(string, string) IRoutes
StaticFS(string, http.FileSystem) IRoutes
}
// 當我們調(diào)用Group返回一個子路由組時,子路由組的Handlers繼承了父路由組的所有handlers裆馒,basePath 也添加了父路由組的路徑姊氓。所以父路由組的handlers都會作用于所有的子路由組中
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
// 將middleware 追加到group的handlers中
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
// 通用方法丐怯、下面的Handle、GET他膳、POST等都會調(diào)用handle
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
// 從group中計算basePath + relativePath响逢,返回根路徑
absolutePath := group.calculateAbsolutePath(relativePath)
// 從group中返回所有middleware和傳進來的handlers
handlers = group.combineHandlers(handlers)
// 調(diào)用engine的addRoute方法绒窑,將url和handlers寫入httprouter的前綴樹中
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
Context.go
type Context struct {
// 對ResponseWriter的實現(xiàn)
writermem responseWriter
// 保留ServeHTTP的Request
Request *http.Request
// 自定義的ResponseWriter接口棕孙、組合了http.ResponseWriter、http.Hijacker些膨、http.Flusher接口蟀俊,
Writer ResponseWriter
// 存儲url的restful風格的參數(shù):/index/:name/:id
Params Params
// 存儲tree中當前url的handlers鏈(包含middlewares和業(yè)務處理handler)
handlers HandlersChain
// 記錄Next函數(shù)處理Handlers鏈的位置
index int8
// url的全路徑
fullPath string
// 保存engine的指針
engine *Engine
// 保存從tree中通過getValue返回的url參數(shù),params的值會賦值給上面的Params
params *Params
skippedNodes *[]skippedNode
mu sync.RWMutex
// 存儲context上下文數(shù)據(jù)
Keys map[string]any
Errors errorMsgs
Accepted []string
// 緩存c.Request.URL.Query()中的值
queryCache url.Values
// 緩存c.Request.PostForm中的數(shù)據(jù)订雾,包含POST肢预、PATCH、PUT方法的body參數(shù)
formCache url.Values
sameSite http.SameSite
}
從sync.Pool中取出context后洼哎,做一些初始化的工作
func (c *Context) reset() {
c.Writer = &c.writermem
c.Params = c.Params[:0]
c.handlers = nil
c.index = -1
c.fullPath = ""
c.Keys = nil
c.Errors = c.Errors[:0]
c.Accepted = nil
c.queryCache = nil
c.formCache = nil
c.sameSite = 0
*c.params = (*c.params)[:0]
*c.skippedNodes = (*c.skippedNodes)[:0]
}
由于context不是線程安全的烫映,如果要在多個協(xié)程中使用context,可以調(diào)用封裝好的Copy函數(shù)
func (c *Context) Copy() *Context {}
Next函數(shù)用于遞歸調(diào)用Middleware噩峦,先執(zhí)行所有Middleware函數(shù)調(diào)用Next()之前的部分锭沟,再執(zhí)行業(yè)務邏輯的Handler,最后采用先進后出的方式识补,依次執(zhí)行所有Middleware函數(shù)調(diào)用Next()之后的內(nèi)容族淮。
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++ // 這兒的index++,主要是為了解決凭涂,如果在Middleware中沒有調(diào)用Next()函數(shù)祝辣,能夠保證程序能夠繼續(xù)執(zhí)行,而不是死循環(huán)
}
}
// 將index設置為abortIndex切油,終止后續(xù)middleware和業(yè)務handler的執(zhí)行
func (c *Context) Abort() {
c.index = abortIndex
}
Key相關的函數(shù)
// 設置key蝙斜,keys用于存儲上下文數(shù)據(jù)。如果調(diào)用ShouldBindBodyWith方法澎胡,也會把body整個內(nèi)容緩存為一個key/value存儲到keys中
func (c *Context) Set(key string, value any) {}
func (c *Context) Get(key string) (value any, exists bool) {}
// 從Key中獲取對應的string類型的值乍炉,其他類型的實現(xiàn)類似,不額外介紹
func (c *Context) GetString(key string) (s string) {
if val, ok := c.Get(key); ok && val != nil {
s, _ = val.(string)
}
return
}
func (c *Context) GetBool(key string) (b bool) {}
func (c *Context) GetInt(key string) (i int) {}
func (c *Context) GetInt64(key string) (i64 int64) {}
func (c *Context) GetUint(key string) (ui uint) {}
func (c *Context) GetUint64(key string) (ui64 uint64) {}
func (c *Context) GetFloat64(key string) (f64 float64) {}
func (c *Context) GetTime(key string) (t time.Time) {}
func (c *Context) GetDuration(key string) (d time.Duration) {}
func (c *Context) GetStringSlice(key string) (ss []string) {}
func (c *Context) GetStringMap(key string) (sm map[string]any) {}
func (c *Context) GetStringMapString(key string) (sms map[string]string) {}
func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) {}
Param 相關函數(shù)
// 獲取Param 參數(shù)滤馍,Param參數(shù)從路由tree中解析出來的岛琼,如果路由參數(shù)中有多個相同key的數(shù)據(jù),只會返回第一個值
func (c *Context) Param(key string) string {
return c.Params.ByName(key)
}
// 也可以手動設置param參數(shù)的值
func (c *Context) AddParam(key, value string) {
c.Params = append(c.Params, Param{Key: key, Value: value})
}
Query參數(shù)相關函數(shù)
// 如果存在巢株,從url query中返回值槐瑞,否則返回"",是c.Request.URL.Query().Get(key)的簡寫
//
// GET /path?id=1234&name=Manu&value=
// c.Query("id") == "1234"
// c.Query("name") == "Manu"
// c.Query("value") == ""
// c.Query("wtf") == ""
func (c *Context) Query(key string) (value string) {
value, _ = c.GetQuery(key)
return
}
// 帶默認值的Query
func (c *Context) DefaultQuery(key, defaultValue string) string {
if value, ok := c.GetQuery(key); ok {
return value
}
return defaultValue
}
// GetQuery 和 Query()類似, 多一個bool返回值阁苞,是Query()函數(shù)的底層調(diào)用
//
// GET /?name=Manu&lastname=
// ("Manu", true) == c.GetQuery("name")
// ("", false) == c.GetQuery("id")
// ("", true) == c.GetQuery("lastname")
func (c *Context) GetQuery(key string) (string, bool) {
if values, ok := c.GetQueryArray(key); ok {
return values[0], ok
}
return "", false
}
// QueryArray 返回[]string
func (c *Context) QueryArray(key string) (values []string) {
values, _ = c.GetQueryArray(key)
return
}
// 初始化query緩存
func (c *Context) initQueryCache() {
if c.queryCache == nil {
if c.Request != nil {
c.queryCache = c.Request.URL.Query()
} else {
c.queryCache = url.Values{}
}
}
}
// GetQueryArray 返回值帶[]string和bool
func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
c.initQueryCache() // 初始化query cache
values, ok = c.queryCache[key]
return
}
// QueryMap 返回字典值
func (c *Context) QueryMap(key string) (dicts map[string]string) {
dicts, _ = c.GetQueryMap(key)
return
}
// GetQueryMap 返回bool和字段值
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
c.initQueryCache() // 初始化query cache
return c.get(c.queryCache, key) // 返回map格式數(shù)據(jù)
}
// PostForm 從post中返回key
func (c *Context) PostForm(key string) (value string) {
value, _ = c.GetPostForm(key)
return
}
// DefaultPostForm 返回帶默認值的key
func (c *Context) DefaultPostForm(key, defaultValue string) string {
if value, ok := c.GetPostForm(key); ok {
return value
}
return defaultValue
}
// GetPostForm 返回key值和bool
func (c *Context) GetPostForm(key string) (string, bool) {
if values, ok := c.GetPostFormArray(key); ok {
return values[0], ok
}
return "", false
}
// PostFormArray 返回切片類型的值
func (c *Context) PostFormArray(key string) (values []string) {
values, _ = c.GetPostFormArray(key)
return
}
// 初始化form 緩存
func (c *Context) initFormCache() {
if c.formCache == nil {
c.formCache = make(url.Values)
req := c.Request
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
if !errors.Is(err, http.ErrNotMultipart) {
debugPrint("error on parse multipart form array: %v", err)
}
}
c.formCache = req.PostForm
}
}
// GetPostFormArray 返回切片類型值和bool
func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
c.initFormCache()
values, ok = c.formCache[key]
return
}
// PostFormMap 返回字典類型的值
func (c *Context) PostFormMap(key string) (dicts map[string]string) {
dicts, _ = c.GetPostFormMap(key)
return
}
// GetPostFormMap 返回字典類型的值和bool
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
c.initFormCache()
return c.get(c.formCache, key)
}
//
func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {
dicts := make(map[string]string)
exist := false
for k, v := range m {
if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {
if j := strings.IndexByte(k[i+1:], ']'); j >= 1 {
exist = true
dicts[k[i+1:][:j]] = v[0]
}
}
}
return dicts, exist
}
Query參數(shù)Binding
MustBind在綁定出錯時返回400錯誤
Bind在綁定出錯時不會返回錯誤
// Bind 會通過請求方法和content-type來判斷使用哪個類型的bind
func (c *Context) Bind(obj any) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.MustBindWith(obj, b)
}
// BindJSON 使用json解析body數(shù)據(jù)
func (c *Context) BindJSON(obj any) error {
return c.MustBindWith(obj, binding.JSON)
}
// 同上
func (c *Context) BindXML(obj any) error {}
func (c *Context) BindQuery(obj any) error {}
func (c *Context) BindYAML(obj any) error {}
func (c *Context) BindTOML(obj any) error {}
// BindHeader 綁定header數(shù)據(jù)到obj對象
func (c *Context) BindHeader(obj any) error {
return c.MustBindWith(obj, binding.Header)
}
// BindUri 將uri參數(shù)綁定到obj
func (c *Context) BindUri(obj any) error {
if err := c.ShouldBindUri(obj); err != nil {
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck
return err
}
return nil
}
// MustBindWith 使用提供的binding將數(shù)據(jù)綁定到obj困檩,如果綁定出錯祠挫,返回400錯誤
func (c *Context) MustBindWith(obj any, b binding.Binding) error {
if err := c.ShouldBindWith(obj, b); err != nil {
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck
return err
}
return nil
}
// ShouldBind
func (c *Context) ShouldBind(obj any) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.ShouldBindWith(obj, b)
}
// ShouldBindJSON
func (c *Context) ShouldBindJSON(obj any) error {
return c.ShouldBindWith(obj, binding.JSON)
}
func (c *Context) ShouldBindXML(obj any) error {}
func (c *Context) ShouldBindQuery(obj any) error {}
func (c *Context) ShouldBindYAML(obj any) error {}
func (c *Context) ShouldBindTOML(obj any) error {}
func (c *Context) ShouldBindHeader(obj any) error {}
func (c *Context) ShouldBindUri(obj any) error {}
// ShouldBindWith 使用提供的binding將request數(shù)據(jù)綁定到obj,出現(xiàn)錯誤不會返回400
func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
return b.Bind(c.Request, obj)
}
// ShouldBindBodyWith 與 ShouldBindWith 類似, 但會body中的數(shù)據(jù)緩存到context的Keys中悼沿,供下次調(diào)用重用
// 注意: 該方法在綁定之前讀取body數(shù)據(jù)等舔。所以如果只調(diào)用一次的話,使用ShouldBindWith會有更好的性能糟趾,
func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error) {
var body []byte
if cb, ok := c.Get(BodyBytesKey); ok {
if cbb, ok := cb.([]byte); ok {
body = cbb
}
}
if body == nil {
body, err = io.ReadAll(c.Request.Body)
if err != nil {
return err
}
c.Set(BodyBytesKey, body)
}
return bb.BindBody(body, obj)
}
Cookie相關函數(shù)
// 讀寫Cookie
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {}
func (c *Context) Cookie(name string) (string, error) {}
Render渲染相關函數(shù)
// Render 寫入響應頭和調(diào)用Render來渲染數(shù)據(jù)
func (c *Context) Render(code int, r render.Render) {
c.Status(code)
if !bodyAllowedForStatus(code) {
r.WriteContentType(c.Writer)
c.Writer.WriteHeaderNow()
return
}
if err := r.Render(c.Writer); err != nil {
// Pushing error to c.Errors
_ = c.Error(err)
c.Abort()
}
}
// 使用http模板渲染數(shù)據(jù)慌植,內(nèi)部統(tǒng)一調(diào)用Render函數(shù)來渲染數(shù)據(jù)
func (c *Context) HTML(code int, name string, obj any) {}
func (c *Context) IndentedJSON(code int, obj any) {} // 使用智能json
func (c *Context) SecureJSON(code int, obj any) {}
func (c *Context) JSONP(code int, obj any) {}
func (c *Context) JSON(code int, obj any) {}
func (c *Context) AsciiJSON(code int, obj any) {}
func (c *Context) PureJSON(code int, obj any) {}
func (c *Context) XML(code int, obj any) {}
func (c *Context) YAML(code int, obj any) {}
func (c *Context) TOML(code int, obj any) {}
func (c *Context) ProtoBuf(code int, obj any) {}
func (c *Context) String(code int, format string, values ...any) {}
// 重定向
func (c *Context) Redirect(code int, location string) {}
Negotiate
Negotiate主要用于根據(jù)客戶端提供的格式,協(xié)商返回對應格式的數(shù)據(jù)
Engine.go
// HandlerFunc 定義了中間件和業(yè)務handler的函數(shù)
type HandlerFunc func(*Context)
// HandlersChain Handlers鏈义郑,底層為handler的切片
type HandlersChain []HandlerFunc
// gin Engine
type Engine struct {
// 組合了RouterGroup蝶柿,所以可在engine中可以直接調(diào)用Group()、GET()非驮、POST()等方法
RouterGroup
// 啟用自動重定向交汤,如果當前路由無法匹配,但存在帶(不帶)尾部斜杠的路徑處理程序劫笙。
// 例如芙扎,如果請求 /foo/ 但僅存在 /foo 的路由,則對于 GET 請求填大,客戶端將重定向到 /foo戒洼,HTTP 狀態(tài)代碼為 301
// 和 307 用于所有其他請求方法。
RedirectTrailingSlash bool
// 啟用路徑修復栋盹,如果沒有已為其注冊的handler施逾,則路由器嘗試修復當前請求路徑。
// 首先刪除多余的路徑元素例获,例如 ../ 或 // 汉额。
// 然后路由器對清理后的路徑進行不區(qū)分大小寫的查找。
// 如果可以找到該路由的句柄榨汤,則路由器進行重定向到正確的路徑蠕搜,
// GET 請求的狀態(tài)代碼為 301, 所有其他請求方法返回的狀態(tài)代碼為 307 收壕。
// 例如 /FOO 和 /..//Foo 可以重定向到 /foo妓灌。
// RedirectTrailingSlash 與此選項沒有關系。
RedirectFixedPath bool
// 如果啟用蜜宪,如果當前請求無法路由虫埂,路由器將檢查是否允許使用其他方法
// 如果是這種情況,則請求將得到“不允許的方法”的響應和 HTTP 405 狀態(tài)碼圃验。
// 如果沒有其他方法可路由掉伏,則返回 NotFound Handler
HandleMethodNotAllowed bool
UseRawPath bool
UnescapePathValues bool
// 移出額外的斜杠
RemoveExtraSlash bool
// 限制http.Request的 ParseMultipartForm 的maxMemory參數(shù)值
MaxMultipartMemory int64
// UseH2C 開啟http2協(xié)議,使用http2client.
UseH2C bool
delims render.Delims
secureJSONPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
// 存儲context的pool
pool sync.Pool
// 使用壓縮前綴樹(radix tree) 存儲url和handler的對應關系
trees methodTrees
maxParams uint16
maxSections uint16
trustedProxies []string
trustedCIDRs []*net.IPNet
}
// 驗證Engine實現(xiàn)了IRouter
var _ IRouter = (*Engine)(nil)
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
// ...
}
engine.RouterGroup.engine = engine
// 配置pool 生成臨時對象的New函數(shù)
engine.pool.New = func() any {
return engine.allocateContext(engine.maxParams)
}
return engine
}
// Default 調(diào)用New函數(shù)初始化Engine,添加了Logger和Recover中間件
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
// context臨時對象的生成方法斧散,在New函數(shù)中調(diào)用
func (engine *Engine) allocateContext(maxParams uint16) *Context {
v := make(Params, 0, maxParams)
skippedNodes := make([]skippedNode, 0, engine.maxSections)
return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
}
// 將中間件添加到RouterGroup的Handler鏈中
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
// 通過method供常、path和handler,將路由信息添加到tree中
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)
// root為空鸡捐,就生成新的root節(jié)點
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
// 添加路由到tree中栈暇、tree中保存了多個壓縮前綴樹結(jié)構(gòu)(每個請求方法一個)
root.addRoute(path, handlers)
// Update maxParams
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
engine.maxSections = sectionsCount
}
}
// 調(diào)用http.ListenAndServe 啟動服務,使用不同的模式啟動服務
func (engine *Engine) Run(addr ...string) (err error) {}
func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {}
func (engine *Engine) RunUnix(file string) (err error) {}
func (engine *Engine) RunFd(fd int) (err error) {}
func (engine *Engine) RunListener(listener net.Listener) (err error) {}
// 實現(xiàn)了http.Handler接口箍镜,請求到達服務端后源祈,會將請求交給ServeHTTP函數(shù)來處理
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 從pool中獲取一個context
c := engine.pool.Get().(*Context)
// 初始化ResponseWriter、Requet
c.writermem.reset(w)
c.Request = req
c.reset()
// handleHTTPRequest處理實際的請求
engine.handleHTTPRequest(c)
// 將context放回pool
engine.pool.Put(c)
}
// 處理具體的請求
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
}
if engine.RemoveExtraSlash {
rPath = cleanPath(rPath)
}
// Find root of the tree for the given HTTP method
t := engine.trees
// 從trees中根據(jù)請求方法類型獲取對應的tree
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// 從tree中獲取handlers鹿寨,并返回handlers和params
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
// handlers不為空新博,調(diào)用Next()方法遞歸調(diào)用middleware和業(yè)處理handler
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != http.MethodConnect && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
}
// 如果開啟了HandleMethodNotAllowed薪夕,就繼續(xù)遍歷tree脚草,在其他tree中去匹配路由,如果匹配到后原献,就返回405錯誤
if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
// 返回404錯誤
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
到這里馏慨,gin的核心模塊context、routergroup姑隅、engine的實現(xiàn)就分析完了写隶,radix tree的實現(xiàn)可以參考httprouter。