在Golang Web项目开发中,中间件是处于请求处理链路中,能够对进入的请求进行预处理、对返回的响应进行后处理的组件,是实现请求拦截与通用逻辑复用的重要手段。合理设计中间件机制,可以让项目中的认证、日志、限流等通用逻辑不需要在每个接口中重复编写。

中间件的核心作用与请求拦截场景
中间件的核心价值是对请求处理流程进行扩展,常见的请求拦截场景包括以下几类:
- 身份认证拦截:校验请求头中的Token是否合法,不合法的请求直接返回无权限响应,不需要进入业务逻辑处理
- 请求日志记录:在请求进入时记录请求的URL、方法、耗时等信息,方便后续问题排查与接口监控
- 跨域处理:为响应添加跨域相关的Header,解决前端跨域请求的问题
- 参数校验与预处理:对请求参数进行统一校验,或者提前解析请求中的公共参数,减少业务层的处理负担
Golang中间件的实现原理
Golang标准库net/http中的Handler接口是中间件实现的基础,该接口只有一个ServeHTTP方法,定义如下:
// Handler接口定义
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
中间件本质上是一个接收http.Handler并返回新的http.Handler的函数,在新的ServeHTTP方法中,可以先执行拦截逻辑,再决定是否调用下一个Handler的处理方法。这种嵌套结构可以形成中间件链,让请求依次经过多个中间件的拦截处理。
基础中间件实现示例
下面是一个简单的日志中间件实现,用于记录每个请求的处理信息:
package main
import (
"fmt"
"log"
"net/http"
"time"
)
// 日志中间件,接收下一个Handler作为参数
func LoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 记录请求开始时间
start := time.Now()
// 执行下一个Handler的处理逻辑
next.ServeHTTP(w, r)
// 请求处理完成后记录日志
log.Printf("请求方法: %s, 请求路径: %s, 处理耗时: %v", r.Method, r.URL.Path, time.Since(start))
})
}
// 业务处理函数
func HelloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Golang Web")
}
func main() {
// 将业务Handler用日志中间件包裹
http.Handle("/", LoggerMiddleware(http.HandlerFunc(HelloHandler)))
// 启动服务监听8080端口
log.Println("服务启动在 :8080 端口")
http.ListenAndServe(":8080", nil)
}
运行上述代码后,访问http://127.0.0.1:8080,控制台会输出对应的请求日志,说明中间件已经成功拦截并处理了请求。
请求拦截与中断处理
在实际场景中,很多中间件需要在拦截到不符合要求的请求时直接中断处理,不需要调用后续的Handler。比如下面的认证中间件,当请求头中没有携带合法的Token时,直接返回401响应:
package main
import (
"fmt"
"net/http"
)
// 认证中间件,校验请求头中的Token
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 获取请求头中的Authorization字段
token := r.Header.Get("Authorization")
// 简单校验Token,实际项目中可以对接更复杂的认证逻辑
if token != "valid_token" {
// Token不合法,直接返回401响应,不调用后续Handler
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintf(w, "无权限访问,Token不合法")
return
}
// Token合法,调用下一个Handler
next.ServeHTTP(w, r)
})
}
// 业务处理函数
func UserHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "用户数据接口返回成功")
}
func main() {
// 组合中间件,先走认证中间件,再走业务Handler
handler := AuthMiddleware(http.HandlerFunc(UserHandler))
http.Handle("/user", handler)
http.ListenAndServe(":8080", nil)
}
启动服务后,如果请求http://127.0.0.1:8080/user时没有携带Authorization: valid_token的请求头,会直接返回无权限响应,说明请求被中间件拦截并中断了后续处理。
中间件链路的串联方式
当项目中有多个中间件时,需要将它们按顺序串联起来,形成完整的中间件链。常见的串联方式有两种,一种是手动嵌套,另一种是使用中间件管理工具。下面是手动嵌套的示例,将日志中间件和认证中间件组合使用:
package main
import (
"fmt"
"log"
"net/http"
"time"
)
// 日志中间件
func LoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("请求路径: %s, 耗时: %v", r.URL.Path, time.Since(start))
})
}
// 认证中间件
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "valid_token" {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintf(w, "Token不合法")
return
}
next.ServeHTTP(w, r)
})
}
// 业务Handler
func OrderHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "订单数据接口返回成功")
}
func main() {
// 手动嵌套中间件,先走日志中间件,再走认证中间件,最后走业务Handler
finalHandler := LoggerMiddleware(AuthMiddleware(http.HandlerFunc(OrderHandler)))
http.Handle("/order", finalHandler)
http.ListenAndServe(":8080", nil)
}
上述代码中,请求会先经过日志中间件,再经过认证中间件,最后进入业务逻辑。如果认证中间件拦截了请求,日志中间件仍然会记录这次请求的信息,符合中间件链的执行逻辑。
常见Web框架中的中间件使用
很多Golang Web框架都对中间件做了封装,使用起来更加便捷,比如Gin框架的中间件使用方式如下:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 自定义中间件
func CustomMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 请求前处理逻辑
fmt.Println("进入自定义中间件")
// 可以修改请求上下文中的数据
c.Set("middleware_key", "middleware_value")
// 调用下一个中间件或业务处理函数
c.Next()
// 请求后处理逻辑
fmt.Println("离开自定义中间件")
}
}
func main() {
r := gin.Default()
// 全局注册中间件,所有请求都会经过该中间件
r.Use(CustomMiddleware())
// 单个路由注册中间件
r.GET("/test", func(c *gin.Context) {
value, _ := c.Get("middleware_key")
c.JSON(http.StatusOK, gin.H{
"message": "请求成功",
"value": value,
})
})
r.Run(":8080")
}
Gin框架的中间件本质和原生实现逻辑一致,只是框架做了封装,让开发者可以更方便地注册和管理中间件,不需要手动嵌套Handler。
中间件使用注意事项
- 中间件的执行顺序和注册顺序一致,需要根据业务优先级合理安排中间件的注册顺序,比如认证中间件一般需要放在业务逻辑之前,日志中间件可以放在最外层
- 在中间件中如果需要终止请求处理,一定要记得调用
return,避免后续逻辑仍然执行 - 不要在中间件中做过于耗时的操作,避免影响整个请求的处理性能
- 中间件中如果需要传递数据到后续的Handler,可以使用请求上下文(如
context.Context或者Gin的gin.Context)来存储和传递数据