3.1.2 net/http 包详解

3.1.2 net/http 包详解 #

Go 语言的 net/http 包是标准库中最重要的包之一,它提供了完整的 HTTP 客户端和服务器实现。这个包设计精良,既简单易用又功能强大,是 Go Web 开发的基石。本节将深入解析 net/http 包的核心组件和使用方法。

包结构概览 #

net/http 包的主要组件包括:

net/http/
├── 核心类型
│   ├── Request          # HTTP 请求
│   ├── Response         # HTTP 响应
│   ├── Header           # HTTP 头部
│   └── Cookie           # HTTP Cookie
├── 服务器组件
│   ├── Server           # HTTP 服务器
│   ├── ServeMux         # 请求路由器
│   ├── Handler          # 请求处理器接口
│   └── HandlerFunc      # 函数适配器
├── 客户端组件
│   ├── Client           # HTTP 客户端
│   ├── Transport        # 传输层
│   └── RoundTripper     # 往返器接口
└── 工具函数
    ├── ListenAndServe   # 启动服务器
    ├── Get/Post/...     # 便捷请求函数
    └── 各种工具函数

核心数据结构 #

Request 结构体 #

Request 代表一个 HTTP 请求:

type Request struct {
    Method     string      // HTTP 方法 (GET, POST, PUT, etc.)
    URL        *url.URL    // 请求的 URL
    Proto      string      // 协议版本 ("HTTP/1.0", "HTTP/1.1", "HTTP/2.0")
    ProtoMajor int         // 协议主版本号
    ProtoMinor int         // 协议次版本号
    Header     Header      // 请求头部
    Body       io.ReadCloser // 请求体
    GetBody    func() (io.ReadCloser, error) // 获取请求体副本的函数
    ContentLength int64    // 内容长度
    TransferEncoding []string // 传输编码
    Close      bool        // 是否在响应后关闭连接
    Host       string      // Host 头部的值
    Form       url.Values  // 解析后的表单数据
    PostForm   url.Values  // 解析后的 POST 表单数据
    MultipartForm *multipart.Form // 解析后的多部分表单数据
    Trailer    Header      // 尾部头部
    RemoteAddr string      // 远程地址
    RequestURI string      // 原始请求 URI
    TLS        *tls.ConnectionState // TLS 连接状态
    Cancel     <-chan struct{} // 请求取消通道 (已废弃)
    Response   *Response   // 重定向响应 (仅客户端)

    // Go 1.7+ 添加的 context 支持
    ctx context.Context
}

Response 结构体 #

Response 代表一个 HTTP 响应:

type Response struct {
    Status     string // 状态行 (e.g. "200 OK")
    StatusCode int    // 状态码 (e.g. 200)
    Proto      string // 协议版本
    ProtoMajor int    // 协议主版本号
    ProtoMinor int    // 协议次版本号
    Header     Header // 响应头部
    Body       io.ReadCloser // 响应体
    ContentLength int64 // 内容长度
    TransferEncoding []string // 传输编码
    Close      bool   // 是否关闭连接
    Uncompressed bool // 是否未压缩
    Trailer    Header // 尾部头部
    Request    *Request // 对应的请求
    TLS        *tls.ConnectionState // TLS 连接状态
}

Header 类型 #

Header 是一个映射类型,用于表示 HTTP 头部:

type Header map[string][]string

Header 提供了丰富的方法:

// 基本操作
func (h Header) Add(key, value string)    // 添加头部值
func (h Header) Set(key, value string)    // 设置头部值
func (h Header) Get(key string) string    // 获取头部值
func (h Header) Del(key string)           // 删除头部
func (h Header) Values(key string) []string // 获取所有值

// 特殊方法
func (h Header) Write(w io.Writer) error  // 写入头部到 Writer
func (h Header) Clone() Header            // 克隆头部

使用示例:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    // 创建头部
    header := make(http.Header)

    // 设置头部值
    header.Set("Content-Type", "application/json")
    header.Set("Authorization", "Bearer token123")

    // 添加多个值
    header.Add("Accept", "application/json")
    header.Add("Accept", "text/plain")

    // 获取头部值
    contentType := header.Get("Content-Type")
    fmt.Println("Content-Type:", contentType)

    // 获取所有 Accept 值
    accepts := header.Values("Accept")
    fmt.Println("Accept values:", accepts)

    // 遍历所有头部
    for key, values := range header {
        fmt.Printf("%s: %v\n", key, values)
    }
}

Cookie 代表一个 HTTP cookie:

type Cookie struct {
    Name  string
    Value string

    Path       string    // 可选
    Domain     string    // 可选
    Expires    time.Time // 可选
    RawExpires string    // 仅用于读取 cookie

    // MaxAge=0 表示未指定 "Max-Age" 属性
    // MaxAge<0 表示立即删除 cookie
    // MaxAge>0 表示 Max-Age 属性存在且以秒为单位给出
    MaxAge   int
    Secure   bool
    HttpOnly bool
    SameSite SameSite
    Raw      string
    Unparsed []string // 未解析的属性值对
}

Cookie 操作示例:

package main

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

func setCookieHandler(w http.ResponseWriter, r *http.Request) {
    // 创建 cookie
    cookie := &http.Cookie{
        Name:     "session_id",
        Value:    "abc123xyz",
        Path:     "/",
        Domain:   "example.com",
        Expires:  time.Now().Add(24 * time.Hour),
        MaxAge:   86400, // 24小时
        Secure:   true,
        HttpOnly: true,
        SameSite: http.SameSiteStrictMode,
    }

    // 设置 cookie
    http.SetCookie(w, cookie)

    fmt.Fprint(w, "Cookie 已设置")
}

func getCookieHandler(w http.ResponseWriter, r *http.Request) {
    // 获取特定 cookie
    cookie, err := r.Cookie("session_id")
    if err != nil {
        fmt.Fprintf(w, "Cookie 不存在: %v", err)
        return
    }

    fmt.Fprintf(w, "Cookie 值: %s", cookie.Value)

    // 获取所有 cookies
    cookies := r.Cookies()
    fmt.Fprintf(w, "\n所有 Cookies: %d 个", len(cookies))
    for _, c := range cookies {
        fmt.Fprintf(w, "\n%s = %s", c.Name, c.Value)
    }
}

Handler 接口和 HandlerFunc #

Handler 接口 #

Handler 是 HTTP 处理器的核心接口:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

任何实现了 ServeHTTP 方法的类型都可以作为 HTTP 处理器。

HandlerFunc 类型 #

HandlerFunc 是一个适配器,允许普通函数作为 HTTP 处理器:

type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

使用示例:

package main

import (
    "fmt"
    "net/http"
)

// 实现 Handler 接口的结构体
type MyHandler struct {
    message string
}

func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "MyHandler: %s", h.message)
}

// 普通函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello from HandlerFunc!")
}

func main() {
    // 使用结构体处理器
    handler1 := &MyHandler{message: "Hello World"}
    http.Handle("/handler", handler1)

    // 使用函数处理器
    http.HandleFunc("/func", helloHandler)

    // 使用匿名函数
    http.HandleFunc("/anonymous", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello from anonymous function!")
    })

    http.ListenAndServe(":8080", nil)
}

ServeMux 路由器 #

ServeMux 是 Go 标准库提供的 HTTP 请求路由器:

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

路由模式 #

ServeMux 支持两种路由模式:

  1. 固定路径:精确匹配
  2. 子树路径:以 / 结尾,匹配子路径
package main

import (
    "fmt"
    "net/http"
)

func main() {
    mux := http.NewServeMux()

    // 固定路径 - 精确匹配
    mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello World")
    })

    // 子树路径 - 匹配 /api/ 下的所有路径
    mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "API endpoint: %s", r.URL.Path)
    })

    // 根路径 - 匹配所有未匹配的路径
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path != "/" {
            http.NotFound(w, r)
            return
        }
        fmt.Fprint(w, "Home Page")
    })

    // 带主机名的路由
    mux.HandleFunc("api.example.com/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "API subdomain")
    })

    http.ListenAndServe(":8080", mux)
}

路由优先级 #

ServeMux 按照以下优先级匹配路由:

  1. 精确匹配优先于模式匹配
  2. 长模式优先于短模式
  3. 带主机名的模式优先于不带主机名的模式
package main

import (
    "fmt"
    "net/http"
)

func main() {
    mux := http.NewServeMux()

    // 这些路由的匹配优先级
    mux.HandleFunc("/api/users/profile", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "User Profile (精确匹配)")
    })

    mux.HandleFunc("/api/users/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Users API (长模式)")
    })

    mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "API (短模式)")
    })

    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Root (最短模式)")
    })

    http.ListenAndServe(":8080", mux)
}

Server 结构体 #

Server 定义了运行 HTTP 服务器的参数:

type Server struct {
    Addr    string  // 监听地址
    Handler Handler // 处理器,如果为 nil 则使用 DefaultServeMux

    // 超时设置
    ReadTimeout       time.Duration // 读取超时
    ReadHeaderTimeout time.Duration // 读取头部超时
    WriteTimeout      time.Duration // 写入超时
    IdleTimeout       time.Duration // 空闲超时

    // TLS 配置
    TLSConfig *tls.Config

    // 连接状态回调
    ConnState func(net.Conn, ConnState)

    // 错误日志
    ErrorLog *log.Logger

    // 其他配置
    MaxHeaderBytes int
    TLSNextProto   map[string]func(*Server, *tls.Conn, Handler)
    ConnContext    func(ctx context.Context, c net.Conn) context.Context
    BaseContext    func(net.Listener) context.Context
}

服务器配置示例 #

package main

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

func main() {
    // 创建处理器
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello World")
    })

    // 配置服务器
    server := &http.Server{
        Addr:              ":8080",
        Handler:           mux,
        ReadTimeout:       10 * time.Second,
        ReadHeaderTimeout: 5 * time.Second,
        WriteTimeout:      10 * time.Second,
        IdleTimeout:       120 * time.Second,
        MaxHeaderBytes:    1 << 20, // 1MB
        ErrorLog:          log.New(os.Stderr, "HTTP Server: ", log.LstdFlags),
    }

    // 启动服务器
    log.Printf("服务器启动在 %s", server.Addr)
    if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        log.Fatalf("服务器启动失败: %v", err)
    }
}

优雅关闭 #

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // 模拟长时间处理
        time.Sleep(2 * time.Second)
        fmt.Fprint(w, "Hello World")
    })

    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

    // 在 goroutine 中启动服务器
    go func() {
        log.Println("服务器启动在 :8080")
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("服务器启动失败: %v", err)
        }
    }()

    // 等待中断信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    log.Println("正在关闭服务器...")

    // 创建超时上下文
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    // 优雅关闭服务器
    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("服务器关闭失败: %v", err)
    }

    log.Println("服务器已关闭")
}

Client 和 Transport #

Client 结构体 #

Client 是 HTTP 客户端:

type Client struct {
    Transport RoundTripper // 传输层
    CheckRedirect func(req *Request, via []*Request) error // 重定向检查
    Jar       CookieJar    // Cookie 管理器
    Timeout   time.Duration // 超时时间
}

默认客户端 #

Go 提供了一个默认的客户端:

var DefaultClient = &Client{}

便捷函数如 http.Gethttp.Post 等都使用这个默认客户端。

自定义客户端 #

package main

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

func main() {
    // 创建自定义客户端
    client := &http.Client{
        Timeout: 10 * time.Second,
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            // 限制重定向次数
            if len(via) >= 3 {
                return fmt.Errorf("重定向次数过多")
            }
            return nil
        },
    }

    // 发送请求
    resp, err := client.Get("https://httpbin.org/get")
    if err != nil {
        fmt.Printf("请求失败: %v\n", err)
        return
    }
    defer resp.Body.Close()

    fmt.Printf("状态码: %d\n", resp.StatusCode)
    fmt.Printf("状态: %s\n", resp.Status)
}

Transport 配置 #

Transport 实现了 RoundTripper 接口,负责实际的 HTTP 传输:

package main

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

func main() {
    // 自定义 Transport
    transport := &http.Transport{
        MaxIdleConns:        100,
        MaxIdleConnsPerHost: 10,
        IdleConnTimeout:     90 * time.Second,
        DisableCompression:  false,
        TLSClientConfig: &tls.Config{
            InsecureSkipVerify: false,
        },
    }

    // 创建使用自定义 Transport 的客户端
    client := &http.Client{
        Transport: transport,
        Timeout:   30 * time.Second,
    }

    resp, err := client.Get("https://httpbin.org/get")
    if err != nil {
        fmt.Printf("请求失败: %v\n", err)
        return
    }
    defer resp.Body.Close()

    fmt.Printf("状态码: %d\n", resp.StatusCode)
}

ResponseWriter 接口 #

ResponseWriter 接口用于构建 HTTP 响应:

type ResponseWriter interface {
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(statusCode int)
}

响应写入顺序 #

正确的响应写入顺序:

  1. 设置头部(可选)
  2. 调用 WriteHeader(可选,默认 200)
  3. 写入响应体
package main

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

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func userHandler(w http.ResponseWriter, r *http.Request) {
    user := User{ID: 1, Name: "张三"}

    // 1. 设置头部
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Cache-Control", "no-cache")

    // 2. 设置状态码(可选)
    w.WriteHeader(http.StatusOK)

    // 3. 写入响应体
    if err := json.NewEncoder(w).Encode(user); err != nil {
        // 注意:这里不能再调用 WriteHeader
        fmt.Printf("编码错误: %v\n", err)
    }
}

func main() {
    http.HandleFunc("/user", userHandler)
    http.ListenAndServe(":8080", nil)
}

常见错误处理 #

package main

import (
    "encoding/json"
    "net/http"
)

type ErrorResponse struct {
    Error   string `json:"error"`
    Code    int    `json:"code"`
    Message string `json:"message"`
}

func sendError(w http.ResponseWriter, statusCode int, message string) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(statusCode)

    errorResp := ErrorResponse{
        Error:   http.StatusText(statusCode),
        Code:    statusCode,
        Message: message,
    }

    json.NewEncoder(w).Encode(errorResp)
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        sendError(w, http.StatusMethodNotAllowed, "只支持 GET 方法")
        return
    }

    // 模拟业务逻辑错误
    if r.URL.Query().Get("id") == "" {
        sendError(w, http.StatusBadRequest, "缺少 id 参数")
        return
    }

    // 正常响应
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"message": "success"}`))
}

func main() {
    http.HandleFunc("/api", apiHandler)
    http.ListenAndServe(":8080", nil)
}

中间件模式 #

虽然标准库没有内置中间件支持,但可以通过函数组合实现:

package main

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

// 中间件类型
type Middleware func(http.Handler) http.Handler

// 日志中间件
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()

        // 调用下一个处理器
        next.ServeHTTP(w, r)

        // 记录日志
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// 认证中间件
func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "未授权", http.StatusUnauthorized)
            return
        }

        // 验证 token(这里简化处理)
        if token != "Bearer valid-token" {
            http.Error(w, "无效 token", http.StatusUnauthorized)
            return
        }

        next.ServeHTTP(w, r)
    })
}

// CORS 中间件
func CORSMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }

        next.ServeHTTP(w, r)
    })
}

// 中间件链
func Chain(middlewares ...Middleware) Middleware {
    return func(next http.Handler) http.Handler {
        for i := len(middlewares) - 1; i >= 0; i-- {
            next = middlewares[i](next)
        }
        return next
    }
}

func main() {
    // 业务处理器
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello World")
    })

    // 应用中间件链
    finalHandler := Chain(
        LoggingMiddleware,
        CORSMiddleware,
        AuthMiddleware,
    )(handler)

    http.Handle("/api", finalHandler)

    // 公开端点(不需要认证)
    publicHandler := Chain(
        LoggingMiddleware,
        CORSMiddleware,
    )(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Public endpoint")
    }))

    http.Handle("/public", publicHandler)

    log.Println("服务器启动在 :8080")
    http.ListenAndServe(":8080", nil)
}

文件服务 #

net/http 包提供了静态文件服务功能:

package main

import (
    "net/http"
    "log"
)

func main() {
    // 方法1:使用 FileServer
    fs := http.FileServer(http.Dir("./static/"))
    http.Handle("/static/", http.StripPrefix("/static/", fs))

    // 方法2:使用 ServeFile
    http.HandleFunc("/download", func(w http.ResponseWriter, r *http.Request) {
        http.ServeFile(w, r, "./files/document.pdf")
    })

    // 方法3:自定义文件处理
    http.HandleFunc("/files/", func(w http.ResponseWriter, r *http.Request) {
        filename := r.URL.Path[len("/files/"):]
        if filename == "" {
            http.Error(w, "文件名不能为空", http.StatusBadRequest)
            return
        }

        // 设置下载头部
        w.Header().Set("Content-Disposition", "attachment; filename="+filename)
        http.ServeFile(w, r, "./uploads/"+filename)
    })

    log.Println("文件服务器启动在 :8080")
    http.ListenAndServe(":8080", nil)
}

最佳实践 #

1. 合理设置超时 #

server := &http.Server{
    Addr:              ":8080",
    ReadTimeout:       10 * time.Second,
    ReadHeaderTimeout: 5 * time.Second,
    WriteTimeout:      10 * time.Second,
    IdleTimeout:       120 * time.Second,
}

2. 使用上下文控制 #

func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()

    select {
    case <-time.After(5 * time.Second):
        fmt.Fprint(w, "处理完成")
    case <-ctx.Done():
        // 请求被取消
        return
    }
}

3. 正确处理错误 #

func handler(w http.ResponseWriter, r *http.Request) {
    data, err := processRequest(r)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    if err := json.NewEncoder(w).Encode(data); err != nil {
        log.Printf("编码错误: %v", err)
    }
}

4. 使用连接池 #

transport := &http.Transport{
    MaxIdleConns:        100,
    MaxIdleConnsPerHost: 10,
    IdleConnTimeout:     90 * time.Second,
}

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

小结 #

net/http 包是 Go Web 开发的基础,本节详细介绍了:

  1. 核心数据结构:Request、Response、Header、Cookie 的结构和用法
  2. 处理器系统:Handler 接口、HandlerFunc 类型和 ServeMux 路由器
  3. 服务器配置:Server 结构体的配置选项和优雅关闭
  4. 客户端功能:Client 和 Transport 的配置和使用
  5. 响应写入:ResponseWriter 接口的正确使用方法
  6. 高级特性:中间件模式、文件服务等实用功能

掌握这些核心概念和组件,将为后续的 HTTP 服务端和客户端开发奠定坚实的基础。在下一节中,我们将学习如何使用这些组件构建完整的 HTTP 服务器。