Mygin实现中间件Middleware

时间:2024-01-26 18:37:25

本篇是mygin的第六篇,参照gin框架,感兴趣的可以从 Mygin第一篇 开始看,Mygin从零开始完全手写,在实现的同时,带你一窥gin框架的核心原理实现。

目的

  • 实现中间件Middleware
    在上一篇 Mygin实现分组路由Group 中,实现了路由分组,且同一分组的执行,会先执行Group,有一点点中间件的雏形了。但是中间件不完全还应该提供中断功能,比如一个Group组中添加了auth鉴权中间件,只有auth认证通过才可以通过,因此需要对上篇中的内容进行一些修改。
    在实现之前,先分析gin中是怎样去实现的这一功能的
func (c *Context) Next() {
	c.index++
    //遍历handlers
	for c.index < int8(len(c.handlers)) {
        //真正调用执行handler方法
		c.handlers[c.index](c)
		c.index++
	}
}

这个时候就有疑问了,从上述方法中看不到中间件执行失败的中断方法,那又是怎么实现中断。
在揭晓答案之前,先看看int8(len(c.handlers) 为什么要写个int8,原因在于gin中规定的handlers最多63个,相信实际的应用请求中,没有超过63个那么多变态的执行链。在gin中如果某一中间件执行失败,就把c.index赋值为63,上述for循环就不满足条件,因此就跳出for循环,不再继续执行后面的代码。gin中对应的代码也很简单。

const abortIndex int8 = math.MaxInt8 >> 1
//中间件执行失败,中断方法
func (c *Context) Abort() {
	c.index = abortIndex
}

因此只需在mygin/content.go中新加Next方法和Abort方法

上下文

content.go中的代码不多,索性加上注释全部贴出来。

  • mygin/content.go
package mygin

import (
	"encoding/json"
	"math"
	"net/http"
)

// 定义 表示最大和上下文应中止时的索引值
const abortIndex int8 = math.MaxInt8 >> 1

// Context 封装了一个HTTP请求的上下文
type Context struct {
	Request *http.Request
	Writer  http.ResponseWriter
	Params  Params
	index   int8
}

// Next 执行链中的剩余处理程序。
func (c *Context) Next(handlers HandlersChain) {
	//遍历handlers
	for c.index < int8(len(handlers)) {
		//真正调用执行handler方法
		handlers[c.index](c)
		c.index++
	}
}

// Abort 中断链中剩余处理程序的执行。
func (c *Context) Abort() {
	c.index = abortIndex
}

// IsAborted 如果当前上下文被中止,则返回true。
func (c *Context) IsAborted() bool {
	return c.index >= abortIndex
}

// writeContentType 如果尚未设置,则设置Content-Type标头。
func writeContentType(w http.ResponseWriter, value []string) {
	header := w.Header()
	if val := header["Content-Type"]; len(val) == 0 {
		header["Content-Type"] = value
	}

}

// Status 设置HTTP响应状态码。
func (c *Context) Status(code int) {
	c.Writer.WriteHeader(code)
}

// JSON 将值序列化为JSON并将其写入响应。
func (c *Context) JSON(v interface{}) error {
	writeContentType(c.Writer, []string{"application/json; charset=utf-8"})
	encoder := json.NewEncoder(c.Writer)
	err := encoder.Encode(v)
	if err != nil {
		c.Status(http.StatusInternalServerError)
	}
	c.Status(http.StatusOK)
	return err
}

// Html 将字符串以HTML形式写入响应。
func (c *Context) Html(v string) error {
	writeContentType(c.Writer, []string{"text/html; charset=utf-8"})
	c.Status(http.StatusOK)
	_, err := c.Writer.Write([]byte(v))
	return err
}

// String 将字符串写入响应
func (c *Context) String(v string) error {
	writeContentType(c.Writer, []string{"text/plain; charset=utf-8"})
	c.Status(http.StatusOK)
	_, err := c.Writer.Write([]byte(v))
	return err
}

接下来就是调用handles的修改了,原来的解决方法是直接循环调用,对应的代码如下:

for _, handler := range handlers {
		handler(&Context{
			Request: r,
			Writer:  w,
			Params:  params,
		})
	}

引擎

  • mygin/engine.go
    现在找到engine.go文件中将上面的代码替换为:
	//实例化一个下上文
	c := &Context{
		Request: r,
		Writer:  w,
		Params:  params,
	}
	// 执行处理函数链
	c.Next(handlers)

测试代码

package main

import (
	"gophp/mygin"
	"path"
)

func main() {
	// 创建一个默认的 mygin 实例
	r := mygin.Default()

	//测试Abort
	group := r.Group("/api", func(context *mygin.Context) {
		//todo....
		context.String("api Group 中间件失败了....\n")
		context.Abort()
	})
	//这个回调不会执行
	group.GET("/hello/:name", func(context *mygin.Context) {
		name := context.Params.ByName("name")
		context.String(path.Join("hello ", name, "!"))
	})

	//测试没有发生Abort
	group2 := r.Group("/api2", func(context *mygin.Context) {
		//todo....
		context.String("api Group 中间件成功了....\n")
	})
	
	//这个回调会执行
	group2.GET("/hello2/:name", func(context *mygin.Context) {
		name := context.Params.ByName("name")
		context.String(path.Join("hello2 ", name, "!\n"))
	})

	// 启动服务器并监听端口
	r.Run(":8088")
}

curl测试

 curl http://127.0.0.1:8088/api/hello/scott
api Group 中间件失败了....
~ curl http://127.0.0.1:8088/api2/hello2/scott
api Group 中间件成功了....
hello2 /scott/!

看到上诉输出,即为成功。