geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。
https://geektutu.com
Apache License 2.0
166 stars 21 forks source link

Go语言动手写Web框架 - Gee第五天 中间件Middleware | 极客兔兔 #45

Open geektutu opened 4 years ago

geektutu commented 4 years ago

https://geektutu.com/post/gee-day5.html

7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,以 Gin 为原型从零设计一个Web框架。本文介绍了如何为Web框架添加中间件的功能(middlewares)。

hu-xiaokang commented 4 years ago
func (c *Context) Next() {
    c.index++
    s := len(c.handlers)
    for ; c.index < s; c.index++ {
        fmt.Printf("index:%d\n", c.index)
        c.handlers[c.index](c)
    }
}

您好,想请教下为什么这个Next函数需要遍历c.handlers?因为我看hadlers函数会带c.Next(),如下Logger:

func Logger() HandlerFunc {
    return func(c *Context) {
        // Start timer
        t := time.Now()
        // Process request
        c.Next()
        // Calculate resolution time
        log.Printf("[%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t))
    }
    }

所以,为什么不直接这样写?

func (c *Context) Next() {
    c.index++
    c.handlers[c.index](c)
}

感谢!!!

geektutu commented 4 years ago

@acaibird 不是所有的handler都会调用 Next()。 手工调用 Next(),一般用于在请求前后各实现一些行为。如果中间件只作用于请求前,可以省略调用Next(),算是一种兼容性比较好的写法吧。

hu-xiaokang commented 4 years ago

@geektutu @acaibird 不是所有的handler都会调用 Next()。 手工调用 Next(),一般用于在请求前后各实现一些行为。如果中间件只作用于请求前,可以省略调用Next(),算是一种兼容性比较好的写法吧。

嗯嗯,明白了,多谢!

blackher commented 4 years ago

func (c *Context) Next() { c.index++ s := len(c.handlers) for ; c.index < s; c.index++ { c.handlers[c.index](c) } } c.index 为啥要初始化为-1 我初始化为0 去掉c.index++ 一直死循环

geektutu commented 4 years ago

@blackher Next() 函数一开始调用了 c.index++,下标恰好从 0 开始。

blackher commented 4 years ago

@geektutu 我的意思是 index 直接初始化为0 Next()就不用c.index++了 我自己尝试初始化为0 去掉c.index++ 一直死循环

geektutu commented 4 years ago

你把Next() 第一行的 c.index++ 去掉,就会一直卡在第一个中间件上了,必然死循环。每调用一次 Next()c.index 得 +1,不然 c.index 就会一直是 0。

死循环之后,是不会走到 for 语句后面的 c.index++ 的。

ReviveKwan commented 4 years ago

@geektutu 提问一下,这样写不是会凡是用到 onlyForV2的 都会报500的错吗?,应该不是遇到错误报500吧?

ronething-bot commented 4 years ago

Context Fail 方法

func (c *Context) Fail(code int, err string) {
    c.index = len(c.handlers)
    c.JSON(code, H{"message": err})
}

c.index = len(c.handlers) 可否置为 c.index = len(c.handlers) - 1

SourceLink commented 4 years ago
func A(c *Context) {
    // 中间件上半部
    part1
    c.Next()
    // 中间件下半部
    part2
}

感觉这样解释比较容易理解

proverbs commented 4 years ago

首先谢谢博主,文章写得很好,思路很清晰。 然后我有个问题,你这个middleware的实现执行是无序的。所以是不是把middleware放在trie的node里面,然后在search的时候collect所有的middlewares,这样更好?

seascheng commented 3 years ago

@proverbs 首先谢谢博主,文章写得很好,思路很清晰。 然后我有个问题,你这个middleware的实现执行是无序的。所以是不是把middleware放在trie的node里面,然后在search的时候collect所有的middlewares,这样更好?

代码看来,因为routerGroups中所有的RouterGroup,在添加时肯定是按照先父再子的顺序添加的,所以从头到尾遍历routerGroups这种简单的收集方式,也就是达成了实际路径中从左向右的查找过程。 所以顺序是可以保证的。

当然这种收集方式有个前提:分组中所有的前缀必须是静态路径,否则使用实际路由从全部分组做前缀匹配,无法保证收集的完整性。这也给上节的分组控制的使用加了限制。

如果我理解错了,还请指出。

void1104 commented 3 years ago

博主,能否请教一个基础问题,为什么Context中,Writer不用指针,而Req却要用指针呢?

geektutu commented 3 years ago

@void1104 这里和 http ServerHTTP 函数原始的两个参数对应 req 和 writer。req 是结构体,用指针可以节省内存,Writer 是一个接口类型,不能用指针。

package http

type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}
void1104 commented 3 years ago

理解了,谢谢大佬

------------------ 原始邮件 ------------------ 发件人: "geektutu/geektutu-blog" <notifications@github.com>; 发送时间: 2020年12月1日(星期二) 中午1:23 收件人: "geektutu/geektutu-blog"<geektutu-blog@noreply.github.com>; 抄送: "彭家鑫"<731009209@qq.com>;"Mention"<mention@noreply.github.com>; 主题: Re: [geektutu/geektutu-blog] Go语言动手写Web框架 - Gee第五天 中间件Middleware | 极客兔兔 (#45)

@void1104 这里和 http ServerHTTP 函数原始的两个参数对应 req 和 writer。req 是结构体,用指针可以节省内存,Writer 是一个接口类型,不能用指针。 package http type Handler interface { ServeHTTP(w ResponseWriter, r *Request) }

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

YoshieraHuang commented 3 years ago

@geektutu @acaibird 不是所有的handler都会调用 Next()。 手工调用 Next(),一般用于在请求前后各实现一些行为。如果中间件只作用于请求前,可以省略调用Next(),算是一种兼容性比较好的写法吧。

你好,大佬写的文章很受用。有个问题想问一下,在使用循环调用handler的话,如何实现类似于gin中的c.Abort()这种中间件的退出机制呢?

geektutu commented 3 years ago

你好,大佬写的文章很受用。有个问题想问一下,在使用循环调用handler的话,如何实现类似于gin中的c.Abort()这种中间件的退出机制呢?

@YoshieraHuang context 中维护一个状态值,调用 c.Abort() 改变状态,循环时检查状态,发现已经中止停止循环即可。

YoshieraHuang commented 3 years ago

你好,大佬写的文章很受用。有个问题想问一下,在使用循环调用handler的话,如何实现类似于gin中的c.Abort()这种中间件的退出机制呢?

@YoshieraHuang context 中维护一个状态值,调用 c.Abort() 改变状态,循环时检查状态,发现已经中止停止循环即可。

理解了,谢谢大佬。

MoneyHappy commented 3 years ago
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))
    }
}

这里进来不都进行 c.Fail(500, "Internal Server Error") 这一步报错了么?没太理解,麻烦大佬有空解释下😂 @geektutu

hyicode commented 3 years ago

@MoneyHappy

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))
  }
}

这里进来不都进行 c.Fail(500, "Internal Server Error") 这一步报错了么?没太理解,麻烦大佬有空解释下😂 @geektutu

这个是中间件功能的示例,这里应该是用发送500错误码来表示中间件的起作用了

Nick233333 commented 3 years ago

目前的中间件设计无法支持指定路由使用中间件吧

walkmiao commented 3 years ago

这是短路中间件,如果使用 后续的中间件和handler就直接跳过了 c.index = len(c.handlers)

jqb44179 commented 3 years ago

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) }) } 这个{}的用法是什么含义呀

jqb44179 commented 3 years ago

学习日志: 经过debug调试,运行后在c.handlers里面会插入需要执行的方法,以/index为例 在c.handers里面就有有以下的内容 1、gee.Logger() 2、main.func 在运行gee.Logger()的时候会显式的调用Next,将会跳转去执行第二步main.func,这时c.index的状态变化如下 1、c.index = -1 // 初始化 2、c.index = 0 // logger 3、c.index = 1 // main 最后将返回logger里面记录的耗时日志

whitebluepants commented 3 years ago

@jqb44179 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) }) } 这个{}的用法是什么含义呀

这里大括号的作用是开创一个新的语法块,或者说作用域,也就是里面定义的同名变量会覆盖外层。在这里的作用我个人觉得更多的是可读性吧,能够清楚知道每个分组的路由设置情况

whitebluepants commented 3 years ago

@geektutu @void1104 这里和 http ServerHTTP 函数原始的两个参数对应 req 和 writer。req 是结构体,用指针可以节省内存,Writer 是一个接口类型,不能用指针。

package http

type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}

我思考和尝试了一下,接口类型确实不能用指针,不然实现这个接口的类型变量就传不进来(或者说类型转换?). 但是在ide上定义一个指针接口倒没有报错,说明确实有指针接口这种东西。想请教下什么时候会用到指针接口呢,网上涉及这个也有点少。

oi8io commented 3 years ago
    v1 := engine.Group("/v1")
    v1.Use(gee.Logger())
    v1.Use(gee.Logger())
    {
        v1.Get("/hello", hello)
        v1.Get("/:lang/say_hello", SayHello)
        v1.Get("/:lang/say_bye/*", SayBye)
    }
    v1Say := v1.Group("/say")
    v1Say.Use(gee.Logger())
    {
        v1Say.Get("/hello", hello)
        v1Say.Get("/:lang/say_hello", SayHello)
        v1Say.Get("/:lang/say_bye/*", SayBye)
    }

博主,如果子group里面使用中间件或者重复使用中间件,这样会导致重复的问题,这个问题是合理还是不合理,因为中间件有什么好办法去重么?用map或者采用指针判断吗?

puck1006 commented 3 years ago

@oidbio

  v1 := engine.Group("/v1")
  v1.Use(gee.Logger())
  v1.Use(gee.Logger())
  {
      v1.Get("/hello", hello)
      v1.Get("/:lang/say_hello", SayHello)
      v1.Get("/:lang/say_bye/*", SayBye)
  }
  v1Say := v1.Group("/say")
  v1Say.Use(gee.Logger())
  {
      v1Say.Get("/hello", hello)
      v1Say.Get("/:lang/say_hello", SayHello)
      v1Say.Get("/:lang/say_bye/*", SayBye)
  }

博主,如果子group里面使用中间件或者重复使用中间件,这样会导致重复的问题,这个问题是合理还是不合理,因为中间件有什么好办法去重么?用map或者采用指针判断吗?

这应该没必要管吧. 使用中间键的权利暴露给用户. 用户要犯傻也没办法呀啊

cuglaiyp commented 3 years ago

onlyForV2中出现的Context的Fail方法,在前面的教程中好像漏掉了

pzx521521 commented 3 years ago

@MoneyHappy

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))
  }
}

这里进来不都进行 c.Fail(500, "Internal Server Error") 这一步报错了么?没太理解,麻烦大佬有空解释下😂 @geektutu

应该是写多了改为 c.String(500, "Internal Server Error") 即可

galaxyzen commented 2 years ago

@hu-xiaokang

func (c *Context) Next() {
  c.index++
  s := len(c.handlers)
  for ; c.index < s; c.index++ {
      fmt.Printf("index:%d\n", c.index)
      c.handlers[c.index](c)
  }
}

您好,想请教下为什么这个Next函数需要遍历c.handlers?因为我看hadlers函数会带c.Next(),如下Logger:

func Logger() HandlerFunc {
  return func(c *Context) {
      // Start timer
      t := time.Now()
      // Process request
      c.Next()
      // Calculate resolution time
      log.Printf("[%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t))
  }
  }

所以,为什么不直接这样写?

func (c *Context) Next() {
  c.index++
  c.handlers[c.index](c)
}

感谢!!!

在中间件A中调用c.Next()的作用:在执行完当前A中间件之前,先执行它后面的那些中间件,后面的中间件执行完,中间件A继续执行c.Next()后面的语句。

dteer commented 2 years ago

请问一下直接去执行handlers就可以了,为什么还要多维护一个index,

christopherChenYW commented 2 years ago

func onlyForV1() gee.HandlerFunc { return func(c gee.Context) { // Start timer fmt.Println(1) } } func onlyForV2() gee.HandlerFunc { return func(c gee.Context) { // Start timer fmt.Println(2) } }

func main() { r := gee.New()

v2 := r.Group("/v1/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)
    })
}

v1 := r.Group("/v1")
v1.Use(onlyForV1()) // v2 group middleware
r.Run(":9999")

} 这样是先打印出了2,然后1.理论上v1/v2 应该先执行1然后2,或许应该对middleware排个序?

junbin-yang commented 2 years ago

@geektutu

你好,大佬写的文章很受用。有个问题想问一下,在使用循环调用handler的话,如何实现类似于gin中的c.Abort()这种中间件的退出机制呢?

@YoshieraHuang context 中维护一个状态值,调用 c.Abort() 改变状态,循环时检查状态,发现已经中止停止循环即可。

不维护状态值,直接c.index = len(c.handlers)也可以吧

xionghao666 commented 2 years ago

兔兔大佬真棒!这个从c.Next()方法设计真的很独到,看了很久才明白,next方法第一行index++,和for循环的index++同时作用保证了这个嵌套循环内的每个handler方法只执行一次,执行到最内层index已经是最大值了,外层的嵌套循环就直接结束了

jxy90 commented 2 years ago

请问一下直接去执行handlers就可以了,为什么还要多维护一个index,

@dter 因为在中间件中还可以添加c.Next()调用.如果没有index,单纯的循环,会导致中间件重复

jxy90 commented 2 years ago

func onlyForV1() gee.HandlerFunc { return func(c gee.Context) { // Start timer fmt.Println(1) } } func onlyForV2() gee.HandlerFunc { return func(c gee.Context) { // Start timer fmt.Println(2) } }

func main() { r := gee.New()

v2 := r.Group("/v1/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)
  })
}

v1 := r.Group("/v1")
v1.Use(onlyForV1()) // v2 group middleware
r.Run(":9999")

} 这样是先打印出了2,然后1.理论上v1/v2 应该先执行1然后2,或许应该对middleware排个序?

@YoshieraHuang yang 讲道理这种使用group的方法,也属于测试开发者了 haha

liujing-siyang commented 2 years ago

@junbin-yang

@geektutu

你好,大佬写的文章很受用。有个问题想问一下,在使用循环调用handler的话,如何实现类似于gin中的c.Abort()这种中间件的退出机制呢?

@YoshieraHuang context 中维护一个状态值,调用 c.Abort() 改变状态,循环时检查状态,发现已经中止停止循环即可。

不维护状态值,直接c.index = len(c.handlers)也可以吧

如果这样的话后续所有的HandlerFunc都不会执行了,如果需要执行路由注册进来的的func就不应该将其加入到handler中,应该单独调用,这样的话就要协调其和中间件的调用顺序了?

dteer commented 2 years ago

问下大佬,有个关于技术选型的问题: 1.我在其他框架源码看到中间件的实现是通过 往headers队列头或尾添加执行方法,然后顺序执行,达到中间件的目的 func A(){part1} func B(){part2} 即:【funcA, header, funcB】 执行动作:【part1 header part2】 2.gin则是往headers头顺序添加执行方法,业务方法保证在最后队列,引入next达到中间件的目的 func A(){part1 c.next() part2} func B(){part3 c.next() part4} 即:【funcA,funcB,funC,funcD】 执行动作:【part1 part3 header part4 part2】 3.我想请教一下gin引入了next增加的复杂度,得到的好处超过第一种吗

junbin-yang commented 2 years ago

@liujing-siyang

@junbin-yang

@geektutu

你好,大佬写的文章很受用。有个问题想问一下,在使用循环调用handler的话,如何实现类似于gin中的c.Abort()这种中间件的退出机制呢?

@YoshieraHuang context 中维护一个状态值,调用 c.Abort() 改变状态,循环时检查状态,发现已经中止停止循环即可。

不维护状态值,直接c.index = len(c.handlers)也可以吧

如果这样的话后续所有的HandlerFunc都不会执行了,如果需要执行路由注册进来的的func就不应该将其加入到handler中,应该单独调用,这样的话就要协调其和中间件的调用顺序了?

是的,所以要看情况是否需要继续往后执行其他方法了。按照博主的教程我也撸了个项目github.com/junbin-yang/see可以参考参考,也是把中间件和最后的路由注册方法分开处理。

junbin-yang commented 2 years ago

@dteer

问下大佬,有个关于技术选型的问题: 1.我在其他框架源码看到中间件的实现是通过 往headers队列头或尾添加执行方法,然后顺序执行,达到中间件的目的 即:【funcA,funcB,header,funcC,funcD】 2.gin则是往headers头顺序添加执行方法,业务方法保证在最后队列,引入next达到中间件的目的 3.我想请教一下gin引入了next增加的复杂度,得到的好处超过第一种吗

一般在header处理之后还需要中间件的情况很少吧。不然啥叫中间件。把header和中间件分开处理。可以将header带入路由注册。匹配路由时不需要二次检索。效率更高。【funcA,funcB,funcC,funcD】【header】。 如果说header之后还需要有其他处理的话应该是返回定义好的数据类型,需要一个方法统一格式化输出,如下:情况

type WrapperFuncType func(c gin.Context) ResponseCms func Wrapper(handle WrapperFuncType) gin.HandlerFunc { return func(c gin.Context) { var resp ResponseCms = handle(c) c.PureJSON(http.StatusOK, resp) } } router.POST("/index", Wrapper(Index))

dteer commented 2 years ago

@junbin-yang

@dteer

问下大佬,有个关于技术选型的问题: 1.我在其他框架源码看到中间件的实现是通过 往headers队列头或尾添加执行方法,然后顺序执行,达到中间件的目的 即:【funcA,funcB,header,funcC,funcD】 2.gin则是往headers头顺序添加执行方法,业务方法保证在最后队列,引入next达到中间件的目的 3.我想请教一下gin引入了next增加的复杂度,得到的好处超过第一种吗

一般在header处理之后还需要中间件的情况很少吧。不然啥叫中间件。把header和中间件分开处理。可以将header带入路由注册。匹配路由时不需要二次检索。效率更高。【funcA,funcB,funcC,funcD】【header】。 如果说header之后还需要有其他处理的话应该是返回定义好的数据类型,需要一个方法统一格式化输出,如下:情况

type WrapperFuncType func(c gin.Context) ResponseCms func Wrapper(handle WrapperFuncType) gin.HandlerFunc { return func(c gin.Context) { var resp ResponseCms = handle(c) c.PureJSON(http.StatusOK, resp) } } router.POST("/index", Wrapper(Index))

gin中next的操作不就是实现在header处理前和处理后做对应操作吗

Da-Hao commented 2 years ago

@dteer

@junbin-yang

@dteer

问下大佬,有个关于技术选型的问题: 1.我在其他框架源码看到中间件的实现是通过 往headers队列头或尾添加执行方法,然后顺序执行,达到中间件的目的 即:【funcA,funcB,header,funcC,funcD】 2.gin则是往headers头顺序添加执行方法,业务方法保证在最后队列,引入next达到中间件的目的 3.我想请教一下gin引入了next增加的复杂度,得到的好处超过第一种吗

一般在header处理之后还需要中间件的情况很少吧。不然啥叫中间件。把header和中间件分开处理。可以将header带入路由注册。匹配路由时不需要二次检索。效率更高。【funcA,funcB,funcC,funcD】【header】。 如果说header之后还需要有其他处理的话应该是返回定义好的数据类型,需要一个方法统一格式化输出,如下:情况

type WrapperFuncType func(c gin.Context) ResponseCms func Wrapper(handle WrapperFuncType) gin.HandlerFunc { return func(c gin.Context) { var resp ResponseCms = handle(c) c.PureJSON(http.StatusOK, resp) } } router.POST("/index", Wrapper(Index))

gin中next的操作不就是实现在header处理前和处理后做对应操作吗

“Gee 的中间件的定义与路由映射的 Handler 一致,处理的输入是Context对象。插入点是框架接收到请求初始化Context对象后,允许用户使用自己定义的中间件做一些额外的处理,例如记录日志等,以及对Context进行二次加工。另外通过调用(*Context).Next()函数,中间件可等待用户自己定义的 Handler处理结束后,再做一些额外的操作”,这是作者在中间件设计部分核心的一段话。

自己的一些想法:1.中间件与路由映射的Handler一致,统一由context进行保存管理。 2.采用第二种方式的话,假设现在有3个中间件,即对应3个func(),你可以在其内部自定义next()的位置,控制在何时执行下一个中间件,甚至直接跳过前或后半部分。 3.采用第一种方式,同样3个中间件,可能要写6个func(),而且不利于集中管理和控制。

zdunker commented 2 years ago

其实我有些纠结当用户这样使用框架的时候,他/她的期待是什么样的:

g1 := engine.New("/g1").Use(MW1)
g2 := g1.New("/g2").Use(MW2, MW3)
g1.Use(MW4)

我觉得期待的样子应该是g1这个group有MW1MW4这两个中间件、g2这个group有MW1MW2MW3这三个中间件。

不知道大佬觉得这种use case是否合理? 如果合理的话,上面用append来添加中间件的方式是不是还有提升空间?

ZAKLLL commented 2 years ago

func (c *Context) Next() {
    c.index++
    s := len(c.handlers)
    //不是所有的handler都会调用 Next()。
    //手工调用 Next(),一般用于在请求前后各实现一些行为。如果中间件只作用于请求前,可以省略调用Next(),此种写法可以兼容 不调用Next的写法
    //并且使用c.index<s;c.index++ 也能保证中间件只执行一次
    //当中间件不调用 next函数时,通过此循环保证中间件执行顺序
    //当中间件调用next 函数时,且当中间件执行完毕,对应c.index也已经到达指定index
    //前面存在的for循环因为是通过c.index<s 做循环判断,则不会重复执行已经执行过的中间件
    for ; c.index < s; c.index++ {
        c.handlers[c.index](c)
    }
}

这个next函数设计得真巧妙,太妙了,

顺便建议这个 next前逻辑和next后逻辑可以类比下java的filter设计,让java同学一眼就懂

ZAKLLL commented 2 years ago

这个请求url匹配中间件,放在路由做可能更合适,在ServeHTTP 通过前缀匹配,对于那些/:xxx/ 模糊路径的pattern是无法正常匹配上的,但是如果放在route中,先将url从router中获取对应的解析后的route,通过目标router.pattern与 中间件进行匹配更合理一点也能匹配到模糊路径了。

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    //var middlewares []HandlerFunc
    //无法实现模糊匹配
    //for _, group := range engine.groups {
    //  if strings.HasPrefix(req.URL.Path, group.prefix) {
    //      middlewares = append(middlewares, group.middlewares...)
    //  }
    //}
    c := newContext(w, req)
    //c.handlers = middlewares
    engine.router.handle(c)
}

//在路由进行中间件匹配处理
func (r *router) handle(c *Context) {
    route, params := r.getRoute(c.Method, c.Path)
    if route != nil {
        //用route 来跟分组前缀匹配
        for _, group := range r.engine.groups {
            if strings.HasPrefix(route.pattern, group.prefix) {
                c.handlers = append(c.handlers, group.middlewares...)
            }

        }

        key := c.Method + "-" + route.pattern
        c.Params = params
        c.handlers = append(c.handlers, r.handlers[key])
    } else {
        c.handlers = append(c.handlers, func(c *Context) {
            c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
        })
    }
    c.Next()
}
yjymmddd commented 2 years ago

想问一下这种前后逻辑都在一个函数里的中间件,和spring中的前后逻辑都在不同的函数中的 interceptor有啥区别

FOOLISH06 commented 2 years ago
var middlewares []HandlerFunc
for _, group := range engine.groups {
    if strings.HasPrefix(req.URL.Path, group.prefix) {
        middlewares = append(middlewares, group.middlewares...)
    }
}

在ServeHTTP()中,每接收到一个请求都需要逐一匹配前缀来添加中间件,太影响性能了。 gin是在前缀树的节点中添加中间件的切片,这样在匹配动态路由并解析参数时,就可以同时获得各分组的中间件。

ueueQ commented 2 years ago

@FOOLISH06

var middlewares []HandlerFunc
for _, group := range engine.groups {
  if strings.HasPrefix(req.URL.Path, group.prefix) {
      middlewares = append(middlewares, group.middlewares...)
  }
}

在ServeHTTP()中,每接收到一个请求都需要逐一匹配前缀来添加中间件,太影响性能了。 gin是在前缀树的节点中添加中间件的切片,这样在匹配动态路由并解析参数时,就可以同时获得各分组的中间件。

这样的话,需要在每一个router的node上加入该node属于哪一个组

Serein404 commented 2 years ago

第二个结果是 [500] /v2/hello/geektutu in 61.467µs for group v2吧,因为在c.Fail之后c.StatusCode = 500。另外下边那个打印的应该也是500