## 使用使用中间件 Go程序的中间件是一个被广泛探索的领域。有许多用于处理中间件的包。 本节将从头开始创建中间件,并实现一个ApplyMiddleware函数将一堆中间件链接在一起。此外还会在请求上下文对象中设置值,并使用中间件检索它们。以演示如何将中间件逻辑与处理程序分离。 ### 实践 1. 建立 middleware.go: ``` package middleware import ( "log" "net/http" "time" ) // Middleware是所有的中间件函数都会返回的 type Middleware func(http.HandlerFunc) http.HandlerFunc // ApplyMiddleware 将应用所有中间件,最后一个参数将是用于上下文传递目的的外部包装 func ApplyMiddleware(h http.HandlerFunc, middleware ...Middleware) http.HandlerFunc { applied := h for _, m := range middleware { applied = m(applied) } return applied } // Logger 记录请求日志 这会通过SetID()传递id func Logger(l *log.Logger) Middleware { return func(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() l.Printf("started request to %s with id %s", r.URL, GetID(r.Context())) next(w, r) l.Printf("completed request to %s with id %s in %s", r.URL, GetID(r.Context()), time.Since(start)) } } } ``` 2. 建立 context.go: ``` package middleware import ( "context" "net/http" "strconv" ) // ContextID 是自定义类型 用于检索context type ContextID int // ID是我们定义的唯一ID const ID ContextID = 0 // SetID 使用自增唯一id更新context func SetID(start int64) Middleware { return func(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), ID, strconv.FormatInt(start, 10)) start++ r = r.WithContext(ctx) next(w, r) } } } // GetID 如果设置,则从上下文中获取ID,否则返回空字符串 func GetID(ctx context.Context) string { if val, ok := ctx.Value(ID).(string); ok { return val } return "" } ``` 3. 建立 handler.go: ``` package middleware import ( "net/http" ) func Handler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("success")) } ``` 4. 建立 main.go: ``` package main import ( "fmt" "log" "net/http" "os" "github.com/agtorre/go-cookbook/chapter7/middleware" ) func main() { // We apply from bottom up h := middleware.ApplyMiddleware( middleware.Handler, middleware.Logger(log.New(os.Stdout, "", 0)), middleware.SetID(100), ) http.HandleFunc("/", h) fmt.Println("Listening on port :3333") err := http.ListenAndServe(":3333", nil) panic(err) } ``` 5. 运行: ``` $ go run main.go Listening on port :3333 $curl "http://localhost:3333 success $curl "http://localhost:3333 success $curl "http://localhost:3333 success ``` 此外在运行main.go的命令行你还会看到: ``` Listening on port :3333 started request to / with id 100 completed request to / with id 100 in 52.284µs started request to / with id 101 completed request to / with id 101 in 40.273µs started request to / with id 102 ``` ### 说明 中间件可用于执行简单操作,例如日志记录,度量标准收集和分析。它还可用于在每个请求上动态填充变量。例如,可以用于从请求中收集X-Header以设置ID或生成ID,就像我们在示例中所做的那样。另一个ID策略可能是为每个请求生成一个UUID,这样我们可以轻松地将日志消息关联在一起,并在构建响应时跟踪请求。 使用上下文值时,考虑中间件的顺序很重要。通常,最好不要让中间件相互依赖。 例如,最好在日志记录中间件本身中生成UUID。 * * * * 学识浅薄,错误在所难免。欢迎在群中就本书提出修改意见,以飨后来者,长风拜谢。 Golang中国(211938256) beego实战(258969317) Go实践(386056972)