#REST 客户端
net/http 提供的 http.Client 是调用第三方 API 的标准工具。生产代码应始终使用共享的 Client实例(内置连接池复用 TCP/TLS 连接),并显式配置超时。
#基本请求
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
func main() {
// 共享 Client:全局复用连接池,务必设置超时
client := &http.Client{
Timeout: 10 * time.Second, // 覆盖连接、发送、响应全过程
}
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close() // 必须关闭,否则连接无法复用
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("读取响应失败:", err)
return
}
fmt.Println("状态码:", resp.StatusCode)
fmt.Printf("响应前 100 字节: %.100s\n", body)
}Warning
两个最常见错误:
- 忘记
defer resp.Body.Close():连接无法回到连接池,高并发下耗尽文件描述符。 - 未设置
Client.Timeout:默认零值表示永不超时,一个慢端点即可拖垮调用方。
即使不读响应体也应完整读取并关闭,否则连接同样无法复用:io.Copy(io.Discard, resp.Body)。
#发送 JSON 与自定义请求
POST、PUT 等需要构造 http.Request:
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
type CreateOrderReq struct {
Item string `json:"item"`
Price float64 `json:"price"`
}
type CreateOrderResp struct {
OrderID string `json:"order_id"`
}
func createOrder(ctx context.Context, client *http.Client, req CreateOrderReq) (*CreateOrderResp, error) {
payload, err := json.Marshal(req)
if err != nil {
return nil, err
}
r, err := http.NewRequestWithContext(ctx, http.MethodPost,
"https://api.example.com/orders", bytes.NewReader(payload))
if err != nil {
return nil, err
}
r.Header.Set("Content-Type", "application/json")
r.Header.Set("Authorization", "Bearer "+os.Getenv("API_TOKEN")) // 敏感信息走环境变量
resp, err := client.Do(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("非预期状态码 %d: %s", resp.StatusCode, body)
}
var out CreateOrderResp
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return nil, err
}
return &out, nil
}
func main() {
client := &http.Client{Timeout: 5 * time.Second}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
order, err := createOrder(ctx, client, CreateOrderReq{Item: "keyboard", Price: 299})
if err != nil {
fmt.Println("下单失败:", err)
return
}
fmt.Println("订单号:", order.OrderID)
}示例需 import "os"。
#查询参数与重试
package main
import (
"fmt"
"net/http"
"net/url"
"time"
)
func search(client *http.Client, keyword string) error {
// url.Values 自动处理转义与编码
q := url.Values{}
q.Set("q", keyword)
q.Set("page", "1")
resp, err := client.Get("https://httpbin.org/get?" + q.Encode())
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Println(resp.StatusCode)
return nil
}
func main() {
client := &http.Client{Timeout: 10 * time.Second}
_ = search(client, "golang tutorial")
}对幂等 GET 请求可增加简单重试(指数退避):
func getWithRetry(client *http.Client, url string, attempts int) (*http.Response, error) {
var lastErr error
for i := 0; i < attempts; i++ {
resp, err := client.Get(url)
if err == nil && resp.StatusCode < 500 {
return resp, nil // 成功或非服务端错误:直接返回
}
if resp != nil {
resp.Body.Close()
}
lastErr = err
time.Sleep(time.Duration(1<<i) * 100 * time.Millisecond) // 100ms、200ms、400ms...
}
return nil, lastErr
}#并发请求
结合并发编程章节的能力,可并行调用多个接口:
package main
import (
"fmt"
"io"
"net/http"
"sync"
"time"
)
func fetchAll(urls []string) map[string]int {
client := &http.Client{Timeout: 5 * time.Second}
var mu sync.Mutex
status := make(map[string]int, len(urls))
var wg sync.WaitGroup
for _, u := range urls {
wg.Add(1)
go func(link string) {
defer wg.Done()
resp, err := client.Get(link)
if err != nil {
return
}
defer resp.Body.Close()
_, _ = io.Copy(io.Discard, resp.Body)
mu.Lock() // map 并发写需要加锁
status[link] = resp.StatusCode
mu.Unlock()
}(u)
}
wg.Wait()
return status
}
func main() {
urls := []string{
"https://httpbin.org/status/200",
"https://httpbin.org/status/404",
}
for u, code := range fetchAll(urls) {
fmt.Println(u, code)
}
}#小结
- Client 必须共享且必须设置超时;响应体必须读取并关闭。
- 自定义请求配合
NewRequestWithContext实现取消与超时传递。 - 并发请求遵循 goroutine + 锁/通道的标准模式。