blog
blog copied to clipboard
Go语言动手写Web框架 - Gee第五天 中间件Middleware | 极客兔兔
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)。
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)
}
感谢!!!
@acaibird 不是所有的handler都会调用 Next()
。
手工调用 Next()
,一般用于在请求前后各实现一些行为。如果中间件只作用于请求前,可以省略调用Next()
,算是一种兼容性比较好的写法吧。
@geektutu @acaibird 不是所有的handler都会调用
Next()
。 手工调用Next()
,一般用于在请求前后各实现一些行为。如果中间件只作用于请求前,可以省略调用Next()
,算是一种兼容性比较好的写法吧。
嗯嗯,明白了,多谢!
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++ 一直死循环
@blackher Next()
函数一开始调用了 c.index++
,下标恰好从 0 开始。
@geektutu 我的意思是 index 直接初始化为0 Next()就不用c.index++了 我自己尝试初始化为0 去掉c.index++ 一直死循环
你把Next()
第一行的 c.index++
去掉,就会一直卡在第一个中间件上了,必然死循环。每调用一次 Next()
,c.index
得 +1,不然 c.index
就会一直是 0。
死循环之后,是不会走到 for 语句后面的 c.index++
的。
@geektutu 提问一下,这样写不是会凡是用到 onlyForV2的 都会报500的错吗?,应该不是遇到错误报500吧?
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
func A(c *Context) {
// 中间件上半部
part1
c.Next()
// 中间件下半部
part2
}
感觉这样解释比较容易理解
首先谢谢博主,文章写得很好,思路很清晰。 然后我有个问题,你这个middleware的实现执行是无序的。所以是不是把middleware放在trie的node里面,然后在search的时候collect所有的middlewares,这样更好?
@proverbs 首先谢谢博主,文章写得很好,思路很清晰。 然后我有个问题,你这个middleware的实现执行是无序的。所以是不是把middleware放在trie的node里面,然后在search的时候collect所有的middlewares,这样更好?
代码看来,因为routerGroups中所有的RouterGroup,在添加时肯定是按照先父再子的顺序添加的,所以从头到尾遍历routerGroups这种简单的收集方式,也就是达成了实际路径中从左向右的查找过程。 所以顺序是可以保证的。
当然这种收集方式有个前提:分组中所有的前缀必须是静态路径,否则使用实际路由从全部分组做前缀匹配,无法保证收集的完整性。这也给上节的分组控制的使用加了限制。
如果我理解错了,还请指出。
博主,能否请教一个基础问题,为什么Context中,Writer不用指针,而Req却要用指针呢?
@void1104 这里和 http ServerHTTP 函数原始的两个参数对应 req 和 writer。req 是结构体,用指针可以节省内存,Writer 是一个接口类型,不能用指针。
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
理解了,谢谢大佬
------------------ 原始邮件 ------------------ 发件人: "geektutu/geektutu-blog" <[email protected]>; 发送时间: 2020年12月1日(星期二) 中午1:23 收件人: "geektutu/geektutu-blog"<[email protected]>; 抄送: "彭家鑫"<[email protected]>;"Mention"<[email protected]>; 主题: 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.
@geektutu @acaibird 不是所有的handler都会调用
Next()
。 手工调用Next()
,一般用于在请求前后各实现一些行为。如果中间件只作用于请求前,可以省略调用Next()
,算是一种兼容性比较好的写法吧。
你好,大佬写的文章很受用。有个问题想问一下,在使用循环调用handler的话,如何实现类似于gin中的c.Abort()
这种中间件的退出机制呢?
你好,大佬写的文章很受用。有个问题想问一下,在使用循环调用handler的话,如何实现类似于gin中的
c.Abort()
这种中间件的退出机制呢?
@YoshieraHuang context 中维护一个状态值,调用 c.Abort()
改变状态,循环时检查状态,发现已经中止停止循环即可。
你好,大佬写的文章很受用。有个问题想问一下,在使用循环调用handler的话,如何实现类似于gin中的
c.Abort()
这种中间件的退出机制呢?@YoshieraHuang context 中维护一个状态值,调用
c.Abort()
改变状态,循环时检查状态,发现已经中止停止循环即可。
理解了,谢谢大佬。
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
@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错误码来表示中间件的起作用了
目前的中间件设计无法支持指定路由使用中间件吧
这是短路中间件,如果使用 后续的中间件和handler就直接跳过了 c.index = len(c.handlers)
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) }) } 这个{}的用法是什么含义呀
学习日志: 经过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里面记录的耗时日志
@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) }) } 这个{}的用法是什么含义呀
这里大括号的作用是开创一个新的语法块,或者说作用域,也就是里面定义的同名变量会覆盖外层。在这里的作用我个人觉得更多的是可读性吧,能够清楚知道每个分组的路由设置情况
@geektutu @void1104 这里和 http ServerHTTP 函数原始的两个参数对应 req 和 writer。req 是结构体,用指针可以节省内存,Writer 是一个接口类型,不能用指针。
package http type Handler interface { ServeHTTP(w ResponseWriter, r *Request) }
我思考和尝试了一下,接口类型确实不能用指针,不然实现这个接口的类型变量就传不进来(或者说类型转换?). 但是在ide上定义一个指针接口倒没有报错,说明确实有指针接口这种东西。想请教下什么时候会用到指针接口呢,网上涉及这个也有点少。
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或者采用指针判断吗?
@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或者采用指针判断吗?
这应该没必要管吧. 使用中间键的权利暴露给用户. 用户要犯傻也没办法呀啊
onlyForV2中出现的Context的Fail方法,在前面的教程中好像漏掉了
@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") 即可