Go程序的中间件是一个被广泛探索的领域。有许多用于处理中间件的包。 本节将从头开始创建中间件,并实现一个ApplyMiddleware函数将一堆中间件链接在一起。此外还会在请求上下文对象中设置值,并使用中间件检索它们。以演示如何将中间件逻辑与处理程序分离。
实践
建立 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))
}
}
}
建立 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 ""
}
建立 handler.go:
package middleware
import (
"net/http"
)
func Handler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("success"))
}
建立 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)
}
运行:
$ 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。
最后编辑: kuteng 文档更新时间: 2021-01-03 15:03 作者:kuteng