在Golang的网络编程场景中,HTTP客户端的使用频率非常高,无论是调用第三方接口还是内部服务通信,都需要通过HTTP客户端完成请求交互。但请求过程中可能出现各类异常,做好错误处理才能保证程序稳定运行。

Golang HTTP客户端常见错误类型
在实际开发中,HTTP客户端请求可能出现的错误主要分为以下几类:
- 请求构建错误:比如URL格式不合法、请求参数设置错误等,这类错误会在创建请求阶段就抛出。
- 网络传输错误:包括DNS解析失败、连接超时、服务端无响应、网络中断等,发生在请求发送和响应接收的过程中。
- 响应状态码异常:服务端返回4xx、5xx等非成功状态码,虽然请求本身传输完成,但业务上属于异常场景。
- 响应处理错误:比如响应体读取失败、响应体解析错误(如JSON格式不符合预期)等。
基础HTTP请求的错误捕获
Golang标准库的net/http包提供了http.Client类型用于发送HTTP请求,所有请求操作返回的错误都需要优先处理。下面是一个基础的GET请求错误处理示例:
package main
import (
"fmt"
"io"
"net/http"
"time"
)
func main() {
// 创建自定义HTTP客户端,设置超时时间
client := &http.Client{
Timeout: 10 * time.Second,
}
// 构建请求,这里如果URL格式错误会返回err
req, err := http.NewRequest("GET", "https://ipipp.com/api/test", nil)
if err != nil {
fmt.Printf("构建请求失败: %vn", err)
return
}
// 发送请求,网络异常等情况会在这里返回err
resp, err := client.Do(req)
if err != nil {
fmt.Printf("发送请求失败: %vn", err)
return
}
// 必须手动关闭响应体,避免资源泄漏
defer resp.Body.Close()
// 检查响应状态码
if resp.StatusCode != http.StatusOK {
fmt.Printf("请求返回异常状态码: %dn", resp.StatusCode)
return
}
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("读取响应体失败: %vn", err)
return
}
fmt.Printf("请求成功,响应内容: %sn", body)
}
细化不同场景的异常处理
超时错误的单独处理
当请求超时时,我们可以通过类型断言判断错误类型,给出更明确的提示:
package main
import (
"fmt"
"net"
"net/http"
"time"
)
func sendRequestWithTimeoutHandle() {
client := &http.Client{
Timeout: 3 * time.Second,
}
resp, err := client.Get("https://ipipp.com/api/timeout_test")
if err != nil {
// 判断是否为超时错误
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
fmt.Println("请求超时,请稍后重试")
return
}
// 其他网络错误
fmt.Printf("请求发生网络错误: %vn", err)
return
}
defer resp.Body.Close()
fmt.Println("请求成功")
}
响应体解析错误处理
如果响应内容是JSON格式,解析失败也需要做对应处理:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
type ApiResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data string `json:"data"`
}
func parseResponse() {
client := &http.Client{}
resp, err := client.Get("https://ipipp.com/api/json_test")
if err != nil {
fmt.Printf("请求失败: %vn", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("读取响应体失败: %vn", err)
return
}
// 解析JSON
var result ApiResponse
err = json.Unmarshal(body, &result)
if err != nil {
fmt.Printf("JSON解析失败: %v,原始响应内容: %sn", err, body)
return
}
fmt.Printf("解析结果: code=%d, message=%sn", result.Code, result.Message)
}
封装统一的错误处理函数
为了避免重复编写错误处理代码,可以把通用的错误处理逻辑封装成函数,提升代码复用性:
package main
import (
"fmt"
"io"
"net/http"
)
// 统一处理HTTP请求错误,返回响应体和最终错误
func doRequest(client *http.Client, req *http.Request) ([]byte, error) {
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("请求发送失败: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("请求返回异常状态码: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("响应体读取失败: %w", err)
}
return body, nil
}
func main() {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://ipipp.com/api/test", nil)
if err != nil {
fmt.Printf("初始化请求失败: %vn", err)
return
}
body, err := doRequest(client, req)
if err != nil {
fmt.Printf("请求处理失败: %vn", err)
return
}
fmt.Printf("请求成功,响应内容: %sn", body)
}
错误处理的最佳实践
- 始终为
http.Client设置合理的超时时间,避免请求无限阻塞。 - 所有响应体都必须调用
Close()方法关闭,建议使用defer语句保证执行。 - 不要忽略任何返回的错误,即使你认为某个请求不会出错,也要做基础的错误捕获。
- 对于业务自定义的异常状态码,建议在错误处理中做更细化的分类提示。
- 错误日志要包含足够的上下文信息,比如请求的URL、请求参数、错误信息,方便后续排查问题。