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