判断Go网络错误应先用errors.As解包net.Error接口,再根据Timeout()和Temporary()方法判断超时或可重试性,而非字符串匹配;http.Client.Do的错误不全是net.Error,需分层处理。

怎么判断一个 Go 错误是不是网络错误
直接看它是否实现了 net.Error 接口。Go 标准库的网络操作(比如 net.Conn.Read、http.Client.Do)在底层出错时,多数会返回满足该接口的错误值,而不是裸的 error。
别用字符串匹配(如 strings.Contains(err.Error(), "timeout")),既脆弱又漏判——比如 syscall.ECONNREFUSED 在不同系统上错误信息不一致,且非英文环境可能直接崩掉。
- 正确做法:类型断言
if netErr, ok := err.(net.Error); ok { ... } - 注意:
net.Error是接口,不是具体类型,不能用errors.Is(err, &net.OpError{})直接比对 - 如果错误是包装过的(比如被
fmt.Errorf("read failed: %w", err)包了一层),要用errors.As(err, &netErr)向下解包
net.Error 里哪些字段真正有用
net.Error 只定义了三个方法:Error()、Timeout()、Temporary()。其中后两个才是关键判断依据,Error() 只是标准 error 接口要求,没额外信息价值。
-
Timeout()返回true表示这是超时错误(如context.DeadlineExceeded、io.ErrDeadlineExceeded或底层 socket timeout) -
Temporary()返回true表示错误可能是暂时的,可以重试(如连接被对端突然关闭、临时 DNS 解析失败) - 注意:
Timeout()为true时,Temporary()通常也为true,但反过来不成立;不要只看其中一个 - 常见误区:把
Temporary()当作“一定能重试成功”的信号——实际上像ENETUNREACH这类系统级错误也可能返回true,但重试毫无意义
为什么 http.Client.Do 返回的错误有时不是 net.Error
因为 HTTP 客户端错误来源不止网络层:URL 解析失败、TLS 握手失败、响应状态码 4xx/5xx、甚至 context 被 cancel,都会产生非 net.Error 的错误。
立即学习“go语言免费学习笔记(深入)”;
- 只有底层 TCP/UDP 连接、读写、DNS 查询等环节失败,才可能返回
net.Error - TLS 层错误(如证书校验失败)属于
crypto/tls,返回的是tls.RecordHeaderError或x509相关错误,不实现net.Error - 如果你在
http.Response.Body.Close()时拿到错误,它大概率是net.Error(因为底层 conn 关闭出问题),但http.Response本身的状态码错误(如 404)根本不算 error - 实操建议:对
http.Client.Do的错误,先用errors.As(err, &netErr)尝试解包;失败再检查是否是url.Error或context.Canceled等常见类型
自定义 net.Error 的坑:别忘了实现 Timeout() 和 Temporary()
如果你自己实现 net.Error(比如 mock 网络错误做测试),只实现 Error() 方法会导致调用方逻辑错乱——很多库(包括标准库的 net/http 重试逻辑)依赖这两个布尔方法做决策。
- 错误示范:
type myErr struct{ msg string }只实现了Error(),其他两个方法缺失 → 断言失败或 panic - 正确写法:必须显式实现全部三个方法,哪怕只是返回固定值(如超时错误就让
Timeout()返回true) - 容易忽略的一点:
Timeout()和Temporary()的语义有重叠但不等价,比如一个硬编码的 “connection refused” 错误,Temporary()应该返回false(连接被拒一般不是临时的),而Timeout()必须是false
net.Error,而是理解每个错误背后的真实原因和重试边界——同一段代码,在容器内网、NAT 后、IPv6-only 环境下,同一个 Temporary() 返回值背后的可恢复性可能完全不同。










