3. HTTP 基础服务搭建:使用 net/http 标准库创建简单 Web 服务;路由注册与中间件概念引入

3.1 使用 net/http 标准库创建 Web 服务

Go 语言的标准库 net/http 提供了构建 HTTP 服务所需的核心能力,无需依赖任何第三方框架即可快速搭建一个可用的 Web 服务。本节将演示如何通过标准库创建基础服务,并理解其核心组件。

3.1.1 核心组件与工作流程

组件 职责 关键方法/类型
Handler 定义处理 HTTP 请求的接口 ServeHTTP(ResponseWriter, *Request)
ServeMux 默认的路由多路复用器,将请求 URL 匹配到注册的 Handler http.NewServeMux()、mux.HandleFunc()
Server 监听端口、接受连接、分发请求 http.Server{Addr: ":8080", Handler: mux}
ListenAndServe 启动 HTTP 服务器并阻塞等待 http.ListenAndServe(":8080", nil)

3.1.2 最小化 Web 服务示例

以下代码创建了一个监听 8080 端口的 HTTP 服务,根路径返回 "Hello, Go!"。

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, Go!")
}

func main() {
    // 方式一:使用默认的 ServeMux(Handler 为 nil)
    http.HandleFunc("/", helloHandler)
    http.ListenAndServe(":8080", nil)

    // 方式二:显式创建 ServeMux(推荐)
    // mux := http.NewServeMux()
    // mux.HandleFunc("/", helloHandler)
    // http.ListenAndServe(":8080", mux)
}
实践要点: 在生产环境中,建议始终显式创建 http.Server 结构体,以便设置超时、读写缓冲区和错误日志等参数,避免使用默认值带来的安全隐患。

3.2 路由注册与模式匹配

net/http 标准库的路由器(ServeMux)支持基于路径前缀的模式匹配。理解其匹配规则是构建稳定 API 的基础。

3.2.1 路由注册方式

  • 精确路径: /api/login 仅匹配该路径。
  • 子树路径: /api/ 匹配以 /api/ 开头的所有路径,包括 /api 本身(会触发 301 重定向到 /api/)。
  • 根路径: / 匹配所有未被其他模式匹配的请求,通常作为兜底处理器。

3.2.2 路由注册示例

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

    // 精确匹配
    mux.HandleFunc("/health", healthHandler)
    mux.HandleFunc("/api/login", loginHandler)

    // 子树匹配:所有 /api/v1/ 开头的请求
    mux.HandleFunc("/api/v1/", apiV1Handler)

    // 兜底匹配
    mux.HandleFunc("/", notFoundHandler)

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

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"status":"ok"}`))
}

func apiV1Handler(w http.ResponseWriter, r *http.Request) {
    // r.URL.Path 可以获取完整路径,用于进一步路由
    fmt.Fprintf(w, "API V1: %s", r.URL.Path)
}
常见陷阱: 当注册 /api(无尾部斜杠)和 /api/(有尾部斜杠)时,ServeMux 会对 /api 的请求自动发起 301 重定向到 /api/。如果希望 /api 和 /api/ 返回不同内容,需要分别注册两个处理器。

3.3 中间件概念引入

在限流熔断与权限验证骨架中,中间件是核心设计模式。它允许我们在不修改业务处理器的情况下,横向添加横切关注点(如日志、鉴权、限流)。

3.3.1 中间件的本质

中间件本质上是一个函数,它接收一个 http.Handler 作为参数,并返回一个新的 http.Handler。返回的 Handler 在执行前后可以插入自定义逻辑,形成处理链。

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

// 日志中间件示例
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 前置处理
        log.Printf("Request: %s %s", r.Method, r.URL.Path)

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

        // 后置处理
        log.Printf("Response sent for %s", r.URL.Path)
    })
}

3.3.2 中间件链式组合

多个中间件可以嵌套组合,形成洋葱模型。请求从外层中间件进入,依次经过内层中间件,到达业务处理器,响应再反向返回。

// 组合多个中间件
func ChainMiddleware(handler http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/api/data", dataHandler)

    // 应用中间件链:日志 -> 鉴权 -> 业务
    wrappedHandler := ChainMiddleware(
        mux,
        LoggingMiddleware,
        AuthMiddleware,
    )

    http.ListenAndServe(":8080", wrappedHandler)
}
架构价值: 在限流熔断骨架中,中间件模式使得我们可以将 令牌桶限流、滑动窗口计数、JWT 鉴权 等逻辑独立封装,通过配置即可灵活组合,无需侵入业务代码。

3.4 中间件与路由的集成架构

下图展示了中间件在 HTTP 请求处理链中的位置与数据流向。

中间件链与请求处理流程 客户端 日志中间件 记录请求/响应 限流中间件 令牌桶/滑动窗口 鉴权中间件 JWT / OAuth2 业务 响应反向传递 中间件链关键特性 1. 每个中间件可独立决定是否继续传递请求(短路) 2. 中间件顺序决定执行优先级(先限流后鉴权) 3. 响应按逆序经过中间件,可统一封装响应格式 4. 中间件可携带状态(如限流器实例、缓存连接)

3.5 构建可扩展的 HTTP 骨架

基于上述概念,我们可以构建一个支持中间件插拔的 HTTP 服务骨架,为后续集成限流、熔断和权限验证奠定基础。

3.5.1 骨架代码结构

package main

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

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

// 路由组:包含路径前缀和中间件列表
type RouteGroup struct {
    prefix      string
    middlewares []Middleware
    mux         *http.ServeMux
}

// 创建路由组
func NewRouteGroup(prefix string) *RouteGroup {
    return &RouteGroup{
        prefix: prefix,
        mux:    http.NewServeMux(),
    }
}

// 为路由组添加中间件
func (g *RouteGroup) Use(middlewares ...Middleware) {
    g.middlewares = append(g.middlewares, middlewares...)
}

// 注册路由并应用组中间件
func (g *RouteGroup) Handle(pattern string, handler http.Handler) {
    fullPattern := g.prefix + pattern
    // 应用组中间件
    for i := len(g.middlewares) - 1; i >= 0; i-- {
        handler = g.middlewares[i](handler)
    }
    g.mux.Handle(fullPattern, handler)
}

func (g *RouteGroup) HandleFunc(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) {
    g.Handle(pattern, http.HandlerFunc(handlerFunc))
}

// 超时中间件示例
func TimeoutMiddleware(timeout time.Duration) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // 使用 http.TimeoutHandler 包装
            timeoutHandler := http.TimeoutHandler(next, timeout, "Request timeout")
            timeoutHandler.ServeHTTP(w, r)
        })
    }
}

func main() {
    // 创建 API 路由组
    apiGroup := NewRouteGroup("/api/v1")
    apiGroup.Use(
        LoggingMiddleware,
        TimeoutMiddleware(5*time.Second),
    )

    // 注册业务路由
    apiGroup.HandleFunc("/users", listUsers)
    apiGroup.HandleFunc("/orders", listOrders)

    // 启动服务
    server := &http.Server{
        Addr:         ":8080",
        Handler:      apiGroup.mux,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    log.Println("Server starting on :8080")
    log.Fatal(server.ListenAndServe())
}
设计建议: 在限流熔断骨架中,建议将中间件分为两类:全局中间件(如日志、恢复、超时)和路由组中间件(如限流、鉴权)。全局中间件在顶层 Server 级别注入,路由组中间件在特定 API 前缀下生效,实现精细化的控制粒度。

3.6 本章小结

通过本章的学习,我们掌握了以下核心能力:

  • 使用 net/http 标准库快速搭建 HTTP 服务,理解 Handler、ServeMux、Server 三者的协作关系。
  • 掌握路由注册的精确匹配与子树匹配规则,避免常见路由陷阱。
  • 理解中间件的本质是 Handler 的包装器,通过链式组合实现横切关注点的解耦。
  • 构建了可扩展的 HTTP 骨架,支持按路由组灵活配置中间件,为后续集成限流、熔断与权限验证提供了清晰的架构基础。