跟Gin一块搭建自己的web框架(四)
这一篇介绍Web框架中的中间件技术。
先来设想一个简单的场景,在处理每一个url的时候需要打印一条日志,说明收到了来自外界的请求。 考虑上一篇的情况,需要在两个handler里边都加上一个print的代码。对于少数的几个url的处理情况还可以介绍,但是一旦url非常多的时候,这个就变成了一个非常繁重并且容易出错的工作。
如果了解python,可能很容易的就想到在python中的处理方式:装饰器。只要定义一个打印日志的装饰器,任何加了这个装饰器的handler就能够实现打印日志的功能。
在Web框架中, 我们也要进行类似的处理,把非业务的需求和业务性的需求分离开来,类似这种打印日志的功能用中间件来实现。
先来看一下,如果没有用之前的基本框架,如何实现中间件的功能。
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there!")
}
func main() {
http.HandleFunc("/", hello)
log.Fatal(http.ListenAndServe(":8080", nil))
}
以上是原始代码, 然后我们添加一个打印日志的中间件
func logMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
// next handler
next.ServeHTTP(wr, r)
logger.Println("receive request")
})
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there!")
}
func main() {
http.HandleFunc("/", logMiddleware(http.HandlerFunc(hello)))
log.Fatal(http.ListenAndServe(":8080", nil))
}
其实原理也是跟python中的装饰器的原理一样,都是将原来的handler函数包裹在一个新函数中返回,在这个新函数中执行了一些非业务的代码,比如上边的打印日志。
其中最关键的步骤,是把hello函数,转成http.HandlerFunc,然后在middleware里边调用ServeHTTP。因为http.HandlerFunc()的参数是func (ResponseWriter, *Request),hello函数能够转成http.HandlerFunc()。 而http.HandlerFunc实现了http.Handler这个接口,所以能够在中间件中调用ServeHTTP函数。
类似的, 除了这个打印日志的中间件, 能够连环地添加多个中间件,比如
timeoutMiddleware(logMiddleware(http.HandlerFunc(hello)))
接下去再看上一篇中gin框架中如何用中间件。
首先我们要做的是把Context和RouterGroup中的HandlerFunc参数都改成slice格式的[]HandlerFunc,用来存放中间件。
我们定义一个user函数用来将中间件加入到上边说的函数链中
func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
group.Handlers = append(group.Handlers, middlewares...)
}
其实就是把新的中间件加入到了group的handler的slice中。
同时,在处理具体的GET请求的时候,接受的参数也需要从单一的HandlerFunc变成可变参数
func (group *RouterGroup) GET(path string, handlers ...HandlerFunc) {
group.Handle("GET", path, handlers)
}
他可以接受多个handler函数作为参数传入,同样,group的Handle函数也做相应的调整
func (group *RouterGroup) Handle(method, p string, handlers []HandlerFunc) {
p = path.Join(group.prefix, p)
handlers = group.combineHandlers(handlers)
group.engine.router.Handle(method, p, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
group.createContext(w, req, params, handlers).Next()
})
}
这边多了个group.combineHandlers的步骤用来将多个handler聚合,然后一块传入到router。combineHandlers的作用其实就是将当前group的Handler和新传入的handler进行整合。
整合完之后调用的时候,需要将slice中的hander依次取出来调用
func (c *Context) Next() {
c.index++
s := int8(len(c.handlers))
for ; c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
最终我们在main函数中如何使用gin的中间件呢,可以对路由的整体加中间件,比如
func main() {
r := gin.New()
r.Use(gin.Logger())
v1 := r.Group("/v1")
{
v1.GET("/login", v1IndexLoginfunc)
v1.GET("/submit", v1IndexSubmitfunc)
}
// Listen and server on 0.0.0.0:8080
r.Run(":8080")
}
也可以只针对某个group加中间件
func main() {
r := gin.New()
v1 := r.Group("/v1")
v1.Use(gin.Logger())
{
v1.GET("/login", v1IndexLoginfunc)
v1.GET("/submit", v1IndexSubmitfunc)
}
// Listen and server on 0.0.0.0:8080
r.Run(":8080")
}
最终的代码参见: https://github.com/harleylau/myGin/tree/master/v0.2
下一篇扩展上下文Context的功能。