3. HTTP 基础:net/http 包快速入门

在构建 REST API 之前,必须掌握 Go 标准库 net/http 的核心用法。本章将带你从零搭建一个可运行的 HTTP 服务器,并理解请求与响应的处理流程。

3.1 net/http 包的核心组件

net/http 包提供了构建 HTTP 客户端和服务端所需的所有功能。其核心组件包括:

组件 类型 作用
Server 结构体 配置监听地址、端口、超时等参数
ServeMux 多路复用器 将请求 URL 路由到对应的处理函数
Handler 接口 定义 ServeHTTP(ResponseWriter, *Request) 方法
ResponseWriter 接口 用于构造 HTTP 响应
Request 结构体 封装客户端请求的所有信息
关键理解: Go 的 HTTP 处理模型是 Handler 接口驱动的。任何实现了 ServeHTTP 方法的类型都可以作为处理器,这为中间件和路由组合提供了极大的灵活性。

3.2 创建第一个 HTTP 服务器

以下代码演示了如何使用 net/http 包创建一个监听 8080 端口的服务器,并处理根路径的请求:

package main

import (
    "fmt"
    "net/http"
)

// 定义一个处理器
func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, 欢迎来到 Go REST API 课程!")
}

func main() {
    // 注册路由
    http.HandleFunc("/", helloHandler)

    // 启动服务器
    fmt.Println("服务器启动,监听 :8080")
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Printf("服务器启动失败: %v\n", err)
    }
}
运行步骤:
  1. 将上述代码保存为 main.go
  2. 在终端执行 go run main.go
  3. 打开浏览器访问 http://localhost:8080

3.3 请求与响应处理流程

当客户端发送请求时,Go 的 HTTP 服务器按照以下流程处理:

  1. 监听连接: ListenAndServe 在指定端口等待 TCP 连接。
  2. 解析请求: 将原始字节流解析为 http.Request 结构体。
  3. 路由匹配: ServeMux 根据请求路径找到对应的 Handler。
  4. 执行处理: 调用 Handler.ServeHTTP 方法。
  5. 写入响应: 通过 ResponseWriter 返回状态码、头部和正文。
HTTP 请求处理流程 客户端请求 ListenAndServe 解析 Request 路由匹配 Handler 处理 写入 Response 返回客户端

3.4 处理不同的 HTTP 方法

REST API 需要根据 HTTP 方法(GET、POST、PUT、DELETE 等)执行不同的逻辑。通过 r.Method 字段可以判断请求方法:

func userHandler(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case http.MethodGet:
        fmt.Fprintf(w, "获取用户列表")
    case http.MethodPost:
        fmt.Fprintf(w, "创建新用户")
    case http.MethodPut:
        fmt.Fprintf(w, "更新用户信息")
    case http.MethodDelete:
        fmt.Fprintf(w, "删除用户")
    default:
        http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
    }
}

func main() {
    http.HandleFunc("/users", userHandler)
    http.ListenAndServe(":8080", nil)
}
常见错误: 忘记处理 OPTIONS 和 HEAD 方法会导致跨域请求(CORS)失败。在生产环境中,建议使用专门的中间件统一处理。

3.5 读取请求参数

客户端可以通过三种方式传递参数:URL 查询参数、表单数据和 JSON 请求体。以下是读取查询参数的示例:

func searchHandler(w http.ResponseWriter, r *http.Request) {
    // 获取查询参数
    query := r.URL.Query()
    keyword := query.Get("q")
    page := query.Get("page")

    if keyword == "" {
        http.Error(w, "缺少查询参数 q", http.StatusBadRequest)
        return
    }

    fmt.Fprintf(w, "搜索关键词: %s, 页码: %s", keyword, page)
}

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

3.6 设置响应头与状态码

良好的 API 设计需要明确的状态码和响应头。以下示例展示了如何设置 JSON 响应:

func jsonHandler(w http.ResponseWriter, r *http.Request) {
    // 设置响应头
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("X-API-Version", "1.0")

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

    // 写入 JSON 数据
    fmt.Fprintf(w, `{"message": "成功", "code": 200}`)
}
常用状态码 含义 使用场景
200 OK 请求成功 GET 获取资源、PUT 更新成功
201 Created 资源创建成功 POST 创建新资源
400 Bad Request 请求参数错误 参数校验失败
404 Not Found 资源不存在 请求路径错误
500 Internal Server Error 服务器内部错误 未捕获的异常

3.7 使用 Server 结构体进行精细控制

直接使用 http.ListenAndServe 会使用默认配置。对于生产环境,建议使用 http.Server 结构体进行精细控制:

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", helloHandler)

    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  30 * time.Second,
    }

    fmt.Println("服务器启动,监听 :8080")
    if err := server.ListenAndServe(); err != nil {
        fmt.Printf("服务器启动失败: %v\n", err)
    }
}
最佳实践: 始终设置 ReadTimeout 和 WriteTimeout,防止慢连接或恶意请求耗尽服务器资源。IdleTimeout 用于 HTTP/2 的持久连接管理。
默认配置 vs 自定义 Server 配置 默认 ListenAndServe • Addr: ":8080"(硬编码) • Handler: nil(使用 DefaultServeMux) • ReadTimeout: 0(无超时) • WriteTimeout: 0(无超时) ⚠ 风险:无超时限制,易被攻击 自定义 Server 结构体 • Addr: ":8080"(可配置化) • Handler: 自定义 mux • ReadTimeout: 10s • WriteTimeout: 10s ✓ 安全:超时控制,资源可控

3.8 本章小结

  • 核心组件: Server、ServeMux、Handler 接口构成了 Go HTTP 处理的基石。
  • 请求处理: 通过 r.Method 区分 HTTP 方法,通过 r.URL.Query() 读取参数。
  • 响应构建: 使用 w.Header().Set() 设置头部,w.WriteHeader() 设置状态码。
  • 生产配置: 始终使用 http.Server 结构体并设置超时参数。