Go 中如何进行 HTTP 请求

本文教程详细介绍在Go中使用net/http包进行HTTP请求,包括GET和POST请求、自定义头与超时、JSON处理,以及AI API和微服务集成。涵盖基础操作到生产最佳实践,帮助Go开发者掌握网络编程技巧。

阅读时长: 14 分钟
共 6648字
作者: eimoon.com

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调用和分布式系统。

先决条件

要跟随本教程,你需要:

进行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方法

  1. 检查熔断器是否打开,若是则直接返回错误。
  2. Marshal payload为JSON。
  3. 创建POST请求,附加体。
  4. 设置头:Content-Type和自定义X-Service-Name(用于追踪)。
  5. 执行请求,更新熔断器状态(失败记录失败,成功记录成功)。

此模式提升微服务间的可靠通信。

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函数

  1. 构建请求体,包括模型、max_tokens和消息。
  2. 创建POST请求到Anthropic端点。
  3. 设置头:Content-Type、x-api-key和anthropic-version。
  4. 执行请求,关闭响应体。
  5. 解析JSON,提取text内容。
  6. 处理错误,包括无内容响应。

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:

本教程属于DigitalOcean的Go编程系列

关于

关注我获取更多资讯

公众号
📢 公众号
个人号
💬 个人号
使用 Hugo 构建
主题 StackJimmy 设计