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)目路徑分配如下:
五 文件上傳
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)如圖:
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
如圖所示:
每個(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)求處理的全流程。
一 請(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
}