Go HTTP handler 中错误不可 panic,须转为对应HTTP状态码;net.DialContext超时需用errors.Is区分DeadlineExceeded与Canceled;io.ReadFull/ReadAtLeast必须显式检查io.ErrUnexpectedEOF;自定义net.Error应实现Timeout()和Temporary()。

Go HTTP handler 中的错误不能直接 panic
HTTP handler 函数签名是 func(http.ResponseWriter, *http.Request),它不返回 error,也不接受 recover 机制自动兜底。一旦在 handler 内部 panic,默认会触发 http.DefaultServeMux 的兜底逻辑:打印堆栈、返回 500,并可能暴露敏感路径信息。
正确做法是把错误转为 HTTP 状态码和响应体:
- 业务校验失败(如参数缺失)→
http.StatusBadRequest(400) - 资源未找到 →
http.StatusNotFound(404) - 权限不足 →
http.StatusForbidden(403)或http.StatusUnauthorized(401) -
后端服务不可用 →
http.StatusServiceUnavailable(503)
func myHandler(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
if id == "" {
http.Error(w, "missing 'id' parameter", http.StatusBadRequest)
return
}
// ... 处理逻辑
}
net.DialContext 超时错误要区分 timeout vs. cancelled
net.DialContext 返回的 error 可能是 context.DeadlineExceeded(超时)或 context.Canceled(主动取消),二者语义不同,影响重试策略和日志分级。
直接用 errors.Is(err, context.DeadlineExceeded) 判断超时;用 errors.Is(err, context.Canceled) 区分是否由上层主动中断。
立即学习“go语言免费学习笔记(深入)”;
- 超时通常需记录为 warn 级别,并考虑降级或重试
- Cancel 多数来自 client 断连或 request context cancel,一般不报警
- 避免用
strings.Contains(err.Error(), "timeout")—— 不可靠,且无法兼容自定义 net.Conn 实现
io.ReadFull 和 io.ReadAtLeast 的错误类型必须显式检查
这两个函数在读取不足时返回 io.ErrUnexpectedEOF,而不是 io.EOF。若与普通 io.Read 混淆处理,容易误判协议帧完整性。
例如解析固定头长的二进制协议时:
-
io.ReadFull(conn, header[:])失败 → 一定是连接提前关闭或数据截断,应关闭 conn 并记录 error -
err == io.EOF表示流正常结束,可安全退出 -
errors.Is(err, io.ErrUnexpectedEOF)表示协议违规,大概率是客户端 bug 或中间设备干扰
header := make([]byte, 8)
_, err := io.ReadFull(conn, header)
if err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) {
log.Warn("incomplete header received")
return
}
if errors.Is(err, io.EOF) {
log.Info("connection closed gracefully")
return
}
log.Error("read header failed", "err", err)
return
}
自定义 net.Error 需实现 Timeout() 和 Temporary() 方法
当封装底层网络调用(如 TLS 握手、DNS 解析)并返回自定义 error 时,若未实现 Timeout() 和 Temporary(),上层库(如 http.Transport)无法判断是否可重试。
典型后果:本该重试的临时 DNS 错误被当作永久失败,导致请求直接失败而非 fallback。
- 网络抖动、连接拒绝(ECONNREFUSED)、TLS handshake timeout → 应返回
Temporary() == true - 证书过期、SNI 不匹配、协议不支持 →
Temporary() == false - 所有超时类错误必须返回
Timeout() == true
Go 标准库中 net.OpError 已正确实现这两方法,优先复用而非自己造轮子。











