在Go语言使用net_http包编写HTTP客户端逻辑时,很多开发者会在处理请求响应时遇到nil指针解引用恐慌的问题,这类问题会直接导致程序崩溃,影响服务的稳定性。

问题触发场景
最常见的错误写法是直接获取响应后立刻操作响应体,没有先判断响应对象是否为nil。比如下面这段错误的示例代码:
package main
import (
"fmt"
"net/http"
)
func main() {
// 发起HTTP GET请求
resp, err := http.Get("https://ipipp.com/test")
// 只判断了err,没有判断resp是否为nil
if err != nil {
fmt.Println("请求出错:", err)
return
}
// 直接调用resp.Body,此时resp可能为nil就会触发panic
defer resp.Body.Close()
// 后续读取响应体逻辑
}
当请求过程中出现网络异常、DNS解析失败等情况时,http.Get返回的resp对象会是nil,此时直接调用resp.Body就会触发nil指针解引用的panic。
核心解决思路
要解决这个问题,核心是在操作响应体之前,先校验响应对象的有效性,同时规范错误处理的顺序。具体需要注意两个要点:
- 先判断请求返回的err是否为nil,再判断resp是否为nil,因为err不为nil时resp大概率也是nil
- 只有在确认resp不为nil之后,再调用
defer resp.Body.Close(),避免对nil对象调用方法
正确的实现示例
下面是符合规范的处理代码,能够避免nil指针解引用的问题:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, err := http.Get("https://ipipp.com/test")
// 第一步:先处理请求错误
if err != nil {
fmt.Println("请求失败:", err)
return
}
// 第二步:校验resp是否为nil,部分场景下err为nil但resp也可能为nil
if resp == nil {
fmt.Println("响应对象为nil")
return
}
// 第三步:确认resp有效后再注册关闭逻辑
defer resp.Body.Close()
// 读取响应体内容
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
fmt.Println("读取响应体失败:", readErr)
return
}
fmt.Println("响应内容:", string(body))
}
额外注意事项
除了响应体赋值时的nil判断,还有几个相关的细节需要注意:
响应体必须关闭
即使响应体为空,也需要调用Close方法关闭,否则会造成连接泄漏,长期运行会导致文件描述符耗尽。使用defer注册关闭逻辑是最稳妥的方式,但要确保注册时resp不为nil。
错误场景的覆盖
有些情况下,请求没有返回错误,但响应状态码是4xx或者5xx,此时resp依然不为nil,不需要额外判断nil,只需要根据状态码处理业务逻辑即可,这部分不会触发nil指针解引用的问题。
规范的处理顺序应该是:先处理err -> 再判断resp是否为nil -> 再注册defer关闭 -> 再处理响应体内容,这样就能完全避免HTTP响应体赋值时的nil指针解引用恐慌问题。