5. Middleware
...大约 3 分钟
1. 中间件 Middleware
中间件(middleware),是非业务的技术类组件。
Web 框架无法实现所有的业务功能,因此可以提供一个插件,允许用户自定义功能,嵌入到框架之中。
设计一个中间件有两个关键点:
- 插入点:若插入点过于底层,中间件逻辑将变得复杂;若插入点离用户接口太近,和用户直接在处理函数中直接调用相比优势不大
- 中间件的输入:输入的参数决定了系统的扩展能力,若参数太少,会降低可扩展性
1.1 设计
插入点和输入:
- 输入:
Context
对象 - 插入点:
Context
初始化之后
功能:
- 中间件应用于路由分组之上,子分组共享父母分组的中间件,每个分组可以使用独有的中间件
- 中间件可以在调用
HandlerFunc
之前或之后进行某些操作 - 多个中间件可以按次序调用
2. 实现
https://github.com/dreamjz/golang-notes/tree/main/books/7-days-golang/Gee/day5-middleware
项目结构如下:
DAY5-MIDDLEWARE
│ go.mod
│ go.work
│ main.go
│
└─gee
context.go
gee.go
go.mod
logger.go
router.go
router_group.go
router_test.go
trie.go
2.1 Context
type Context struct {
Writer http.ResponseWriter
Req *http.Request
// Request info
Path string
Method string
Params map[string]string // Dynamic route parameters
// Response info
StatusCode int
// Middleware
handlers []HandlerFunc
index int
}
func newContext(w http.ResponseWriter, req *http.Request) *Context {
return &Context{
Writer: w,
Req: req,
Path: req.URL.Path,
Method: req.Method,
index: -1,
}
}
handlers
:中间件/处理函数列表index
:当前执行的函数,初始值 -1
func (c *Context) Next() {
c.index++
n := len(c.handlers)
for c.index < n {
c.handlers[c.index](c)
c.index++
}
}
func (c *Context) Fail(code int, errMsg string) {
c.index = len(c.handlers)
c.JSON(code, H{"message": errMsg})
}
Next
: 调用下一个处理函数/中间件Fail
:修改c.index
跳过后序的中间件/处理函数的执行
2.2 gee
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var middlewares []HandlerFunc
for _, group := range engine.groups {
if strings.HasPrefix(req.URL.Path, group.prefix) {
middlewares = append(middlewares, group.middlewares...)
}
}
c := newContext(w, req)
c.handlers = middlewares
engine.router.handle(c)
}
- 获取分组的中间件列表
- 添加至当前的 Context 的
handlers
中 - 调用
engine.router.handle
2.3 router
func (r *router) handle(c *Context) {
n, params := r.getRoute(c.Method, c.Path)
var handler HandlerFunc
if n != nil {
c.Params = params
key := c.Method + "-" + n.pattern
handler = r.handlers[key]
} else {
handler = func(c *Context) {
c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
}
}
c.handlers = append(c.handlers, handler)
c.Next()
}
- 获取当前请求对应的处理函数,添加至
handlers
末尾 - 调用
c.Next()
,开始执行handlers
函数列表
2.4 logger
package gee
import (
"log"
"time"
)
func Logger() HandlerFunc {
return func(c *Context) {
start := time.Now()
c.Next()
log.Printf("[%d] %s in %vms", c.StatusCode, c.Req.RequestURI, time.Since(start).Milliseconds())
}
}
- 调用下一个函数前,获取当前时间
- 调用
c.Next()
等待其他的函数执行 - 统计执行时间
3. Demo
package main
import (
"gee"
"log"
"net/http"
)
func main() {
r := gee.New()
r.Use(gee.Logger())
r.GET("/index", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Index Page</h1>")
})
v1 := r.Group("/v1")
{
v1.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Welcome v1</h1>")
})
v1.GET("/hello", func(c *gee.Context) {
c.String(http.StatusOK, "Hello %s, you're at %s\n", c.Query("name"), c.Path)
})
}
v2 := r.Group("/v2")
{
v2.GET("/hello/:name", func(c *gee.Context) {
c.String(http.StatusOK, "Hello %s, you're at %s\n", c.Param("name"), c.Path)
})
m1 := func(c *gee.Context) {
log.Println("Before v2 middleware#1")
c.Next()
log.Println("After v2 middleware#1")
}
m2 := func(c *gee.Context) {
log.Println("Before v2 middleware#2")
c.Next()
log.Println("After v2 middleware#2")
}
v2.Use(m1, m2)
}
r.Run(":8000")
}
m1,m2
:仅作用在v2
分组上,并打印执行信息
$ curl http://localhost:8000/v2/hello/alice
Hello alice, you're at /v2/hello/alice
>>> log:
2023/10/10 11:00:53 Before v2 middleware#1
2023/10/10 11:00:53 Before v2 middleware#2
2023/10/10 11:00:53 After v2 middleware#2
2023/10/10 11:00:53 After v2 middleware#1
2023/10/10 11:00:53 [200] /v2/hello/alice in 0ms
Reference
Powered by Waline v2.15.2