做好每一件事,读好每一本书,天道酬勤
七天实现web框架--中间件的实现
2022-04-16 / 4 min read

在web开发中,我们需要在真正的逻辑处理前,我们有些操作需要提前进行处理,同时逻辑处理后,我们需要处理其他的逻辑。

什么是中间件

在正式的业务逻辑处理前或之后,我们需要进行一些处理。这些处理是需要我们添加中间件来进行添加的。因为在框架中,我们需要给用户自己添加一些处理方式函数的地方的,这个地方呢我们不能太过于底层,这样对于用户来说肯定是不好的。
所以我们需要一个比较好的方式进行添加。

实现思路

对于这个中间件的添加,逻辑我们也是很简单的,我们使用一个切片进行存储函数来实现,首先是需要在路由中进行存储的,然后我们将这个东西放在上下文中去进行存储。

type RouterGroup struct {
	prefix      string
	middlewares []HandlerFunc // support middleware
	parent      *RouterGroup  // support nesting
	engine      *Engine       // all groups share a Engine instance
}

上下文中的存储

type Context struct {
	// origin objects
	Writer http.ResponseWriter
	Req    *http.Request
	// request info
	Path   string
	Method string
	Params map[string]string
	// response info
	StatusCode int
	// middleware
	handlers []HandlerFunc
	index    int
}

func (c *Context) Next() {
	c.index++
	s := len(c.handlers)
	for ; c.index < s; c.index++ {
		c.handlers[c.index](c)
	}
}

为什么在路由中进行存储了我们还需要在上下文进行存储呢?
我们可以看到,在这里我们还实现了一个函数,就是next,关于next函数其实就是我们将中间件分成了两个部分,一个是处理业务代码前处理,和业务代码处理后处理,那么在next前的就是业务逻辑前,然后后面就是业务逻辑后。然后这里我们做一个演示:

func A(c *Context) {
    part1
    c.Next()
    part2
}
func B(c *Context) {
    part3
    c.Next()
    part4
}

我们来看一下上面这个函数的执行顺序:
假设我们应用了中间件 A 和 B,和路由映射的 Handler。c.handlers是这样的[A, B, Handler],c.index初始化为-1。调用c.Next(),接下来的流程是这样的:

c.index++,c.index 变为 0
0 < 3,调用 c.handlers[0],即 A
执行 part1,调用 c.Next()
c.index++,c.index 变为 1
1 < 3,调用 c.handlers[1],即 B
执行 part3,调用 c.Next()
c.index++,c.index 变为 2
2 < 3,调用 c.handlers[2],即Handler
Handler 调用完毕,返回到 B 中的 part4,执行 part4
part4 执行完毕,返回到 A 中的 part2,执行 part2
part2 执行完毕,结束。
一句话说清楚重点,最终的顺序是part1 -> part3 -> Handler -> part 4 -> part2。恰恰满足了我们对中间件的要求,接下来看调用部分的代码,就能全部串起来了。

测试函数

func onlyForV2() gee.HandlerFunc {
	return func(c *gee.Context) {
		// Start timer
		t := time.Now()
		// if a server error occurred
		c.Fail(500, "Internal Server Error")
		// Calculate resolution time
		log.Printf("[%d] %s in %v for group v2", c.StatusCode, c.Req.RequestURI, time.Since(t))
	}
}

func main() {
	r := gee.New()
	r.Use(gee.Logger()) // global midlleware
	r.GET("/", func(c *gee.Context) {
		c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
	})

	v2 := r.Group("/v2")
	v2.Use(onlyForV2()) // v2 group middleware
	{
		v2.GET("/hello/:name", func(c *gee.Context) {
			// expect /hello/geektutu
			c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)
		})
	}

	r.Run(":9999")
}