# 8.01 课后复习-AOP

对责任链模式不太了解的读者建议先翻看一下[责任链模式简介](https://github.com/step-by-step-wiki/GoBook/blob/main/PART09.Appendix/附录1.责任链模式.md)

## PART1. 非集中式的设计方案--洋葱模式

### 1.1 定义Middleware

`middleware.go`:

```go
package web

// Middleware 中间件
type Middleware func(HandleFunc) HandleFunc
```

这种入参和返回值均为函数类型的设计,是函数式编程.

### 1.2 编排Middleware的顺序

实际上这一步就是将多个Middleware组建成一条责任链

#### 1.2.1 定义中间件链

`httpServer.go`:

```go
package web

import (
	"net"
	"net/http"
)

// HTTPServer HTTP服务器
type HTTPServer struct {
	router                   // router 路由树
	middlewares []Middleware // middlewares Server级别的中间件链 实际上就是责任链 所有的请求都会经过这个链的处理
}
```

#### 1.2.2 构建中间件链

需要注意的是,构建中间件链的顺序,和中间件的执行顺序是相反的.换言之,也就是说最后一个被执行的中间件是最先被组装到责任链上的

`httpServer.go`:

```go
package web

import (
	"net"
	"net/http"
)

// HTTPServer HTTP服务器
type HTTPServer struct {
	router                   // router 路由树
	middlewares []Middleware // middlewares Server级别的中间件链 实际上就是责任链 所有的请求都会经过这个链的处理
}

// ServeHTTP WEB框架入口
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 构建上下文
	ctx := &Context{
		Req:  r,
		Resp: w,
	}

	// 构建责任链
	// step1. 找到请求对应的HandleFunc
	root := s.serve
	for i := len(s.middlewares) - 1; i >= 0; i-- {
		// step2. 从后往前构建责任链
		// Tips: 组装的过程是从后向前的 也就是说最后一个被执行的中间件是最先被组装到责任链上的
		root = s.middlewares[i](root)
	}

	// 从责任链的头部开始执行
	root(ctx)
}
```

这里之所以要先找到请求对应的HandleFunc,是因为请求在经过了整条中间件链后,最终还是要去执行这个HandleFunc.

你可能有所疑问的地方在于:如果我找不到请求对应的HandleFunc,那为什么还要走这一整条中间件链?

我个人的理解是:**因为这条中间件链是Server级别的**.就如同Nginx的access.log,即使访问了一个不存在的url,同样也会在access.log中记录一条404的日志

## PART2. 测试

`middleware_test.go`:

```go
package web

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

// Test_Middleware 测试中间件的工作顺序
func Test_Middleware(t *testing.T) {
	s := NewHTTPServer()

	s.middlewares = []Middleware{
		Middleware1,
		Middleware2,
		Middleware3,
		Middleware4,
	}

	s.ServeHTTP(nil, &http.Request{})
}

func Middleware1(next HandleFunc) HandleFunc {
	return func(ctx *Context) {
		fmt.Println("中间件1开始执行")
		next(ctx)
		fmt.Println("中间件1结束执行")
	}
}

func Middleware2(next HandleFunc) HandleFunc {
	return func(ctx *Context) {
		fmt.Println("中间件2开始执行")
		next(ctx)
		fmt.Println("中间件2结束执行")
	}
}

func Middleware3(next HandleFunc) HandleFunc {
	return func(ctx *Context) {
		fmt.Println("中间件3中断后续中间件的执行")
	}
}

func Middleware4(next HandleFunc) HandleFunc {
	return func(ctx *Context) {
		fmt.Println("中间件4不会被执行")
	}
}
```

测试结果:

```
中间件1开始执行
中间件2开始执行
中间件3中断后续中间件的执行
中间件2结束执行
中间件1结束执行
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://go.sai.show/part08.review/8.01-ke-hou-fu-xi-aop.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
