
在 go 的 net/http 中,标准 responsewriter 接口不提供直接判断是否已写入响应的方法;需通过封装自定义 responsewriter 并跟踪 writeheader/write 调用状态来实现安全检测。
在 go 的 net/http 中,标准 responsewriter 接口不提供直接判断是否已写入响应的方法;需通过封装自定义 responsewriter 并跟踪 writeheader/write 调用状态来实现安全检测。
Go 的 http.ResponseWriter 是一个接口,其设计遵循“写即提交”原则:一旦调用 WriteHeader() 或 Write()(且响应头尚未显式写入),底层 HTTP 连接便会立即发送状态行和响应头,后续再调用 WriteHeader() 就会触发 http: multiple response.WriteHeader calls 错误。遗憾的是,标准库未暴露 Written() 或类似方法——w.Header().Get("Status") 等方式无效,因为 Header() 返回的是待发送的 header map,而非已提交状态。
因此,可靠方案是构建一个轻量级包装器(wrapper),记录写入行为:
type doneWriter struct {
http.ResponseWriter
written bool
}
func (w *doneWriter) WriteHeader(statusCode int) {
w.written = true
w.ResponseWriter.WriteHeader(statusCode)
}
func (w *doneWriter) Write(b []byte) (int, error) {
w.written = true
return w.ResponseWriter.Write(b)
}
// 可选:添加安全检查方法
func (w *doneWriter) Written() bool {
return w.written
}
该包装器拦截 WriteHeader 和 Write 调用,在执行原操作前将 written 标志置为 true。使用时,将其注入中间件或处理器链:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
dw := &doneWriter{
ResponseWriter: w,
written: false,
}
next.ServeHTTP(dw, r)
// 处理“无人响应”的兜底逻辑
if !dw.written {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
})
}
⚠️ 注意事项:
- 不可重用:doneWriter 实例仅对单次请求有效,切勿跨请求复用;
- 避免嵌套包装:若多层中间件均需检测,应确保仅最外层包装器负责状态追踪,或统一使用同一实例;
- WriteHeader 与 Write 的等效性:即使未显式调用 WriteHeader(),首次 Write() 也会隐式触发 WriteHeader(http.StatusOK),因此二者均需拦截;
- 第三方替代方案:社区已有成熟封装如 github.com/flimzy/donewriter,提供 IsWritten() 方法及测试友好的实现,生产环境推荐直接引入。
综上,Go 的响应写入状态检测本质是“防御性封装”问题——没有银弹 API,但通过符合 http.ResponseWriter 接口语义的 wrapper,可稳健、清晰地实现响应控制流管理。










