• Mygin实现中间件Middleware


    本篇是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/!
    

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


    __EOF__

  • 本文作者: pengb
  • 本文链接: https://www.cnblogs.com/pengb/p/17984996
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    数据驱动的软件智能化开发| ChinaOSC
    l8-d7 实现TCP通信
    CentOS源码更新Linux最新内核
    计算每组的前 N 名
    JavaScript-DOM
    Java中时间工具类
    官宣 | 效率源文档修复神器正式出道:超高性价比工具,破损文档1秒修复
    Flutter 中的单元测试:从工作流基础到复杂场景
    Flowable(四):关于CICD持续集成部署
    问题记录--VSCode、CMake、MSVC编译,可执行文件无输出
  • 原文地址:https://www.cnblogs.com/pengb/p/17984996