在Golang的Web开发中,中间件是处理请求前后通用逻辑的重要组件,比如请求日志打印、权限校验、跨域处理等场景都可以通过中间件实现,避免在每个接口处理函数中重复编写相同代码。

Golang中间件的基本原理
Golang的Web中间件本质上是一个函数,它接收一个http.Handler,返回一个新的http.Handler,在新的处理逻辑中可以先执行自定义的前置操作,再调用原Handler处理请求,最后执行后置操作。这种包装模式让中间件可以灵活叠加,形成中间件链。
基础中间件结构
标准库的http.Handler接口只有一个ServeHTTP方法,我们的中间件需要满足这个接口才能被正常使用,下面是基础的中间件定义形式:
// 定义中间件类型,接收一个Handler返回一个新的Handler
type Middleware func(http.Handler) http.Handler
// 示例:简单的日志中间件
func LoggingMiddleware(next http.Handler) http.Handler {
// 返回实现了ServeHTTP方法的对象
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 前置操作:打印请求信息
start := time.Now()
log.Printf("请求开始: 方法=%s, 路径=%s", r.Method, r.URL.Path)
// 调用下一个处理器
next.ServeHTTP(w, r)
// 后置操作:打印请求耗时
log.Printf("请求结束: 耗时=%v", time.Since(start))
})
}
实现中间件链
当有多个中间件需要依次执行时,我们需要将它们串联成中间件链,保证请求按照中间件的添加顺序依次经过每个中间件的处理。
中间件链的构造方式
我们可以通过一个函数来依次包装所有中间件,把中间件从外到内嵌套,请求会先经过第一个中间件的前置逻辑,再经过第二个,直到到达最终的接口处理函数,之后再反向执行各个中间件的后置逻辑。
// 构造中间件链,middlewares是中间件列表,handler是最终的处理器
func Chain(middlewares []Middleware, handler http.Handler) http.Handler {
// 从最后一个中间件开始包装,保证执行顺序正确
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
使用示例
下面是一个完整的示例,包含两个中间件和一个最终的接口处理函数,注册到路由中使用:
package main
import (
"log"
"net/http"
"time"
)
// 中间件类型定义
type Middleware func(http.Handler) http.Handler
// 日志中间件
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("请求开始: 方法=%s, 路径=%s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("请求结束: 耗时=%v", time.Since(start))
})
}
// 请求拦截中间件:校验请求头中是否包含Authorization字段
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 前置拦截:检查权限
if r.Header.Get("Authorization") == "" {
http.Error(w, "缺少权限凭证", http.StatusUnauthorized)
return
}
// 权限通过后调用下一个处理器
next.ServeHTTP(w, r)
})
}
// 构造中间件链的函数
func Chain(middlewares []Middleware, handler http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
// 最终的接口处理函数
func HelloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Golang 中间件示例"))
}
func main() {
// 定义中间件列表
middlewares := []Middleware{
LoggingMiddleware,
AuthMiddleware,
}
// 构造最终的处理器,包含中间件链和接口处理函数
finalHandler := Chain(middlewares, http.HandlerFunc(HelloHandler))
// 注册路由
http.Handle("/hello", finalHandler)
// 启动服务
log.Println("服务启动在 :8080 端口")
http.ListenAndServe(":8080", nil)
}
请求拦截的常见场景
中间件的请求拦截能力可以用于很多实际场景,除了上面的权限校验,还可以用于跨域处理、请求参数校验、限流等。
跨域中间件示例
下面是处理跨域请求的中间件示例,在请求到达接口前设置对应的响应头:
// 跨域中间件
func CorsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 设置跨域响应头
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// 如果是预检请求,直接返回成功
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
// 非预检请求继续执行下一个处理器
next.ServeHTTP(w, r)
})
}
注意事项
- 中间件的执行顺序和添加顺序相关,第一个添加的中间件会先执行前置逻辑,最后执行后置逻辑。
- 如果中间件在调用
next.ServeHTTP之前就返回响应,后续的中间件和接口处理函数都不会被执行,这是实现拦截的核心逻辑。 - 如果需要在中间件和接口处理函数之间传递数据,可以使用
context.Context来存储和获取数据。
总结
Golang中实现Web中间件的核心是利用函数包装http.Handler,通过嵌套的方式构造中间件链。这种方式没有复杂的依赖,只需要标准库就可以实现,同时灵活性很高,可以根据需求添加任意多个中间件。掌握中间件的实现和使用,可以大幅提升Web服务的代码复用率和可维护性。