4. Go 标准库 net/http 入门:HTTP 服务器基础、处理请求与响应、路由分发基础

Go 语言的标准库 net/http 为构建 HTTP 服务器提供了强大且简洁的原生支持。本章将带你从零开始,掌握使用标准库创建服务器、处理请求与响应,以及实现基础路由分发的核心技能。这是后续构建 REST API 的基石。

4.1 HTTP 服务器基础:启动你的第一个服务

使用 net/http 启动一个 HTTP 服务器非常简单,核心函数是 http.ListenAndServe。它负责监听一个 TCP 端口,并为每个传入的 HTTP 请求创建一个新的 goroutine 进行处理。

核心函数签名:
func ListenAndServe(addr string, handler Handler) error
  • addr:监听地址,例如 ":8080"(监听所有网络接口的 8080 端口)。
  • handler:处理器,负责处理所有传入的请求。通常传入 nil 以使用默认的多路复用器 http.DefaultServeMux。

示例:一个最简单的 HTTP 服务器

package main

import (
    "fmt"
    "net/http"
)

func main() {
    // 使用默认的 ServeMux 作为处理器
    // 当访问根路径 "/" 时,调用 helloHandler 函数
    http.HandleFunc("/", helloHandler)

    // 启动服务器,监听 8080 端口
    fmt.Println("服务器启动,监听端口 :8080")
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Println("服务器启动失败:", err)
    }
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, 你访问了: %s!", r.URL.Path)
}
💡 提示: http.ListenAndServe 是一个阻塞调用,它会一直运行直到服务器因为错误而关闭。在生产环境中,你可能需要使用 http.Server 结构体进行更精细的控制(如超时设置)。

4.2 处理请求与响应:理解 Handler 与 ResponseWriter

在 Go 的 HTTP 模型中,每个请求都由一个实现了 http.Handler 接口的对象来处理。这个接口只有一个方法:

type Handler interface {
    ServeHTTP(w http.ResponseWriter, r *http.Request)
}

两个核心参数构成了请求-响应的全部交互:

参数 类型 作用与关键方法
w http.ResponseWriter 用于构建和发送 HTTP 响应。关键方法:
w.Write([]byte):写入响应体。
w.WriteHeader(statusCode):设置状态码。
w.Header().Set(key, value):设置响应头。
r *http.Request 代表客户端发来的 HTTP 请求。关键字段:
r.Method:请求方法 (GET, POST 等)。
r.URL:请求的 URL 对象。
r.Body:请求体 (io.ReadCloser)。
r.Header:请求头。

构建一个完整的响应示例:

func userHandler(w http.ResponseWriter, r *http.Request) {
    // 1. 设置响应头
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("X-Custom-Header", "my-value")

    // 2. 设置状态码 (必须在写入响应体之前调用)
    w.WriteHeader(http.StatusOK) // 200

    // 3. 写入响应体
    response := `{"message": "获取用户信息成功", "user": "Alice"}`
    w.Write([]byte(response))
}
⚠️ 重要规则: w.WriteHeader() 必须在 w.Write() 之前调用。如果在 Write() 之后调用,Go 会自动写入 200 OK 状态码,此时再调用 WriteHeader() 将无效。同样,设置响应头 w.Header().Set() 也应在 WriteHeader() 之前完成。

4.3 路由分发基础:ServeMux 的工作原理

http.ServeMux 是 Go 标准库提供的 HTTP 请求多路复用器(路由器)。它的核心职责是:根据请求的 URL 路径,将请求分发到已注册的处理器函数。

路由注册与匹配规则:

  • 精确匹配: 注册路径 /help 只匹配 /help。
  • 子树匹配: 注册路径 /api/ 会匹配所有以 /api/ 开头的路径(如 /api/users, /api/orders/123)。
  • 根路径兜底: 注册路径 / 会匹配所有未被其他模式匹配的请求,通常作为 404 或默认页面处理器。
  • 最长匹配优先: 当多个模式匹配时,选择最长的那个。例如,同时注册了 / 和 /api/,访问 /api/users 会由 /api/ 的处理器处理。

示例:构建一个简单的 RESTful 风格路由

package main

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

func main() {
    // 注册不同的路由模式
    http.HandleFunc("/", rootHandler)          // 兜底
    http.HandleFunc("/api/", apiRouter)        // API 子树
    http.HandleFunc("/health", healthHandler)  // 精确匹配

    fmt.Println("REST API 服务器启动 :8080")
    http.ListenAndServe(":8080", nil)
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusNotFound)
    fmt.Fprintf(w, "404 - 页面未找到: %s", r.URL.Path)
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"status": "UP"})
}

func apiRouter(w http.ResponseWriter, r *http.Request) {
    // 在 /api/ 处理器内部,根据 Method 和具体路径进行二次分发
    path := r.URL.Path
    switch {
    case path == "/api/users" && r.Method == "GET":
        fmt.Fprintf(w, "获取所有用户")
    case path == "/api/users" && r.Method == "POST":
        fmt.Fprintf(w, "创建新用户")
    default:
        w.WriteHeader(http.StatusNotFound)
        fmt.Fprintf(w, "API 端点未实现: %s %s", r.Method, path)
    }
}
Go net/http 请求处理流程 HTTP 客户端 ListenAndServe ServeMux 路由匹配决策 最长匹配原则 / 精确 vs 子树 Handler.ServeHTTP() 构建 Response 关键组件说明: • ListenAndServe: 启动 TCP 监听 • ServeMux: 根据 URL 路径分发请求 • Handler: 处理业务逻辑并写入响应

4.4 深入理解 Handler 与 HandlerFunc

除了直接实现 Handler 接口,Go 还提供了 http.HandlerFunc 适配器类型,它允许将普通函数(具有 func(w, r) 签名)作为 Handler 使用。这正是 http.HandleFunc 内部的工作原理。

方式 代码示例 适用场景
实现 Handler 接口
type myHandler struct{}
func (h *myHandler) ServeHTTP(w, r) {
    // ...
}
http.Handle("/path", &myHandler{})
需要维护状态或依赖注入的复杂处理器。
使用 HandlerFunc 适配器
func myFunc(w, r) {
    // ...
}
http.Handle("/path", http.HandlerFunc(myFunc))
将普通函数显式转换为 Handler。
使用 HandleFunc 便捷方法
http.HandleFunc("/path", myFunc)
最常用,代码最简洁。

4.5 实战:构建一个简单的用户 API

综合以上知识,我们构建一个仅使用标准库的、具备基本路由能力的用户管理 API。

package main

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

// 模拟数据存储
var users = map[string]string{
    "1": "Alice",
    "2": "Bob",
}

func main() {
    // 注册路由
    http.HandleFunc("/users", usersHandler)
    http.HandleFunc("/users/", userByIDHandler)

    log.Println("用户 API 服务器启动 :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

// 处理 /users 路径
func usersHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    switch r.Method {
    case http.MethodGet:
        json.NewEncoder(w).Encode(users)
    case http.MethodPost:
        var newUser map[string]string
        if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
            http.Error(w, `{"error":"无效的请求体"}`, http.StatusBadRequest)
            return
        }
        // 简单处理:将新用户添加到 map
        for id, name := range newUser {
            users[id] = name
        }
        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(newUser)
    default:
        http.Error(w, `{"error":"方法不允许"}`, http.StatusMethodNotAllowed)
    }
}

// 处理 /users/{id} 路径
func userByIDHandler(w http.ResponseWriter, r *http.Request) {
    // 从路径中提取 ID: /users/123 -> "123"
    id := strings.TrimPrefix(r.URL.Path, "/users/")
    if id == "" {
        http.Error(w, `{"error":"用户 ID 不能为空"}`, http.StatusBadRequest)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    switch r.Method {
    case http.MethodGet:
        name, ok := users[id]
        if !ok {
            http.Error(w, `{"error":"用户未找到"}`, http.StatusNotFound)
            return
        }
        json.NewEncoder(w).Encode(map[string]string{"id": id, "name": name})
    default:
        http.Error(w, `{"error":"方法不允许"}`, http.StatusMethodNotAllowed)
    }
}
💡 关键点总结:
  • 路径解析: 使用 strings.TrimPrefix 从 URL 路径中提取动态参数(如用户 ID)。这是标准库路由的常见模式。
  • 方法分发: 在处理器内部通过 r.Method 进行 GET/POST/PUT/DELETE 的分发,实现 RESTful 风格。
  • JSON 处理: 使用 encoding/json 包轻松实现请求体的解码和响应体的编码。
  • 错误处理: 使用 http.Error 快速返回标准 HTTP 错误响应。
标准库路由分发与处理逻辑 HTTP 请求 ServeMux 路由匹配 /users 处理器 GET: 列表 / POST: 创建 /users/{id} 处理器 GET: 详情 / 路径解析 /health 处理器 健康检查 JSON 响应 JSON 响应 JSON 响应

4.6 本章小结

通过本章的学习,你已经掌握了 Go 标准库 net/http 的核心用法:

  • 使用 http.ListenAndServe 启动一个 HTTP 服务器。
  • 理解 http.ResponseWriter 和 *http.Request 的职责,并能够构建和发送响应。
  • 掌握 http.ServeMux 的路由匹配规则(精确匹配、子树匹配、最长匹配优先)。
  • 学会使用 http.HandleFunc 和 http.Handler 接口注册处理器。
  • 能够结合路径解析和方法分发,构建一个简单的 RESTful API。