网络编程

36.1 HTTP 客户端基础

Go 通过 net/http 包提供了强大的 HTTP 客户端功能。

发送 GET 请求

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    // 发送 GET 请求
    resp, err := http.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.Println("响应体:", string(body))
}

发送 POST 请求

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

func main() {
    // 准备请求数据
    data := map[string]string{
        "name": "Alice",
        "age":  "30",
    }
    jsonData, _ := json.Marshal(data)

    // 发送 POST 请求
    resp, err := http.Post(
        "https://httpbin.org/post",
        "application/json",
        bytes.NewBuffer(jsonData),
    )
    if err != nil {
        fmt.Println("请求失败:", err)
        return
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println("响应:", string(body))
}

发送表单数据

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
)

func main() {
    // 准备表单数据
    formData := url.Values{}
    formData.Set("username", "alice")
    formData.Set("password", "123456")

    // 发送 POST 表单请求
    resp, err := http.PostForm(
        "https://httpbin.org/post",
        formData,
    )
    if err != nil {
        fmt.Println("请求失败:", err)
        return
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println("响应:", string(body))
}

36.2 自定义请求

使用 http.NewRequest

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

func main() {
    // 准备请求体
    data := map[string]string{"name": "Alice"}
    jsonData, _ := json.Marshal(data)

    // 创建请求
    req, err := http.NewRequest(
        "POST",
        "https://httpbin.org/post",
        bytes.NewBuffer(jsonData),
    )
    if err != nil {
        fmt.Println("创建请求失败:", err)
        return
    }

    // 设置请求头
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer token123")
    req.Header.Set("X-Custom-Header", "custom-value")

    // 发送请求
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("请求失败:", err)
        return
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println("响应:", string(body))
}

设置请求头

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    req, _ := http.NewRequest("GET", "https://httpbin.org/headers", nil)

    // 设置单个请求头
    req.Header.Set("Authorization", "Bearer token123")

    // 添加请求头(可添加多个同名头)
    req.Header.Add("Accept", "application/json")
    req.Header.Add("Accept", "text/html")

    // 设置 User-Agent
    req.Header.Set("User-Agent", "MyGoClient/1.0")

    client := &http.Client{}
    resp, _ := client.Do(req)
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

处理响应头

package main

import (
    "fmt"
    "net/http"
)

func main() {
    resp, err := http.Get("https://httpbin.org/get")
    if err != nil {
        fmt.Println("请求失败:", err)
        return
    }
    defer resp.Body.Close()

    // 获取单个响应头
    contentType := resp.Header.Get("Content-Type")
    fmt.Println("Content-Type:", contentType)

    // 遍历所有响应头
    fmt.Println("\n所有响应头:")
    for key, values := range resp.Header {
        for _, value := range values {
            fmt.Printf("%s: %s\n", key, value)
        }
    }
}

36.3 Cookie 处理

发送 Cookie

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    req, _ := http.NewRequest("GET", "https://httpbin.org/cookies", nil)

    // 添加 Cookie
    cookie1 := &http.Cookie{
        Name:  "session_id",
        Value: "abc123",
    }
    cookie2 := &http.Cookie{
        Name:  "user_id",
        Value: "user001",
    }
    req.AddCookie(cookie1)
    req.AddCookie(cookie2)

    client := &http.Client{}
    resp, _ := client.Do(req)
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

自动管理 Cookie

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/http/cookiejar"
)

func main() {
    // 创建 Cookie Jar
    jar, _ := cookiejar.New(nil)

    // 创建带有 Cookie Jar 的客户端
    client := &http.Client{
        Jar: jar,
    }

    // 第一次请求(服务器会设置 Cookie)
    resp1, _ := client.Get("https://httpbin.org/cookies/set?session_id=abc123")
    resp1.Body.Close()

    // 第二次请求(自动携带 Cookie)
    resp2, _ := client.Get("https://httpbin.org/cookies")
    defer resp2.Body.Close()

    body, _ := io.ReadAll(resp2.Body)
    fmt.Println(string(body))
}

36.4 超时设置

客户端超时

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    // 创建带超时的客户端
    client := &http.Client{
        Timeout: 5 * time.Second,
    }

    resp, err := client.Get("https://httpbin.org/delay/3")
    if err != nil {
        fmt.Println("请求失败:", err)
        return
    }
    defer resp.Body.Close()

    fmt.Println("状态码:", resp.StatusCode)
}

使用 Context 超时

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "time"
)

func main() {
    // 创建带超时的 Context
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    // 创建请求
    req, _ := http.NewRequestWithContext(ctx, "GET", "https://httpbin.org/delay/5", nil)

    // 发送请求
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("请求失败:", err)
        return
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

36.5 文件上传

上传文件

package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
)

func uploadFile(filename string) error {
    // 打开文件
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    // 创建 multipart writer
    var buf bytes.Buffer
    writer := multipart.NewWriter(&buf)

    // 添加文件字段
    part, _ := writer.CreateFormFile("file", filename)
    io.Copy(part, file)

    // 添加其他字段
    writer.WriteField("description", "文件描述")

    writer.Close()

    // 发送请求
    req, _ := http.NewRequest("POST", "https://httpbin.org/post", &buf)
    req.Header.Set("Content-Type", writer.FormDataContentType())

    client := &http.Client{}
    resp, _ := client.Do(req)
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))

    return nil
}

func main() {
    uploadFile("test.txt")
}

下载文件

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
)

func downloadFile(url, filename string) error {
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    // 创建文件
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    // 写入文件
    _, err = io.Copy(file, resp.Body)
    return err
}

func main() {
    err := downloadFile("https://example.com/file.txt", "downloaded.txt")
    if err != nil {
        fmt.Println("下载失败:", err)
        return
    }
    fmt.Println("下载成功")
}

36.6 HTTPS 请求

跳过证书验证

package main

import (
    "crypto/tls"
    "fmt"
    "io"
    "net/http"
)

func main() {
    // 创建跳过证书验证的客户端
    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{Transport: tr}

    resp, _ := client.Get("https://example.com")
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

使用自定义证书

package main

import (
    "crypto/tls"
    "crypto/x509"
    "io"
    "net/http"
    "os"
)

func main() {
    // 读取证书
    caCert, _ := os.ReadFile("ca.crt")
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)

    // 创建客户端
    client := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{
                RootCAs: caCertPool,
            },
        },
    }

    resp, _ := client.Get("https://example.com")
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    println(string(body))
}

36.7 代理设置

使用代理

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
)

func main() {
    // 设置代理
    proxyURL, _ := url.Parse("http://proxy.example.com:8080")
    client := &http.Client{
        Transport: &http.Transport{
            Proxy: http.ProxyURL(proxyURL),
        },
    }

    resp, _ := client.Get("https://httpbin.org/ip")
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

环境变量代理

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    // 使用环境变量中的代理设置
    client := &http.Client{
        Transport: &http.Transport{
            Proxy: http.ProxyFromEnvironment,
        },
    }

    resp, _ := client.Get("https://httpbin.org/ip")
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

36.8 连接池配置

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    // 配置连接池
    transport := &http.Transport{
        MaxIdleConns:        100,              // 最大空闲连接数
        MaxIdleConnsPerHost: 10,               // 每个主机的最大空闲连接数
        IdleConnTimeout:     90 * time.Second, // 空闲连接超时
        DisableKeepAlives:   false,            // 启用 Keep-Alive
    }

    client := &http.Client{
        Transport: transport,
        Timeout:   30 * time.Second,
    }

    resp, _ := client.Get("https://httpbin.org/get")
    defer resp.Body.Close()

    fmt.Println("状态码:", resp.StatusCode)
}

36.9 重定向处理

禁止重定向

package main

import (
    "fmt"
    "net/http"
)

func main() {
    client := &http.Client{
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            return http.ErrUseLastResponse // 不跟随重定向
        },
    }

    resp, _ := client.Get("https://httpbin.org/redirect/1")
    defer resp.Body.Close()

    fmt.Println("状态码:", resp.StatusCode)
    fmt.Println("Location:", resp.Header.Get("Location"))
}

自定义重定向

package main

import (
    "fmt"
    "net/http"
)

func main() {
    client := &http.Client{
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            fmt.Printf("重定向到: %s\n", req.URL)
            if len(via) >= 10 {
                return fmt.Errorf("重定向次数过多")
            }
            return nil
        },
    }

    resp, _ := client.Get("https://httpbin.org/redirect/3")
    defer resp.Body.Close()

    fmt.Println("最终状态码:", resp.StatusCode)
}

36.10 小结

本章详细介绍了 Go 语言的 HTTP 客户端编程:

  1. 基本请求:GET、POST、表单提交
  2. 自定义请求:使用 http.NewRequest 创建请求
  3. 请求头处理:设置和读取请求头
  4. Cookie 处理:发送 Cookie 和自动管理 Cookie
  5. 超时设置:客户端超时和 Context 超时
  6. 文件操作:文件上传和下载
  7. HTTPS 请求:证书验证和自定义证书
  8. 代理设置:使用代理访问网络
  9. 连接池:配置连接池提高性能
  10. 重定向:控制重定向行为

HTTP 客户端是进行网络编程的基础,掌握它能让你更好地与外部服务交互。在下一章中,我们将学习如何构建 HTTP 服务器。