writeheader必须在write之前调用,否则状态码失效;推荐优先使用http.error,404/500需配合语义化响应体并记录日志;测试时须验证responserecorder.code。

Go HTTP handler 里 WriteHeader 必须在 Write 之前调用
很多新手一上来就 w.WriteHeader(404); w.Write([]byte("not found")),看起来没问题,但一旦前面有中间件、日志或 panic 恢复逻辑偷偷写了响应头(比如调用了 Write),再调用 WriteHeader 就完全失效——Go 的 http.ResponseWriter 是“一次写头、多次写体”,头一旦隐式写出(比如第一次 Write 时自动补 200),WriteHeader 就再也改不了状态码。
实操建议:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 始终把
WriteHeader放在所有Write或json.NewEncoder(w).Encode(...)调用之前 - 如果用了
http.Error,它内部已封装好WriteHeader+Write,可直接用,不用自己拆 - 避免在 defer 中调用
WriteHeader,除非你 100% 确保没写过 body(defer 很容易踩到“已写 body 后再设状态码”的坑)
404 和 500 不该只靠 WriteHeader,要配合语义化响应体
WriteHeader(404) 只是发个状态码,浏览器或客户端看到的可能是一片空白或默认错误页。真实项目里,404 应返回带提示的 HTML/JSON,500 更得记录日志并返回用户友好的错误(而非暴露堆栈)。
常见错误现象:
- 只写
w.WriteHeader(404),没写任何响应体 → 客户端收空响应,curl 看不到内容,前端 fetch 报“empty response” - 500 时直接
panic或裸抛错误 → 生产环境泄露敏感路径和变量名
实操建议:
- 404 场景:用
http.Error(w, "Not Found", http.StatusNotFound),简洁安全;自定义时先w.WriteHeader(http.StatusNotFound),再w.Write([]byte(`{"error":"not found"}`)) - 500 场景:务必捕获 error、打日志(如
log.Printf("server error: %v", err)),再返回统一 JSON:w.WriteHeader(http.StatusInternalServerError); json.NewEncoder(w).Encode(map[string]string{"error": "internal error"})
用 http.Error 替代手写 WriteHeader + Write 更稳妥
http.Error 是 Go 标准库提供的“安全快捷方式”,它自动处理状态码、Content-Type(text/plain)、响应体写入,并且兼容所有 ResponseWriter 实现(包括测试用的 httptest.ResponseRecorder)。
参数差异:
-
http.Error(w, msg, code):msg 是字符串,会原样写出,Content-Type 默认是text/plain; charset=utf-8 - 如果需要 JSON 响应,别硬套
http.Error,改用显式WriteHeader+json.Encoder,否则 Content-Type 不匹配 -
http.Error内部已检查是否已写 header,不会 panic,比裸调WriteHeader更健壮
测试时别忘了检查 ResponseRecorder.Code 而不是响应体
单元测试 handler 时,很多人只断言响应体内容,却漏掉状态码本身。比如 recorder.Body.String() 对了,但 recorder.Code 还是 200 —— 这说明 WriteHeader 根本没生效,或者被覆盖了。
实操建议:
- 用
httptest.NewRecorder(),调 handler 后立刻检查recorder.Code == http.StatusNotFound - 如果 handler 里用了
http.Error,它会正确设置recorder.Code;但如果误在Write后才调WriteHeader,recorder.Code仍为 0 或 200 - 测试 panic 场景时,确保 recover 逻辑里也设置了正确状态码,否则测试通过但线上 500 变 200
状态码这事看着简单,但真正在中间件、defer、panic 恢复、日志注入混在一起时,很容易悄无声息地失效。最稳的方式,是把状态码决策点收口到 handler 入口或统一错误处理层,别让它散落在多处。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










