42·网络编程进阶

Handler 与中间件

handlermiddlewarehandlerfunc

第42课:Handler 与中间件

学习目标

  1. 理解 http.Handler 接口的核心定义与作用
  2. 掌握 http.HandlerFunc 适配器的使用方法
  3. 学会创建自定义的 HTTP Handler 处理函数
  4. 理解中间件(Middleware)的设计模式与工作原理
  5. 能够实现一个简单的日志记录中间件

核心概念

1. Handler 接口

在 Go 的 net/http 包中,Handler 是一个接口,它只定义了一个方法:

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

任何实现了 ServeHTTP 方法的类型都可以用来处理 HTTP 请求。你可以把它想象成一个收银台:客户(请求)来了,收银员(Handler)负责处理结账(生成响应)。

2. HandlerFunc 适配器

为了方便使用普通函数作为 Handler,Go 提供了一个适配器类型:

type HandlerFunc func(ResponseWriter, *Request)

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

这就像给普通员工(函数)发放了收银员资格证书(实现 Handler 接口),使其可以直接上岗。

3. 中间件模式

中间件是一种拦截器模式,它像洋葱一样层层包裹着真正的业务处理器。请求从外到内穿过各层中间件,响应则从内到外穿出。每个中间件可以:

  • 在调用下一层之前执行代码(请求阶段)
  • 调用下一层处理
  • 在下一层返回之后执行代码(响应阶段)

常见的中间件功能:日志记录、身份认证、CORS 处理、限流等。

代码示例

示例1:基础 Handler 使用

package main

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

// 自定义 Handler 结构体
type GreetHandler struct {
    Greeting string
}

// 实现 Handler 接口
func (h *GreetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "%s, World!", h.Greeting)
}

// 使用 HandlerFunc 适配器
func aboutHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "This is the about page.")
}

func main() {
    // 创建自定义 Handler 实例
    greet := &GreetHandler{Greeting: "Hello"}
    
    // 注册路由
    http.Handle("/greet", greet)
    http.HandleFunc("/about", aboutHandler)
    
    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

示例2:实现日志中间件

package main

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

// 真正的业务处理器
func homeHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Welcome to the homepage!"))
}

// 日志中间件:记录请求信息
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 %s %v",
            r.Method,
            r.RequestURI,
            r.RemoteAddr,
            time.Since(start),
        )
    })
}

// 认证中间件示例
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 检查认证 token
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return // 注意:这里要 return,否则会继续执行
        }
        
        // 认证通过,继续处理
        next.ServeHTTP(w, r)
    })
}

func main() {
    // 创建处理器
    mux := http.NewServeMux()
    mux.HandleFunc("/", homeHandler)
    
    // 组合中间件:先认证,再日志,最后是业务处理器
    // 注意顺序:请求是从外到内(auth -> logging -> home)
    finalHandler := authMiddleware(loggingMiddleware(mux))
    
    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", finalHandler))
}

实践练习

练习1:基础练习(易)

创建一个中间件,记录每个请求的处理时间(毫秒)。

// 你的代码实现
func timingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 记录开始时间
        start := time.Now()
        
        // 调用下一个处理器
        
        // 计算并打印耗时
    })
}

预期输出:控制台显示类似 请求处理耗时: 123µs 的日志

练习2:中等练习(中)

实现一个 IP 白名单中间件,只允许特定 IP 访问。

var allowedIPs = []string{"127.0.0.1", "::1"}

func ipWhitelistMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        clientIP := r.RemoteAddr // 注意:实际可能需要解析 IP 部分
        
        // 检查 IP 是否在白名单中
        // 如果不在,返回 403 Forbidden
        // 如果在,调用 next.ServeHTTP
    })
}

预期输出:非白名单 IP 访问时返回 403 错误

练习3:综合练习(难)

创建一个 "健康检查" 中间件链:

  1. 第一个中间件添加 X-Request-ID 响应头(使用 UUID)
  2. 第二个中间件记录请求到标准输出
  3. 最终处理器返回状态码 200 和 "OK"

预期输出:响应包含 X-Request-ID 头,且控制台有日志

常见错误

  1. 忘记调用 next.ServeHTTP()

    // 错误:中间件被中断,后续处理器不会执行
    func badMiddleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            log.Println("before")
            // 缺少 next.ServeHTTP(w, r)
            log.Println("after")
        })
    }
    
  2. 中间件顺序错误

    // 错误:认证应该在业务处理之前
    finalHandler := loggingMiddleware(authMiddleware(mux)) // 错误顺序!
    
    // 正确:auth -> logging -> business
    finalHandler := authMiddleware(loggingMiddleware(mux))
    
  3. 在中间件中错误地结束响应

    func middleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if someCondition {
                http.Error(w, "error", 500)
                // 错误:没有 return,会继续执行 next
            }
            next.ServeHTTP(w, r)
        })
    }
    
  4. 误用 http.HandleFunchttp.Handle

    // 错误:HandleFunc 期望函数,Handle 期望 Handler
    http.Handle("/path", func(w http.ResponseWriter, r *http.Request) {}) // 编译错误
    
    // 正确:
    http.HandleFunc("/path", func(w http.ResponseWriter, r *http.Request) {})
    

小结

  1. Handler 接口http.Handler 是 Go Web 服务的核心,所有请求处理器都需要实现 ServeHTTP 方法
  2. HandlerFunc:通过 http.HandlerFunc 适配器,普通函数可以直接用作 Handler
  3. 中间件模式:通过包装 Handler 实现功能增强,遵循洋葱模型
  4. 中间件链:可以像俄罗斯套娃一样层层嵌套,形成处理链
  5. 常见应用:日志、认证、限流、CORS、压缩等都可以用中间件实现

下一课我们将学习如何设计规范的 REST API,利用今天学到的 Handler 和中间件知识构建生产级的 Web 服务。

练习编辑器

go
Loading...

继续学习

完成本课后,建议继续学习下一课「REST API 设计与实现」 以巩固所学知识。