gin框架總結(jié)

gin框架總結(jié)

一 gin框架初識(shí)

1.1 helloworld

gin框架中的路由是基于httprouter開(kāi)發(fā)的。HelloWorld:

package main
import (
    "github.com/gin-gonic/gin"
    "fmt"
)
func main() {
    r := gin.Default()    //Default返回一個(gè)默認(rèn)路由引擎
    r.GET("/", func(c *gin.Context) {
        username := c.Query("username")
        fmt.Println(username)
        c.JSON(200, gin.H{
            "msg":"hello world",
        })
    })
    r.Run()            //默認(rèn)位于0.0.0.0:8080,可傳入?yún)?shù)":3030";也可以綁定服務(wù)器
}

二 參數(shù)獲取

2.1 get請(qǐng)求參數(shù)獲取方式:

c.Query("username")
c.QueryDefault("username","lisi")       //如果username為空,則賦值為lisi
name := c.Param("name")                  //路由地址為:/user/:name/:pass,獲取參數(shù)
reqPara := c.Request.URL.Query()    //獲取Get的所有參數(shù)

2.2 post請(qǐng)求參數(shù)獲取

name := c.PostForm("name")
price := c.DefaultPostForm("price", "100")
reqPost = c.Request.PostForm      //獲取 Post 所有參數(shù)

2.3 參數(shù)綁定

參數(shù)綁定利用反射機(jī)制,自動(dòng)提取querystring,form表單,json,xml等參數(shù)到結(jié)構(gòu)體中,可以極大提升開(kāi)發(fā)效率。

package main
import (
    "net/http"
    "github.com/gin-gonic/gin"
    "fmt"
)
type User struct {
    Username string `form:"username" json:"username" binding:"required"`
    Password string `form:"password" json:"password" binding:"required"`
}

func login(c *gin.Context) {
    var user User
    fmt.Println(c.PostForm("username"))
    fmt.Println(c.PostForm("password"))
    err := c.ShouldBind(&user)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": err.Error()
        })
    }

    c.JSON(http.StatusOK, gin.H{
        "username": user.Username,
        "password": user.Password,
    })
}

func main() {
    router := gin.Default()
    router.POST("/login", login)
    router.Run(":3000")
}

三 靜態(tài)文件

靜態(tài)化當(dāng)前目錄下static文件夾:

    router := gin.Default()
    router.Static("/static", "./static")
    router.Run(":3000")

注意:同樣推薦使用go build,不要使用開(kāi)發(fā)工具的run功能。

四 結(jié)果返回

4.1 返回JSON

c.JSON(200,gin.H{"msg":"OK"})
c.JSON(200,結(jié)構(gòu)體)

4.2 返回模板

    router.LoadHTMLGlob("templates/**/*")
    router.GET("/test/index", func(c *gin.Context){
        c.HTML(http.StatusOK, "test/index.tmpl", gin.H{
            "msg": "test",
        })
    })

模板文件:index.tmpl


{{define "test/index.tmpl"}}
<html>

    <head>
    </head>

    <body>

        test...

        {{.}}
        -----
        {{.msg}}

    </body>

</html>

{{end}}

注意事項(xiàng):不要使用編輯器的run功能,會(huì)出現(xiàn)路徑錯(cuò)誤,推薦使用命令build,項(xiàng)目路徑分配如下:


gin-01.png

五 文件上傳

5.1 單文件上傳

 router.POST("/upload", func (c *gin.Context) {
    file, err := c.FormFile("file")
    if (err != nil) {
        c.JSON(http.StatusInternalServerError, gin.H{
            "msg": err.Error(),
        })
        return
    }
    dst := fmt.Sprintf("/uploads/&s", file.Filename)
    c.SavaeUpLoadedFile(file, dst)
    c.JSON(http.StatusOK, gin.H{
        "msg":"ok",
    })
 })

5.2 多文件上傳

 router.POST("/upload", func(c *gin.Context) {
        // 多文件
        form, _ := c.MultipartForm()
        files := form.File["upload[]"]

        for _, file := range files {
            log.Println(file.Filename)

            // 上傳文件到指定的路徑
            // c.SaveUploadedFile(file, dst)
        }
        c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
    })

一 路由分組

訪問(wèn)路徑是:/user/login/user/signin

package main

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

func login(c *gin.Context) {
    c.JSON(300, gin.H{
        "msg": "login",
    })
}

func logout(c *gin.Context) {
    c.JSON(300, gin.H{
        "msg": "logout",
    })
}

func main() {

    router := gin.Default()

    user := router.Group("/user")
    {
        user.GET("/login", login)
        user.GET("/logout", logout)
    }

    router.Run(":3000")
}

二 路由設(shè)計(jì)

2.0 項(xiàng)目結(jié)構(gòu)

筆者自己的路由設(shè)計(jì),僅供參考:

項(xiàng)目結(jié)構(gòu)如圖:

gin-02.png

2.1 main.go

main.go:

package main

import (
    "Demo1/router"
)

func main() {
    r := router.InitRouter()
    _ = r.Run()
}

2.2 路由模塊化核心 routes.go

routes.go:

package router

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

func InitRouter() *gin.Engine {

    r := gin.Default()

    // 路由模塊化
    userRouter(r)
    orderRouter(r)

    return r
}

2.3 業(yè)務(wù)處理

userRouter.go示例:

package router

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

func userRouter(r *gin.Engine) {

    r.GET("/user/login", userLogin)

}

func userLogin(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "code": 10001,
        "msg": "登錄成功",
        "data": nil,
    })
}

gin配合單元測(cè)試

https://github.com/stretchr/testify/assert 是個(gè)很好的單元測(cè)試框架。

在上一節(jié)中配置了筆者自己項(xiàng)目的路由模塊化思路,下面是配套的單元測(cè)試demo:

userRouter_test.go

package test

import (
    "Demo1/router"
    "github.com/stretchr/testify/assert"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestUserRouter_userLogin(t *testing.T) {
    r := router.InitRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest(http.MethodGet, "/user/login", nil)
    r.ServeHTTP(w, req)
    assert.Equal(t, http.StatusOK, w.Code)
    assert.Equal(t, `{"code":10001,"data":null,"msg":"登錄成功"}`, w.Body.String())
}

一 Gin中間件

1.1 中間件的概念

gin框架允許在處理請(qǐng)求時(shí),加入用戶自己的鉤子函數(shù),該鉤子函數(shù)即中間件。他的作用與Java中的攔截器,Node中的中間件相似。

中間件需要返回gin.HandlerFunc函數(shù),多個(gè)中間件通過(guò)Next函數(shù)來(lái)依次執(zhí)行。

1.2 入門(mén)使用案例

現(xiàn)在設(shè)計(jì)一個(gè)中間件,在每次路由函數(shù)執(zhí)行前打印一句話,在上一節(jié)的項(xiàng)目基礎(chǔ)上新建middleware文件夾,新建一個(gè)中間件文件MyFmt.go

package middleware

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

// 定義一個(gè)中間件
func MyFMT() gin.HandlerFunc {
    return func(c *gin.Context) {
        host := c.Request.Host
        fmt.Printf("Before: %s\n",host)
        c.Next()
        fmt.Println("Next: ...")
    }
}

在路由函數(shù)中使用中間件:

r.GET("/user/login", middleware.MyFMT(),  userLogin)

打印結(jié)果:

Before: localhost:8080
Next: ...
[GIN] 2019/07/28 - 16:28:16 | 200 |      266.33μs |             ::1 | GET      /user/login

1.2 中間件的詳細(xì)使用方式

全局中間件:直接使用 gin.Engine結(jié)構(gòu)體的Use()方法,中間件將會(huì)在項(xiàng)目的全局起作用。

func InitRouter() *gin.Engine {

    r := gin.Default()

    // 全局中間件
    r.Use(middleware.MyFMT())

    // 路由模塊化
    userRouter(r)
    orderRouter(r)

    return r
}

路由分組中使用中間件:

router := gin.New()
user := router.Group("user", gin.Logger(),gin.Recovery())
{
    user.GET("info", func(context *gin.Context) {

    })
    user.GET("article", func(context *gin.Context) {

    })
}

單個(gè)路由使用中間件(支持多個(gè)中間件的使用):

router := gin.New()
router.GET("/test",gin.Recovery(),gin.Logger(),func(c *gin.Context){
    c.JSON(200,"test")
})

1.3 內(nèi)置中間件

Gin也內(nèi)置了一些中間件,可以直接使用:

func BasicAuth(accounts Accounts) HandlerFunc
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc //攔截請(qǐng)求參數(shù)并進(jìn)行綁定
func ErrorLogger() HandlerFunc       //錯(cuò)誤日志處理
func ErrorLoggerT(typ ErrorType) HandlerFunc //自定義類(lèi)型的錯(cuò)誤日志處理
func Logger() HandlerFunc //日志記錄
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc //將http.HandlerFunc包裝成中間件
func WrapH(h http.Handler) HandlerFunc //將http.Handler包裝成中間件

二 請(qǐng)求的攔截與后置

中間件的最大作用就是攔截過(guò)濾請(qǐng)求,比如我們有些請(qǐng)求需要用戶登錄或者需要特定權(quán)限才能訪問(wèn),這時(shí)候便可以中間件中做過(guò)濾攔截。

下面三個(gè)方法中斷請(qǐng)求后,直接返回200,但響應(yīng)的body中不會(huì)有數(shù)據(jù):

func (c *Context) Abort()
func (c *Context) AbortWithError(code int, err error) *Error
func (c *Context) AbortWithStatus(code int)
func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{})        // 中斷后可以返回json數(shù)據(jù)

如果在中間件中調(diào)用gin.Context的Next()方法,則可以請(qǐng)求到達(dá)并完成業(yè)務(wù)處理后,再經(jīng)過(guò)中間件后置攔截處理:

func MyMiddleware(c *gin.Context){
    //請(qǐng)求前
    c.Next()
    //請(qǐng)求后
}

一 gin.Engine

Engine是框架的入口,是gin框架的核心,通過(guò)Engine對(duì)象來(lái)定義服務(wù)路由信息、組裝插件、運(yùn)行服務(wù)。不過(guò)Engine的本質(zhì)只是對(duì)內(nèi)置HTTP服務(wù)的包裝。

gin.Default() 函數(shù)會(huì)生成一個(gè)默認(rèn)的 Engine 對(duì)象,包含2個(gè)默認(rèn)常用插件

  • Logger:用于輸出請(qǐng)求日志
  • Recovery:用于確保單個(gè)請(qǐng)求發(fā)生 panic 時(shí)記錄異常堆棧日志,輸出統(tǒng)一的錯(cuò)誤響應(yīng)。
func Default() *Engine {
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

二 gin的路由

2.1 路由樹(shù)

在 Gin 框架中,路由規(guī)則被分成了最多 9 棵前綴樹(shù),每一個(gè) HTTP Method對(duì)應(yīng)一棵 前綴樹(shù) ,樹(shù)的節(jié)點(diǎn)按照 URL 中的 / 符號(hào)進(jìn)行層級(jí)劃分,URL 支持 :name 形式的名稱匹配,還支持 *subpath 形式的路徑通配符:

// 匹配單節(jié)點(diǎn) named
pattern = /book/:id
match /book/123
nomatch /book/123/10
nomatch /book/

// 匹配子節(jié)點(diǎn) catchAll mode
/book/*subpath
match /book/
match /book/123
match /book/123/10

如圖所示:

gin-03.jpeg

每個(gè)節(jié)點(diǎn)都會(huì)掛接若干請(qǐng)求處理函數(shù)構(gòu)成一個(gè)請(qǐng)求處理鏈 HandlersChain。當(dāng)一個(gè)請(qǐng)求到來(lái)時(shí),在這棵樹(shù)上找到請(qǐng)求 URL 對(duì)應(yīng)的節(jié)點(diǎn),拿到對(duì)應(yīng)的請(qǐng)求處理鏈來(lái)執(zhí)行就完成了請(qǐng)求的處理。

type Engine struct {
  ...
  trees methodTrees
  ...
}

type methodTrees []methodTree

type methodTree struct {
    method string
    root   *node  // 樹(shù)根
}

type node struct {
  path string // 當(dāng)前節(jié)點(diǎn)的路徑
  ...
  handlers HandlersChain // 請(qǐng)求處理鏈
  ...
}

type HandlerFunc func(*Context)

type HandlersChain []HandlerFunc

Engine 對(duì)象包含一個(gè) addRoute 方法用于添加 URL 請(qǐng)求處理器,它會(huì)將對(duì)應(yīng)的路徑和處理器掛接到相應(yīng)的請(qǐng)求樹(shù)中:

func (e *Engine) addRoute(method, path string, handlers HandlersChain)

2.2 路由組

RouterGroup 是對(duì)路由樹(shù)的包裝,所有的路由規(guī)則最終都是由它來(lái)進(jìn)行管理。Engine 結(jié)構(gòu)體繼承了 RouterGroup ,所以 Engine 直接具備了 RouterGroup 所有的路由管理功能,同時(shí) RouteGroup 對(duì)象里面還會(huì)包含一個(gè) Engine 的指針,這樣 Engine 和 RouteGroup 就成了「你中有我我中有你」的關(guān)系。

type Engine struct {
  RouterGroup
  ...
}

type RouterGroup struct {
  ...
  engine *Engine
  ...
}

RouterGroup 實(shí)現(xiàn)了 IRouter 接口,暴露了一系列路由方法,這些方法最終都是通過(guò)調(diào)用 Engine.addRoute 方法將請(qǐng)求處理器掛接到路由樹(shù)中。

  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 內(nèi)部有一個(gè)前綴路徑屬性,它會(huì)將所有的子路徑都加上這個(gè)前綴再放進(jìn)路由樹(shù)中。有了這個(gè)前綴路徑,就可以實(shí)現(xiàn) URL 分組功能。
Engine 對(duì)象內(nèi)嵌的 RouterGroup 對(duì)象的前綴路徑是 /,它表示根路徑。RouterGroup 支持分組嵌套,使用 Group 方法就可以讓分組下面再掛分組,依次類(lèi)推。

2.3 HTTP錯(cuò)誤

當(dāng) URL 請(qǐng)求對(duì)應(yīng)的路徑不能在路由樹(shù)里找到時(shí),就需要處理 404 NotFound 錯(cuò)誤。當(dāng) URL 的請(qǐng)求路徑可以在路由樹(shù)里找到,但是 Method 不匹配,就需要處理 405 MethodNotAllowed 錯(cuò)誤。Engine 對(duì)象為這兩個(gè)錯(cuò)誤提供了處理器注冊(cè)的入口。

func (engine *Engine) NoMethod(handlers ...HandlerFunc)
func (engine *Engine) NoRoute(handlers ...HandlerFunc)

異常處理器和普通處理器一樣,也需要和插件函數(shù)組合在一起形成一個(gè)調(diào)用鏈。如果沒(méi)有提供異常處理器,Gin 就會(huì)使用內(nèi)置的簡(jiǎn)易錯(cuò)誤處理器。

注意這兩個(gè)錯(cuò)誤處理器是定義在 Engine 全局對(duì)象上,而不是 RouterGroup。對(duì)于非 404 和 405 錯(cuò)誤,需要用戶自定義插件來(lái)處理。對(duì)于 panic 拋出來(lái)的異常需要也需要使用插件來(lái)處理。

2.4 HTTPS

Gin 不支持 HTTPS,官方建議是使用 Nginx 來(lái)轉(zhuǎn)發(fā) HTTPS 請(qǐng)求到 Gin。

三 gin.Context

gin.Context內(nèi)保存了請(qǐng)求的上下文信息,是所有請(qǐng)求處理器的入口參數(shù):

type HandlerFunc func(*Context)

type Context struct {
  ...
  Request *http.Request // 請(qǐng)求對(duì)象
  Writer ResponseWriter // 響應(yīng)對(duì)象
  Params Params // URL匹配參數(shù)
  ...
  Keys map[string]interface{} // 自定義上下文信息
  ...
}

Context 對(duì)象提供了非常豐富的方法用于獲取當(dāng)前請(qǐng)求的上下文信息,如果你需要獲取請(qǐng)求中的 URL 參數(shù)、Cookie、Header 都可以通過(guò) Context 對(duì)象來(lái)獲取。這一系列方法本質(zhì)上是對(duì) http.Request 對(duì)象的包裝:

// 獲取 URL 匹配參數(shù)  /book/:id
func (c *Context) Param(key string) string
// 獲取 URL 查詢參數(shù) /book?id=123&page=10
func (c *Context) Query(key string) string
// 獲取 POST 表單參數(shù)
func (c *Context) PostForm(key string) string
// 獲取上傳的文件對(duì)象
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
// 獲取請(qǐng)求Cookie
func (c *Context) Cookie(name string) (string, error) 
...

Context 對(duì)象提供了很多內(nèi)置的響應(yīng)形式,JSON、HTML、Protobuf 、MsgPack、Yaml 等。它會(huì)為每一種形式都單獨(dú)定制一個(gè)渲染器。通常這些內(nèi)置渲染器已經(jīng)足夠應(yīng)付絕大多數(shù)場(chǎng)景,如果你覺(jué)得不夠,還可以自定義渲染器。

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)
}

所有的渲染器最終還是需要調(diào)用內(nèi)置的 http.ResponseWriter(Context.Writer) 將響應(yīng)對(duì)象轉(zhuǎn)換成字節(jié)流寫(xiě)到套接字中。

type ResponseWriter interface {
 // 容納所有的響應(yīng)頭
 Header() Header
 // 寫(xiě)B(tài)ody
 Write([]byte) (int, error)
 // 寫(xiě)Header
 WriteHeader(statusCode int)
}

四 插件與請(qǐng)求鏈

gin的插件機(jī)制中,函數(shù)鏈的尾部是業(yè)務(wù)處理,前面部分是插件函數(shù)。在 Gin 中插件和業(yè)務(wù)處理函數(shù)形式是一樣的,都是 func(*Context)。當(dāng)我們定義路由時(shí),Gin 會(huì)將插件函數(shù)和業(yè)務(wù)處理函數(shù)合并在一起形成一個(gè)鏈條結(jié)構(gòu)。

type Context struct {
  ...
  index uint8 // 當(dāng)前的業(yè)務(wù)邏輯位于函數(shù)鏈的位置
  handlers HandlersChain // 函數(shù)鏈
  ...
}

// 挨個(gè)調(diào)用鏈條中的處理函數(shù)
func (c *Context) Next() {
    c.index++
    for s := int8(len(c.handlers)); c.index < s; c.index++ {
        c.handlers[c.index](c)
    }
}

所以在業(yè)務(wù)代碼中,一般一個(gè)處理函數(shù)時(shí),路由節(jié)點(diǎn)也需要掛載一個(gè)函數(shù)鏈條。

Gin 在接收到客戶端請(qǐng)求時(shí),找到相應(yīng)的處理鏈,構(gòu)造一個(gè) Context 對(duì)象,再調(diào)用它的 Next() 方法就正式進(jìn)入了請(qǐng)求處理的全流程。

gin-04.jpeg

一 請(qǐng)求流程梳理

首先從gin最開(kāi)始的創(chuàng)建engine對(duì)象部分開(kāi)始:

router := gin.Default()

該方法返回了Engine結(jié)構(gòu)體,常見(jiàn)屬性有:

type Engine struct {
    //路由組
    RouterGroup
    RedirectTrailingSlash bool
    RedirectFixedPath bool
    HandleMethodNotAllowed bool
    ForwardedByClientIP    bool
    AppEngine bool
    UseRawPath bool
    UnescapePathValues bool
    MaxMultipartMemory int64
    delims           render.Delims
    secureJsonPrefix string
    HTMLRender       render.HTMLRender
    FuncMap          template.FuncMap
    allNoRoute       HandlersChain
    allNoMethod      HandlersChain
    noRoute          HandlersChain
    noMethod         HandlersChain
    // 對(duì)象池 用來(lái)創(chuàng)建上下文context
    pool             sync.Pool
    //記錄路由方法的 比如GET POST 都會(huì)是數(shù)組中的一個(gè) 每個(gè)方法對(duì)應(yīng)一個(gè)基數(shù)樹(shù)的一個(gè)root的node
    trees            methodTrees
}

Default方法其實(shí)就是創(chuàng)建了該對(duì)象,并添加了一些默認(rèn)中間件:

func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

注意,這里Default方法內(nèi)部調(diào)用了New方法,該方法默認(rèn)添加了路由組"/"

func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
        RouterGroup: RouterGroup{
            Handlers: nil,
            basePath: "/",
            root:     true,
        },
        FuncMap:                template.FuncMap{},
        RedirectTrailingSlash:  true,
        RedirectFixedPath:      false,
        HandleMethodNotAllowed: false,
        ForwardedByClientIP:    true,
        AppEngine:              defaultAppEngine,
        UseRawPath:             false,
        UnescapePathValues:     true,
        MaxMultipartMemory:     defaultMultipartMemory,
        trees:                  make(methodTrees, 0, 9),
        delims:                 render.Delims{Left: "{{", Right: "}}"},
        secureJsonPrefix:       "while(1);",
    }
    engine.RouterGroup.engine = engine
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

context對(duì)象存儲(chǔ)了上下文信息,包括:engine指針、request對(duì)象,responsewriter對(duì)象等,context在請(qǐng)求一開(kāi)始就被創(chuàng)建,且貫穿整個(gè)執(zhí)行過(guò)程,包括中間件、路由等等:

type Context struct {
    writermem responseWriter
    Request   *http.Request
    Writer    ResponseWriter

    Params   Params
    handlers HandlersChain
    index    int8

    engine *Engine

    // Keys is a key/value pair exclusively for the context of each request.
    Keys map[string]interface{}

    // Errors is a list of errors attached to all the handlers/middlewares who used this context.
    Errors errorMsgs

    // Accepted defines a list of manually accepted formats for content negotiation.
    Accepted []string
}

接下來(lái)是Use方法:

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
    //調(diào)用routegroup的use方法
    engine.RouterGroup.Use(middleware...)
    engine.rebuild404Handlers()
    engine.rebuild405Handlers()
    return engine
}

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
    //為group的handlers添加中間件 
    group.Handlers = append(group.Handlers, middleware...)
    return group.returnObj()
}

最后到達(dá)最終路由,有GET,POST等多種方法,但是每個(gè)方法的處理方式都是相同的,即把group和傳入的handler合并,計(jì)算出路徑存入tree中等待客戶端調(diào)用:

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    //調(diào)用get方法
    return group.handle("GET", relativePath, handlers)
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    //計(jì)算路徑地址,比如group地址是 router.Group("/api")
    //結(jié)果為/api/test/ 就是最終計(jì)算出來(lái)的結(jié)果 使用path.join 方法拼接 其中加了一些判斷
    absolutePath := group.calculateAbsolutePath(relativePath)
    //把group中的handler和傳入的handler合并 
    handlers = group.combineHandlers(handlers)
    //把方法 路徑 和處理方法作為node 加入到基數(shù)樹(shù)種,基數(shù)樹(shù)在下次單獨(dú)學(xué)習(xí)分析
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
    finalSize := len(group.Handlers) + len(handlers)
    if finalSize >= int(abortIndex) {
        panic("too many handlers")
    }
    mergedHandlers := make(HandlersChain, finalSize)
    copy(mergedHandlers, group.Handlers)
    copy(mergedHandlers[len(group.Handlers):], handlers)
    return mergedHandlers
}

run方法則是啟動(dòng)服務(wù),在http包中會(huì)有一個(gè)for邏輯不停的監(jiān)聽(tīng)端口:

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
}

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

二 書(shū)寫(xiě)類(lèi)似源碼

一 Gin對(duì)象的構(gòu)建

Gin框架是基于golang官方的http包搭建起來(lái)的,http包最重要的實(shí)現(xiàn)是:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

利用該方法,以及參數(shù)中的Handler接口,實(shí)現(xiàn)Gin的Engine,Context:

package engine

import "net/http"

type Engine struct {

}

func (e *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {

}

context對(duì)象其實(shí)就是對(duì)ServeHTTP方法參數(shù)的封裝,因?yàn)檫@2個(gè)參數(shù)在web開(kāi)發(fā)中一個(gè)完整請(qǐng)求鏈中都會(huì)用到:

type Context struct {
    writermem   responseWriter
    Request     *http.Request
    Writer      ResponseWriter
}

type responseWriter struct {
    http.ResponseWriter
    size        int
    status      int
}

這里多了一個(gè)屬性 writermem,如果僅僅只從功能上考慮,這里有了Request、Writer已經(jīng)足夠使用了,但是框架需要應(yīng)對(duì)多變的返回?cái)?shù)據(jù)情況,必須對(duì)其進(jìn)行封裝,比如:

type ResponseWriter interface {

    http.ResponseWriter

    Pusher() http.Pusher

    Status() int
    Size() int
    WriteString(string) (int, error)
    Written() bool
    WriteHeaderNow()
}

這里對(duì)外暴露的是接口RespnserWriter,內(nèi)部的http.ResponseWriter實(shí)現(xiàn)原生的ResponseWriter接口,在reset()的時(shí)候進(jìn)行拷貝即可:

func (c *Context) reset() {
    c.Writer = &c.writermem
    c.Params = c.Params[0:0]
    c.handlers = nil
    c.index = -1
    c.Keys = nil
    c.Errors = c.Errors[0:0]
    c.Accepted = nil
}

這樣做能夠更好的符合面向接口編程的概念。

Context也可以通過(guò)對(duì)象池復(fù)用提升性能:

type Engine struct {
    pool             sync.Pool
}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    engine.handleHTTPRequest(c)

    engine.pool.Put(c)
}

緊接著就可以在Context的基礎(chǔ)上實(shí)現(xiàn)其大量的常用方法了:

func (c *Context) Param(key string) string{
    return ""
}

func (c *Context) Query(key string) string {
    return ""
}

func (c *Context) DefaultQuery(key, defaultValue string) string {
    return ""
}


func (c *Context) PostFormArray(key string) []string{
    return nil
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,540評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,028評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,801評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,223評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,442評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,800評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,996評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評(píng)論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,233評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,662評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,926評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,702評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評(píng)論 2 374

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