gin是golang写的轻量级web框架,这里不介绍gin的基本使用方法,这里主要看下中间件(Middleware)的使用和gin的代码实现。
1. Middleware
中间件也叫拦截器或者过滤器,本质上都是在一个http请求被处理之前执行的一段代码,gin的中间件是一个函数,函数签名和gin的路由处理函数一致,都是func(*Context)
类型。
1.1 注册中间件
func startServer() {
port := os.Getenv("GIN_PORT")
if port == "" {
port = ":10080"
}
router := gin.New()
router.Use(gin.Logger(), gin.Recovery())
router.GET("/index", func(context *gin.Context) {
context.String(http.StatusOK, "ok")
})
router.Run(port)
}
func main() {
startServer()
}
说明:
- gin.New()返回一个没有注册中间件的gin.Engine对象,gin.Default()默认注册Logger()和Recovery()中间件
- 手动注册Logger()中间件并启动http服务器
1.1 gin.New()
gin.New()
创建一个gin.Egine实例router
,并初始化router
的匿名成员RouterGroup
,因为RouterGroup可以嵌套,所以router
的RouterGroup
就是最外层RouterGroup。初始化代码如下:
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedPlatform: defaultPlatform,
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);",
trustedProxies: []string{"0.0.0.0/0"},
trustedCIDRs: defaultTrustedCIDRs,
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
如下是RouterGroup类型定义:
type RouterGroup struct {
Handlers HandlersChain // 中间件组成的切片
basePath string // 路由组的基础路径
engine *Engine // 这个RouterGroup实例所属的Egine对象,可以看做父子关系中的父RouterGroup
root bool // 是否为最外层的RouterGroup,其实就是是否为最终创建的Egine对象
}
可知router
对象的RouterGroup
成员被初始化为:
Handlers为nil,意味着还未设置中间件函数
basePath为/
,就是根路径
engine为nil,因为它本身就是最外层的RouterGroup
root为true,因为它本身就是最外层的RouterGroup
1.2 注册中间件
实例中通过router.Use(gin.Logger(), gin.Recovery())
注册了两个中间件,Use()方法其实是RouterGroup类型的方法,因为Egine类型包含一个RouterGroup的匿名成员,所以Egine类型的对象也能调用Use()方法,看代码实现:
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
就是把参数中的中间件追加到router
对象的RouterGroup
成员,现在RouterGroup
成员的Handlers
成员包含两个中间件gin.Logger()
,gin.Recovery()
。
1.3 注册路由处理函数
router.GET("/index", func(context *gin.Context) {
context.String(http.StatusOK, "ok")
})
看具体代码实现:
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
代码比较简单:
- 计算绝对路径,其实就是拼接
RouterGroup
成员的basePath
成员和参数relativePath
- 把
RouterGroup
中的中间件和参数handlers合并成更大的切片,中间件在前- 把真实的路径和处理函数添加到路由树
值得注意的是拼接路径后gin会自动清理路径,比如上面的路径实际拼接是//index
,gin自动清理成/index
,/index
的处理函数最终也变成了3个,其中前2个来自中间件。
注意: gin用httprouter路由树实现,但是也有点区别,httprouter的handler是一个函数,gin中是一个函数切片
2. 处理http请求
gin的Egine类型实现了ServeHTTP(w, r)方法,看具体实现:
// ServeHTTP conforms to the http.Handler interface.
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)
}
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
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != "CONNECT" && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
}
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
}
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
gin的ServeHTTP(w,r)和httprouter中ServeHTTP(w, r)实现基本一致,只不过gin引入了一个Context类型用来包装:
- 创建一个Context对象,并把w, req成员设置到Context内部
- 根据http请求的方法找到路由树根节点,并通过path找对应的handlers,如果查找失败则返回错误
- 如果找到handlers则把handlers设置到Context内部成员,并执行Context的Next()方法,接下来看Next()方法实现
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
Next()方法其实就是遍历匹配到的handlers,并依次执行,所以访问/index
时每次实际上都执行三个函数,先执行两个中间件函数后执行路由处理函数。
有意思的是在中间件中调用Next()方法,就变成中间件递归调用Next()方法,下面是gin.Logger()中间件主要实现:
return func(c *Context) {
// Start timer
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// Process request
c.Next()
// Log only when path is not being skipped
if _, ok := skip[path]; !ok {
param := LogFormatterParams{
Request: c.Request,
isTerm: isTerm,
Keys: c.Keys,
}
// Stop timer
param.TimeStamp = time.Now()
param.Latency = param.TimeStamp.Sub(start)
param.ClientIP = c.ClientIP()
param.Method = c.Request.Method
param.StatusCode = c.Writer.Status()
param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
param.BodySize = c.Writer.Size()
if raw != "" {
path = path + "?" + raw
}
param.Path = path
fmt.Fprint(out, formatter(param))
}
}
在执行Next()函数时先执行中间件,所以如下代码会先执行:
// Start timer
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
然后进入递归调用Next(),这时会先执行gin.Recovery()中间件再执行/index
处理函数,这些执行完才回到gin.Logger()剩下的部门,所以中间件中c.Next()之前的代码比路由处理函数先执行,c.Next()之后的代码在路由处理函数执行完后才执行,所以gin.Logger()才能计算处理一个http请求的时间和获取http的状态码。下面一段代码能更好的演示多个中间件中调用Next()方法的执行顺序:
func LogMiddleware(context *gin.Context) {
fmt.Println("LogMiddleware start")
context.Next()
fmt.Println("LogMiddleware end")
}
func TestMiddleware(context *gin.Context) {
fmt.Println("TestMiddleware start")
context.Next()
fmt.Println("TestMiddleware end")
}
func startServer() {
port := os.Getenv("GIN_PORT")
if port == "" {
port = ":10080"
}
router := gin.New()
router.Use(LogMiddleware, TestMiddleware)
router.GET("/index", func(context *gin.Context) {
fmt.Println("process /index")
context.String(http.StatusOK, "ok")
})
router.Run(port)
}
func main() {
startServer()
}
访问/index
路径,结果如下:
2. BasicAuth()中间件
假如用户需要通过认证才能访问/account/submit
,/account/info
,则可以创建一个RouterGroup,并且给这个RouterGroup设置一个中间件去验证用户身份,gin提供BasicAuth()中间件来实现该功能。
var myAccounts = map[string]string {
"jordan": "23",
"kobe":"24",
"wade":"3",
}
func LogMiddleware(context *gin.Context) {
fmt.Println("LogMiddleware start")
context.Next()
fmt.Println("LogMiddleware end")
}
func TestMiddleware(context *gin.Context) {
fmt.Println("TestMiddleware start")
context.Next()
fmt.Println("TestMiddleware end")
}
func startServer() {
port := os.Getenv("GIN_PORT")
if port == "" {
port = ":10080"
}
router := gin.New()
router.Use(LogMiddleware, TestMiddleware)
group := router.Group("/account", gin.BasicAuth(myAccounts))
group.GET("/submit", func(context *gin.Context) {
context.String(http.StatusOK, "submit success")
})
group.GET("/info", func(context *gin.Context) {
context.String(http.StatusOK, "ok")
})
router.Run(port)
}
func main() {
startServer()
}
注意group
是从router
创建的,所以group
会包含router
的中间件,/account/submit
,/account/info
的处理函数有4个,依次为gin.Logger(), gin.Recovery(), gin.BasicAuth()和用户设置的handler。
设置BasicAuth()中间件,把传入的myAccounts中的成员当做user,password,并把user + ":" + password作base64转换保存起来:
func processAccounts(accounts Accounts) authPairs {
length := len(accounts)
assert1(length > 0, "Empty list of authorized credentials")
pairs := make(authPairs, 0, length)
for user, password := range accounts {
assert1(user != "", "User can not be empty")
value := authorizationHeader(user, password)
pairs = append(pairs, authPair{
value: value,
user: user,
})
}
return pairs
}
func authorizationHeader(user, password string) string {
base := user + ":" + password
return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
}
myAccounts转换后的结果如下:
"jordan": Basic d2FkZToz
"kobe": Basic am9yZGFuOjIz
"wade": Basic a29iZToyNA==
当对/account/submit
,/account/info
请求时,中间件判断http头中Authorization
字段的值是否能匹配myAccounts转换后的字段,如果能则通过,匹配不成功则返回401。