Menu
CoddyTech

作为装饰器的中间件

本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 97 课,共 107 课。

中间件是 Go 中装饰器模式最实用的应用之一。在 Web 开发中,中间件函数会包装 HTTP 处理程序,以添加日志记录、身份验证或计时等横切关注点,而无需修改原始处理程序。

关键在于,中间件接收一个处理器,并返回一个添加了新行为的处理器。这正是装饰器模式:输入和输出的接口相同,但功能得到增强:

type Handler func(request string) string

func LoggingMiddleware(next Handler) Handler {
    return func(request string) string {
        result := next(request)
        return "[LOG] " + result
    }
}

func AuthMiddleware(next Handler) Handler {
    return func(request string) string {
        return "[AUTH] " + next(request)
    }
}

每个中间件都会包装链中的下一个处理器,在调用被包装的处理器之前或之后添加自己的行为。你可以通过组合多个中间件来将它们叠加起来:

func MainHandler(request string) string {
    return "Response to: " + request
}

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] 对: GET /users的响应

包装的顺序决定了执行顺序:最外层的中间件会先运行。这种模式广泛用于 Go Web 框架的请求处理管道中,因此你可以轻松添加或移除功能,而无需修改核心处理程序逻辑。

challenge icon

挑战

简单

让我们使用“中间件装饰器”模式来构建请求处理流水线!你将创建一个系统,让中间件函数包装处理程序,从而添加计时、验证和格式化等功能,同时不修改核心处理程序的逻辑。

你会将代码分成 3 个文件:

  • handler.go:定义处理程序类型,以及用于处理请求的基础处理程序。

    创建一个 Handler 类型,它是一个接收请求字符串并返回响应字符串的函数:

    type Handler func(request string) string

    实现一个 BaseHandler 函数,让它对接收到的任何请求都返回 Processed: [request]。

  • middleware.go:创建用于包装处理程序并添加额外行为的中间件函数。

    编写 3 个中间件函数,每个函数接收一个 Handler,并返回一个新的 Handler:

    • TimingMiddleware:为响应添加计时信息,返回 [TIMING] [response]
    • ValidationMiddleware:为响应添加验证前缀,返回 [VALID] [response]
    • UppercaseMiddleware:将整个响应转换为大写

    每个中间件都应调用被包装的处理程序,并增强它的结果。

  • main.go:通过组合中间件来构建处理流水线。

    读取要应用的中间件层数。然后,对每一层读取中间件类型(timing、validation 或 uppercase),并用该中间件包装当前处理程序。按读取顺序应用中间件:第一个读到的中间件会成为最外层包装器。

    构建流水线后,读取请求字符串,通过组合后的处理程序处理请求,并打印结果。

输入如下:

  • 第 1 行:中间件层数
  • 后续各行:每行一个中间件类型(timing、validation 或 uppercase)
  • 最后一行:要处理的请求

例如,给定:

2
timing
validation
GET /users

输出应为:

[TIMING] [VALID] Processed: GET /users

再例如,给定:

3
uppercase
timing
validation
POST /data

输出应为:

[TIMING] [VALID] PROCESSED: POST /DATA

再例如,给定:

1
uppercase
hello world

输出应为:

PROCESSED: HELLO WORLD

再例如,给定:

0
simple request

输出应为:

Processed: simple request

注意,中间件的包装顺序会影响输出:第一个应用的中间件(最外层)会先运行,并接收到所有内层中间件处理后的最终结果。在第二个示例中,大写中间件最后运行(位于最内层),因此它会先将基础响应转换为大写,然后计时和验证中间件再添加各自的前缀!

亲手试试

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 读取中间件层数
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

	// 读取中间件类型
	middlewareTypes := make([]string, n)
	for i := 0; i < n; i++ {
		line, _ := reader.ReadString('\n')
		middlewareTypes[i] = strings.TrimSpace(line)
	}

	// 读取请求
	request, _ := reader.ReadString('\n')
	request = strings.TrimSpace(request)

	// TODO:从 BaseHandler 开始

	// TODO:用中间件包装处理器,构建处理流程
	// 按顺序应用中间件,先读取的中间件成为最外层包装
	// 提示:按逆序遍历 middlewareTypes,即可实现这一点
	// 最后应用的中间件位于最外层(最先运行)

	// TODO:通过组合后的处理器处理请求,并打印结果
	fmt.Println("TODO: implement pipeline")
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Go 在线编译器