go 的 http.get 和 client.do 不会因 404/500 返回 error,err == nil 仅表示网络层成功;业务是否成功必须显式检查 resp.statuscode,推荐用 resp.statuscode >= 200 && resp.statuscode
Go 的
http.Get和client.Do不会因 404/500 返回 error这是最常被误判的点:只要网络通畅、服务端返回了 HTTP 响应(哪怕内容是 HTML 错误页或 JSON 错误体),
err就是nil。你拿到的*http.Response是有效的,resp.StatusCode才是唯一可信的业务成功信号。常见错误现象:代码只检查
if err != nil就认为请求“成功”,接着直接io.ReadAll(resp.Body)解析 JSON,结果把{"errcode":401,"errmsg":"token expired"}当成正常数据解,panic 或逻辑错乱。
- 必须显式判断
resp.StatusCode,推荐用resp.StatusCode >= 200 && resp.StatusCode ,比只比 <code>== http.StatusOK更健壮(兼容 201、204 等)- 如果状态码不在预期范围,不要跳过
resp.Body—— 它可能含关键错误信息,但需先读再关,否则连接复用会出问题defer resp.Body.Close()要写在检查状态码之前,避免 panic 时漏关非 200 响应体怎么安全读取?
io.ReadAll前必须限流服务端对 4xx/5xx 响应仍可能返回大体积 body(比如堆栈日志、HTML 错误页、冗长 debug 信息)。直接
io.ReadAll(resp.Body)可能 OOM,尤其在高并发场景下。正确做法不是“不读”,而是“可控地读”:
- 用
http.MaxBytesReader包一层resp.Body,例如限制最多读 1MB:io.ReadAll(http.MaxBytesReader(nil, resp.Body, 1- 若只需错误提示,可用
bufio.NewReader(resp.Body).ReadString('\n')读首行,或io.LimitReader(resp.Body, 4096)截断- 读完后仍要
resp.Body.Close()—— 即使读到 EOF 或 error,也要关,否则底层连接无法复用如何统一拦截并分类处理不同状态码?封装
checkResponse函数重复写
switch resp.StatusCode很容易漏 case 或处理不一致。建议抽成可复用函数,把“感知”和“响应”解耦:
- 函数签名类似:
func checkResponse(resp *http.Response) error- 内部按范围分类:2xx 返回 nil;4xx 返回带
ClientError类型的 error;5xx 返回ServerError并可触发重试逻辑- 对 401/403 这类认证相关状态码,可额外提取
resp.Header.Get("WWW-Authenticate")辅助诊断- 别在函数里直接
log.Fatal或 panic —— 让调用方决定是重试、降级还是上报为什么
resp.Body在非 200 时有时读不到内容?这不是 Go 的 bug,而是服务端行为差异:有些 API(如 RESTful 设计良好的接口)对 400/500 会返回结构化 JSON;有些 Web 服务器(如 nginx 默认配置)则返回 HTML 错误页;还有些直接返回空 body。
关键点在于:Go 客户端从不屏蔽非 200 的
Body,它始终可用 —— 读不到内容,是因为服务端根本没写。真正难的不是“怎么读状态码”,而是在状态码异常时,既要拿到足够诊断信息,又不能拖垮服务——Body 读多读少、关不关、重不重试,全得看你的 SLA 和下游容错能力。
- 用
curl -v或netcat对比请求头,确认 Go 客户端和服务端期望的 Accept 头是否一致(比如服务端只对Accept: application/json返回 JSON 错误体)- 检查服务端是否做了 “短路响应”:某些框架在 500 时跳过 middleware,导致错误体未生成
- Go 侧可加
req.Header.Set("Accept", "application/json")显式声明期望格式,提高错误体可读性
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!












