Go 中如何进行 HTTP 请求
引言
当程序需要与其他程序通信时,许多开发者会选择HTTP协议。Go语言的标准库(standard library)强大,其中net/http包不仅支持创建HTTP服务器,还能作为客户端发起HTTP请求。本教程将指导你创建一个程序,使用net/http包向HTTP服务器发送多种类型的请求。首先,你将使用默认的Go HTTP客户端进行GET请求,然后扩展程序以发送带有请求体的POST请求。最后,你将自定义POST请求,添加HTTP头和超时机制,以处理长时间运行的请求。
Go的net/http包(net/http)是处理HTTP操作的核心工具,支持从简单请求到复杂自定义的各种场景。
关键要点
通过本教程,你将掌握:
- 基础HTTP操作:使用Go的net/http包进行GET和POST请求。
- 请求自定义:添加头、超时和自定义HTTP客户端,用于生产环境。
- 错误处理:HTTP请求和响应的正确错误处理模式(Go错误处理)。
- JSON处理:解析JSON响应并在请求中发送JSON数据。
- AI集成:与AI API和微服务的现代集成模式。
- 性能优化:连接池、超时和重试机制。
- 生产最佳实践:安全考虑、日志记录和监控。
这些知识将帮助你构建可靠的HTTP客户端,适用于Web服务、API调用和分布式系统。
先决条件
要跟随本教程,你需要:
- 安装Go 1.16或更高版本。参考如何在Ubuntu 20.04上安装Go教程。
- 熟悉在Go中创建HTTP服务器,详见如何在Go中创建HTTP服务器。
- 了解goroutines和通道读取,更多信息见如何在Go中并发运行多个函数。
- 推荐了解HTTP请求的组成和发送方式(HTTP协议)。
进行GET请求
Go的net/http包提供了多种作为客户端使用的方式。你可以使用全局HTTP客户端的函数如http.Get快速发起带URL的GET请求,或者创建http.Request来自定义请求细节。本节将首先使用http.Get创建初始程序,然后更新为使用http.Request和默认HTTP客户端。
使用http.Get发起请求
在程序的第一版中,使用http.Get向程序内运行的HTTP服务器发起请求。http.Get无需额外设置,适合单次快速请求。
首先,创建projects目录并进入:
mkdir projects
cd projects
然后,创建httpclient目录并进入:
mkdir httpclient
cd httpclient
在httpclient目录下,使用nano(或你喜欢的编辑器)打开main.go文件:
nano main.go
在main.go中添加以下代码:
package main
import (
"errors"
"fmt"
"net/http"
"os"
"time"
)
const serverPort = 3333
这些导入包括程序所需的标准包。serverPort常量设为3333,用于HTTP服务器监听端口和客户端连接。
接下来,在main函数中设置goroutine启动HTTP服务器:
func main() {
go func() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("server: %s /\n", r.Method)
})
server := http.Server{
Addr: fmt.Sprintf(":%d", serverPort),
Handler: mux,
}
if err := server.ListenAndServe(); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
fmt.Printf("error running http server: %s\n", err)
}
}
}()
time.Sleep(100 * time.Millisecond)
}
服务器使用fmt.Printf打印传入请求信息,监听serverPort。time.Sleep允许服务器启动。
在main函数中,设置请求URL并使用http.Get发起请求:
requestURL := fmt.Sprintf("http://localhost:%d", serverPort)
res, err := http.Get(requestURL)
if err != nil {
fmt.Printf("error making http request: %s\n", err)
os.Exit(1)
}
fmt.Printf("client: got response!\n")
fmt.Printf("client: status code: %d\n", res.StatusCode)
}
http.Get使用默认HTTP客户端发送请求,返回http.Response或error。如果失败,打印错误并退出;成功则打印响应和状态码。
保存并关闭文件。运行程序:
go run main.go
输出示例:
server: GET /
client: got response!
client: status code: 200
服务器收到GET请求,客户端获得200状态码。http.Get适合快速请求,但http.Request提供更多自定义选项。
使用http.Request发起请求
相比http.Get,http.Request提供对方法、URL等的更多控制。本节切换到http.Request,便于后续自定义。
首先,更新服务器处理器返回模拟JSON响应,并导入io包:
import (
"io"
// 其他导入
)
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("server: %s /\n", r.Method)
fmt.Fprintf(w, `{"message": "hello!"}`)
})
更新请求代码,使用http.NewRequest和http.DefaultClient.Do:
requestURL := fmt.Sprintf("http://localhost:%d", serverPort)
req, err := http.NewRequest(http.MethodGet, requestURL, nil)
if err != nil {
fmt.Printf("client: could not create request: %s\n", err)
os.Exit(1)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("client: error making http request: %s\n", err)
os.Exit(1)
}
fmt.Printf("client: got response!\n")
fmt.Printf("client: status code: %d\n", res.StatusCode)
resBody, err := io.ReadAll(res.Body)
if err != nil {
fmt.Printf("client: could not read response body: %s\n", err)
os.Exit(1)
}
fmt.Printf("client: response body: %s\n", resBody)
http.NewRequest创建请求而不立即发送,便于修改。http.DefaultClient.Do发送请求。io.ReadAll读取响应体(io.ReadCloser)。
运行更新程序:
server: GET /
client: got response!
client: status code: 200
client: response body: {"message": "hello!"}
在复杂程序中,可使用encoding/json包处理JSON响应。更多JSON知识见Go中如何使用JSON。
高级HTTP客户端配置
生产环境中,需要更复杂的HTTP客户端配置。以下是带连接池、超时和重试的生产级客户端示例:
// 生产级HTTP客户端高级配置
func createProductionClient() *http.Client {
transport := &http.Transport{
MaxIdleConns: 100, // 最大空闲连接数
MaxIdleConnsPerHost: 10, // 每个主机最大空闲连接数
IdleConnTimeout: 90 * time.Second, // 空闲连接超时
DisableCompression: false, // 启用压缩
DisableKeepAlives: false, // 启用Keep-Alive
}
return &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 自定义重定向处理
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
},
}
}
此配置优化性能和可靠性。
发送POST请求
在REST API中,GET用于检索数据,POST用于发送数据到服务器(REST API)。POST请求将数据置于请求体中,几乎是GET的反向。
本节更新程序为POST请求,包含请求体,并增强服务器打印更多请求信息。
首先,添加新导入:
import (
"bytes"
"strings"
// 其他导入
)
更新服务器处理器打印查询字符串、头和请求体:
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("server: %s /\n", r.Method)
fmt.Printf("server: query id: %s\n", r.URL.Query().Get("id"))
fmt.Printf("server: content-type: %s\n", r.Header.Get("content-type"))
fmt.Printf("server: headers:\n")
for headerName, headerValue := range r.Header {
fmt.Printf("\t%s = %s\n", headerName, strings.Join(headerValue, ", "))
}
reqBody, err := io.ReadAll(r.Body)
if err != nil {
fmt.Printf("server: could not read request body: %s\n", err)
}
fmt.Printf("server: request body: %s\n", reqBody)
fmt.Fprintf(w, `{"message": "hello!"}`)
})
使用r.URL.Query().Get获取查询参数,r.Header.Get获取头,循环打印所有头,io.ReadAll读取体。
更新main函数的请求为POST,带体:
time.Sleep(100 * time.Millisecond)
jsonBody := []byte(`{"client_message": "hello, server!"}`)
bodyReader := bytes.NewReader(jsonBody)
requestURL := fmt.Sprintf("http://localhost:%d?id=1234", serverPort)
req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader)
jsonBody为[]byte(JSON编码输出类型)。bytes.NewReader包装为io.Reader,满足http.Request体要求。requestURL添加查询字符串id=1234。http.NewRequest使用http.MethodPost和bodyReader。
运行程序:
server: POST /
server: query id: 1234
server: content-type:
server: headers:
Accept-Encoding = gzip
User-Agent = Go-http-client/1.1
Content-Length = 36
server: request body: {"client_message": "hello, server!"}
client: got response!
client: status code: 200
client: response body: {"message": "hello!"}
服务器显示POST、查询id、头和体。头顺序可能因map迭代而异。
高级错误处理和重试逻辑
生产应用需要鲁棒错误处理。以下是带重试的增强版本:
// 带重试逻辑的增强错误处理
func makeRequestWithRetry(client *http.Client, req *http.Request, maxRetries int) (*http.Response, error) {
var lastErr error
for attempt := 0; attempt <= maxRetries; attempt++ {
resp, err := client.Do(req)
if err == nil {
// 检查HTTP错误状态码
if resp.StatusCode >= 400 {
resp.Body.Close()
if attempt < maxRetries && isRetryableError(resp.StatusCode) {
time.Sleep(time.Duration(attempt+1) * time.Second)
continue
}
return nil, fmt.Errorf("HTTP error: %d %s", resp.StatusCode, resp.Status)
}
return resp, nil
}
lastErr = err
// 检查是否可重试错误
if attempt < maxRetries && isRetryableNetworkError(err) {
time.Sleep(time.Duration(attempt+1) * time.Second)
continue
}
break
}
return nil, fmt.Errorf("request failed after %d attempts: %w", maxRetries+1, lastErr)
}
func isRetryableError(statusCode int) bool {
return statusCode == 429 || statusCode == 502 || statusCode == 503 || statusCode == 504
}
func isRetryableNetworkError(err error) bool {
if netErr, ok := err.(net.Error); ok {
return netErr.Temporary() || netErr.Timeout()
}
return false
}
此函数实现指数退避重试,处理网络和HTTP错误。
JSON处理最佳实践
在HTTP请求中使用JSON时,遵循结构化模式:
// 结构化JSON请求/响应处理
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type APIResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Data User `json:"data,omitempty"`
}
func createUser(client *http.Client, user User) (*APIResponse, error) {
jsonData, err := json.Marshal(user)
if err != nil {
return nil, fmt.Errorf("failed to marshal JSON: %w", err)
}
req, err := http.NewRequest("POST", "https://api.example.com/users", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
var apiResp APIResponse
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return &apiResp, nil
}
使用struct标签(json:"...")实现类型安全的JSON编解码。
自定义HTTP请求
HTTP头用于描述数据类型,如Content-Type头告知接收方解释体的方式(HTTP请求与响应)。过去,数据多为HTML;现在包括JSON、视频等。
本节更新程序设置Content-Type头,并使用自定义HTTP客户端。
更新main函数:
req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader)
if err != nil {
fmt.Printf("client: could not create request: %s\n", err)
os.Exit(1)
}
req.Header.Set("Content-Type", "application/json")
client := http.Client{
Timeout: 30 * time.Second,
}
res, err := client.Do(req)
if err != nil {
fmt.Printf("client: error making http request: %s\n", err)
os.Exit(1)
}
req.Header.Set设置application/json(媒体类型)。自定义client设置30秒超时,防止无限等待。默认http.DefaultClient无超时,可能耗尽资源。
运行程序:
server: POST /
server: query id: 1234
server: content-type: application/json
server: headers:
Accept-Encoding = gzip
User-Agent = Go-http-client/1.1
Content-Length = 36
Content-Type = application/json
server: request body: {"client_message": "hello, server!"}
client: got response!
client: status code: 200
client: response body: {"message": "hello!"}
Content-Type头允许同一路径支持JSON/XML API。
要测试超时,在服务器处理器添加time.Sleep(35 * time.Second):
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// ... 其他代码
fmt.Fprintf(w, `{"message": "hello!"}`)
time.Sleep(35 * time.Second)
})
运行时,30秒超时触发:
server: POST /
server: query id: 1234
server: content-type: application/json
server: headers:
Content-Type = application/json
Accept-Encoding = gzip
User-Agent = Go-http-client/1.1
Content-Length = 36
server: request body: {"client_message": "hello, server!"}
client: error making http request: Post "http://localhost:3333?id=1234": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
exit status 1
超时导致context deadline exceeded错误,使用自定义客户端避免挂起请求。
高级模式:Go中的微服务和AI集成
微服务通信模式
在现代微服务架构(微服务托管)中,HTTP客户端需与多个服务通信。以下是结构化示例:
// 服务发现和负载均衡
type ServiceClient struct {
BaseURL string
HTTPClient *http.Client
CircuitBreaker *CircuitBreaker
}
func (sc *ServiceClient) CallService(endpoint string, payload interface{}) (*http.Response, error) {
if sc.CircuitBreaker.IsOpen() {
return nil, errors.New("circuit breaker is open")
}
jsonData, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", sc.BaseURL+endpoint, bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Service-Name", "user-service")
resp, err := sc.HTTPClient.Do(req)
if err != nil {
sc.CircuitBreaker.RecordFailure()
return nil, err
}
sc.CircuitBreaker.RecordSuccess()
return resp, nil
}
ServiceClient结构体:包含BaseURL(服务基地址)、HTTPClient(配置超时等)和CircuitBreaker(熔断器模式,用于容错)。
CallService方法:
- 检查熔断器是否打开,若是则直接返回错误。
- Marshal payload为JSON。
- 创建POST请求,附加体。
- 设置头:Content-Type和自定义X-Service-Name(用于追踪)。
- 执行请求,更新熔断器状态(失败记录失败,成功记录成功)。
此模式提升微服务间的可靠通信。
AI API集成模式
现代应用常集成AI服务(AI服务)。以下是常见模式:
// OpenAI API集成
type OpenAIClient struct {
APIKey string
BaseURL string
HTTPClient *http.Client
}
func (c *OpenAIClient) GenerateText(prompt string) (string, error) {
requestBody := map[string]interface{}{
"model": "gpt-3.5-turbo",
"messages": []map[string]string{
{"role": "user", "content": prompt},
},
"max_tokens": 150,
}
jsonData, err := json.Marshal(requestBody)
if err != nil {
return "", err
}
req, err := http.NewRequest("POST", c.BaseURL+"/v1/chat/completions", bytes.NewBuffer(jsonData))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.APIKey)
resp, err := c.HTTPClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var response struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
} `json:"choices"`
}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return "", err
}
if len(response.Choices) == 0 {
return "", errors.New("no response from AI service")
}
return response.Choices[0].Message.Content, nil
}
此示例向OpenAI chat completions端点发送提示,解析响应提取内容。
Anthropic Claude API集成
// Anthropic Claude API集成
func (c *OpenAIClient) CallClaude(prompt string) (string, error) {
requestBody := map[string]interface{}{
"model": "claude-3-sonnet-20240229",
"max_tokens": 1000,
"messages": []map[string]string{
{"role": "user", "content": prompt},
},
}
jsonData, err := json.Marshal(requestBody)
if err != nil {
return "", err
}
req, err := http.NewRequest("POST", "https://api.anthropic.com/v1/messages", bytes.NewBuffer(jsonData))
if err != != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-api-key", c.APIKey)
req.Header.Set("anthropic-version", "2023-06-01")
resp, err := c.HTTPClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var response struct {
Content []struct {
Text string `json:"text"`
} `json:"content"`
}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return "", err
}
if len(response.Content) == 0 {
return "", errors.New("no response from Claude")
}
return response.Content[0].Text, nil
}
CallClaude函数:
- 构建请求体,包括模型、max_tokens和消息。
- 创建POST请求到Anthropic端点。
- 设置头:Content-Type、x-api-key和anthropic-version。
- 执行请求,关闭响应体。
- 解析JSON,提取text内容。
- 处理错误,包括无内容响应。
AI API的流式响应处理
对于支持流式的AI API,有效处理响应:
func (c *OpenAIClient) StreamGenerateText(prompt string, onChunk func(string)) error {
requestBody := map[string]interface{}{
"model": "gpt-3.5-turbo",
"messages": []map[string]string{
{"role": "user", "content": prompt},
},
"stream": true,
}
jsonData, err := json.Marshal(requestBody)
if err != nil {
return err
}
req, err := http.NewRequest("POST", c.BaseURL+"/v1/chat/completions", bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.APIKey)
resp, err := c.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "data: ") {
data := strings.TrimPrefix(line, "data: ")
if data == "[DONE]" {
break
}
var chunk struct {
Choices []struct {
Delta struct {
Content string `json:"content"`
} `json:"delta"`
} `json:"choices"`
}
if err := json.Unmarshal([]byte(data), &chunk); err == nil {
if len(chunk.Choices) > 0 && chunk.Choices[0].Delta.Content != "" {
onChunk(chunk.Choices[0].Delta.Content)
}
}
}
}
return scanner.Err()
}
使用bufio.Scanner逐行读取SSE(Server-Sent Events)格式的流式响应,解析data:块,调用onChunk回调处理每个内容块。
生产HTTP客户端的最佳实践
在Go中构建生产HTTP客户端时,遵循以下最佳实践,确保可靠、安全和可维护性。
| 最佳实践 | 描述 | 示例/提示 |
|---|---|---|
| 使用超时 | 始终设置超时,避免挂起请求。 | client.Timeout = 30 * time.Second |
| 安全TLS配置 | 强制最小TLS版本并验证证书。 | tls.Config{MinVersion: tls.VersionTLS12} |
| 连接池 | 重用连接提升效率和性能。 | 使用http.Transport的MaxIdleConns和IdleConnTimeout |
| 鲁棒错误处理 | 检查并处理所有错误,包括非2xx响应。 | if resp.StatusCode != http.StatusOK { ... } |
| 限制重定向 | 防止无限重定向循环。 | client.CheckRedirect = ... |
| 设置适当头 | 始终设置必要头(如Content-Type、认证)。 | req.Header.Set("Authorization", "Bearer ...") |
| 日志请求和响应 | 添加日志用于可观测性和调试。 | 使用日志中间件包装transport |
| 监控和仪表化 | 收集延迟、错误和吞吐量指标。 | 集成Prometheus等工具 |
| 使用Context | 传递context.Context支持取消和截止时间。 | req.WithContext(ctx) |
| 保护敏感数据 | 绝不日志秘密或敏感负载。 | 在日志中掩码或省略敏感字段 |
| 带退避的重试 | 为瞬时错误实现指数退避重试。 | 使用github.com/cenkalti/backoff库 |
| 尊重速率限制 | 处理HTTP 429并相应退避。 | 检查429 Too Many Requests并使用Retry-After头 |
这些实践使HTTP客户端生产就绪。
1. 安全考虑
生产HTTP客户端的安全最佳实践:
// 安全HTTP客户端配置
func createSecureClient() *http.Client {
transport := &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12, // 最小TLS 1.2
},
DisableCompression: false,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
}
return &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
}
// API认证的请求签名
func signRequest(req *http.Request, secret string) {
timestamp := time.Now().Unix()
req.Header.Set("X-Timestamp", strconv.FormatInt(timestamp, 10))
// 创建签名(简化示例)
signature := createSignature(req.Method, req.URL.Path, timestamp, secret)
req.Header.Set("X-Signature", signature)
}
强制TLS 1.2+,并使用签名增强认证。
2. 监控和可观测性
为HTTP客户端添加日志和指标:
// 带日志和指标的HTTP客户端
type LoggingTransport struct {
Transport http.RoundTripper
Logger *log.Logger
}
func (t *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
start := time.Now()
t.Logger.Printf("HTTP %s %s", req.Method, req.URL.String())
resp, err := t.Transport.RoundTrip(req)
duration := time.Since(start)
if err != nil {
t.Logger.Printf("HTTP %s %s failed after %v: %v", req.Method, req.URL.String(), duration, err)
} else {
t.Logger.Printf("HTTP %s %s completed in %v with status %d", req.Method, req.URL.String(), duration, resp.StatusCode)
}
return resp, err
}
LoggingTransport包装RoundTripper,记录请求方法、URL、持续时间和状态。
常见问题解答
1. 如何在Go中进行GET请求?
最简单方式使用http.Get:
resp, err := http.Get("https://api.example.com/data")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
更多控制,使用http.NewRequest和自定义客户端:
req, err := http.NewRequest("GET", "https://api.example.com/data", nil)
if err != nil {
log.Fatal(err)
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
2. 如何在Go中发送JSON数据进行POST请求?
Marshal数据并设置头:
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
user := User{Name: "John Doe", Email: "john@example.com"}
jsonData, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
req, err := http.NewRequest("POST", "https://api.example.com/users", bytes.NewBuffer(jsonData))
if err != nil {
log.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
3. 如何在Go中添加HTTP请求头?
使用Header.Set:
req, err := http.NewRequest("GET", "https://api.example.com/data", nil)
if err != nil {
log.Fatal(err)
}
// 添加自定义头
req.Header.Set("Authorization", "Bearer your-token")
req.Header.Set("User-Agent", "MyApp/1.0")
req.Header.Set("Accept", "application/json")
req.Header.Set("X-Custom-Header", "custom-value")
client := &http.Client{}
resp, err := client.Do(req)
4. 在Go中解析JSON响应的最佳方式是什么?
使用encoding/json和结构化类型:
type APIResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data struct {
ID int `json:"id"`
Name string `json:"name"`
} `json:"data"`
}
resp, err := http.Get("https://api.example.com/data")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
var apiResp APIResponse
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %s, Name: %s\n", apiResp.Status, apiResp.Data.Name)
动态JSON使用map[string]interface{}。
5. 如何为Go中的HTTP请求设置超时?
在HTTP客户端设置Timeout:
// 整个请求的全局超时
client := &http.Client{
Timeout: 30 * time.Second,
}
// 或使用context精细控制
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
log.Println("Request timed out")
} else {
log.Fatal(err)
}
}
6. 何时使用Resty而不是net/http?
使用Resty当需要:
- 简化API:流式链式API。
- 内置JSON处理:自动Marshal/Unmarshal。
- 中间件:内置日志、重试和错误处理。
- 少量样板:减少重复代码。
// net/http(更冗长)
req, err := http.NewRequest("POST", "https://api.example.com/users", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
// Resty(更简洁)
resp, err := resty.R().
SetHeader("Content-Type", "application/json").
SetBody(user).
Post("https://api.example.com/users")
保留net/http当需要最大控制、无外部依赖、更好性能或学习基础。
结论
本教程中,你创建了带HTTP服务器的程序,使用Go的net/http包发起请求。首先,使用http.Get进行GET请求。然后,使用http.NewRequest和http.DefaultClient.Do进行GET。接下来,更新为带bytes.NewReader体POST请求。最后,设置http.Request的Header.Set添加Content-Type,并创建自定义http.Client设置30秒超时。
net/http包还包括http.Post用于简单POST、CookieJar支持cookies等功能(HTTP 1.1 vs HTTP/2)。
相关教程
继续学习Go HTTP:
- 如何在Go中创建HTTP服务器 - 补充客户端知识的服务器创建。
- 如何在Go中使用Context - 掌握请求取消和超时。
- 使用Nginx在Ubuntu上部署Go Web应用 - 生产部署。
- Go中如何使用JSON - API通信的JSON深入。
本教程属于DigitalOcean的Go编程系列。
关于
关注我获取更多资讯