Go如何在中间件中处理错误_Go Web错误处理流程说明

10次阅读

必须在中间件最外层用defer+recover捕获panic,记录并返回500错误Error应通过context传递由统一错误处理器响应,避免中间件直接写响应;禁用log.Fatal/os.Exit以防进程退出。

Go如何在中间件中处理错误_Go Web错误处理流程说明

中间件里 panic 了怎么办

gohttp 中间件本身不捕获 panic,一旦 panic 发生,整个请求协程会终止,连接可能被意外关闭,日志也不一定留下痕迹。这不是“错误处理”,是服务不稳定源。

必须在中间件最外层加 recover(),且只应在 HTTP 请求生命周期内做——不能在 goroutine 或定时任务里盲目 recover。

  • recover 必须紧跟在 defer 后,且 defer 必须在 handler 执行前注册
  • recover 只对当前 goroutine 有效,不要试图跨 goroutine 捕获
  • recover 后应返回标准 HTTP 错误(如 500),并记录 panic 堆(用 debug.PrintStack()log.printf("%+v", err)
func Recovery(next http.Handler) http.Handler { 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 		defer func() { 			if err := recover(); err != nil { 				http.Error(w, "Internal Server Error", http.StatusInternalServerError) 				log.Printf("PANIC in %s %s: %+v", r.Method, r.URL.Path, err) 				debug.PrintStack() 			} 		}() 		next.ServeHTTP(w, r) 	}) }

如何把 error 转成 HTTP 响应

中间件不该直接调用 http.Error() 或写响应体,而应把错误“传递下去”,由统一的错误处理器收口。否则各中间件各自写状态码、写 body,容易冲突或遗漏 Content-Type。

推荐用自定义 error 类型 + context 传递,例如:

  • 定义 type appError Struct { Code int; Message String; Err error }
  • 在中间件中检测业务逻辑返回的 error,如果是 *AppError,就设置 ctx = context.WithValue(r.Context(), appErrorKey, err)
  • 最外层中间件检查 ctx 中是否有 AppError,有则统一写响应
func ErrorHandler(next http.Handler) http.Handler { 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 		next.ServeHTTP(w, r) 		if err, ok := r.Context().Value(appErrorKey).(*AppError); ok { 			w.Header().Set("Content-Type", "application/json") 			w.WriteHeader(err.Code) 			json.NewEncoder(w).Encode(map[string]string{"error": err.Message}) 		} 	}) }

中间件链中 error 传播的常见陷阱

很多开发者在中间件里调用 next.ServeHTTP() 后继续执行后续代码,却忽略了:如果下游 handler 已经写了响应头和 body,再写就会 panic(http: multiple response.WriteHeader calls)。

更隐蔽的问题是:你认为“出错就 return”,但没考虑中间件本身可能被嵌套多层,return 只退出当前函数,不会中断整个链。

  • 不要在 next.ServeHTTP() 后写响应逻辑,除非你明确知道下游没写过
  • 避免用 “if err != nil { return }” 风格跳过后续逻辑;改用 “if err != nil { …; return }” 并确保所有分支都终止
  • 若需提前终止链(如鉴权失败),应直接写响应并 return,不要依赖下游“不执行”

为什么不要在中间件里用 log.Fatal 或 os.Exit

log.Fatalos.Exit 会终止整个进程,不是单个请求。哪怕只在一个请求里触发,也会干掉所有正在处理的连接、未 flush 的日志、后台 goroutine。

真实场景中,这类调用往往藏在第三方库的“兜底错误处理”里,比如某 SDK 遇到配置缺失就 log.Fatal("missing key") —— 这类代码必须包装或替换。

  • 所有中间件内出现的错误,都应降级为 HTTP 响应 + 日志记录
  • 启动期配置错误可以 log.Fatal,但运行时请求期绝对不行
  • go vetstaticcheck 扫描项目,查 log.Fatal / os.Exit 是否出现在 handler 或中间件函数内

实际最难的不是写 recover,而是判断一个 error 到底该透传、该转成 400、还是该记日志后忽略。这取决于它是否影响语义、是否可重试、是否暴露敏感信息——这些没法靠中间件自动决定,得靠每个 handler 自己标注清楚。

text=ZqhQzanResources