gin 框架架構(gòu)及源碼解析

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。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末讲仰,一起剝皮案震驚了整個濱河市慕趴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鄙陡,老刑警劉巖冕房,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異趁矾,居然都是意外死亡耙册,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人虏束,你說我怎么就攤上這事镶摘。” “怎么了突照?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我弃揽,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任蹋宦,我火速辦了婚禮披粟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冷冗。我一直安慰自己守屉,他們只是感情好,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布蒿辙。 她就那樣靜靜地躺著拇泛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪思灌。 梳的紋絲不亂的頭發(fā)上俺叭,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機與錄音泰偿,去河邊找鬼熄守。 笑死,一個胖子當著我的面吹牛耗跛,可吹牛的內(nèi)容都是我干的裕照。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼调塌,長吁一口氣:“原來是場噩夢啊……” “哼晋南!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起羔砾,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤负间,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后姜凄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體政溃,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年檀葛,在試婚紗的時候發(fā)現(xiàn)自己被綠了玩祟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡屿聋,死狀恐怖空扎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情润讥,我是刑警寧澤转锈,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站楚殿,受9級特大地震影響撮慨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一砌溺、第九天 我趴在偏房一處隱蔽的房頂上張望影涉。 院中可真熱鬧,春花似錦规伐、人聲如沸蟹倾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鲜棠。三九已至,卻和暖如春培慌,著一層夾襖步出監(jiān)牢的瞬間豁陆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工吵护, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盒音,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓何址,卻偏偏與公主長得像里逆,于是被迫代替她去往敵國和親进胯。 傳聞我的和親對象是個殘疾皇子用爪,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容