【Gee-Web框架】【Day5】【Golang】中间件

  1. 设计并实现Web框架的中间件Middlewares机制
  2. 实现通用的Logger中间件,能够记录请求到响应所花费的时间

一、 中间件

1. 简要介绍

  • 中间件middlewares,简单说就是非业务的技术类组件
  • web框架本身不可能去理解所有的业务,因而不可能实现所有的功能,因此,框架需要有一个插口,允许用户自己定义功能,嵌入到框架中,仿佛这个功能是框架原生支持的一样
  • 对中间件而言,需要考虑2个比较关键的点
    • 插入点在哪?使用框架的人并不关心底层逻辑的具体实现,如果插入点太底层,中间件逻辑就会非常复杂。如果插入点离用户太近,那和用户直接定义一组函数,每次在Handler中手工调用没有多大优势了
    • 中间件的输入是什么?中间件的输入,决定了扩展能力。暴露的参数太少,用户发挥空间有限

2. 中间件的设计

  • 中间件的定义与路由映射的Handler一致,处理的输入是Context对象
  • 插入点是框架接收到请求初始化Context对象后,允许用户使用自己定义的中间件做一些额外的处理,例如记录日志等,以及对Context进行二次加工
  • 中间件是应用在RouterGroup上的,应用在最顶层的Group,相当于作用于全局,所有的请求都会被中间件处理
  • 那为什么不作用在每一条路由规则上呢?作用在某条路由规则,那还不如用户直接在Handler中调用直观。只作用在某条路由规则的功能通用性太差,不适合定义为中间件
  • 当接受到请求后,匹配路由,该请求的所有信息都保存在Context中
  • 中间件也不例外,接收到请求后,应查找所有应作用于该路由的中间件,保存在Context中,依次进行调用
  • 为什么依次调用后,还需要在Context中保存中,因为在设计中,中间件不仅作用在处理流程前,也可以作用在处理流程后,即在用户定义的Handler处理完毕后,还可以执行剩下的操作

3. 具体实现

  • logger.go
    func Logger() HandlerFunc{
    	return func(c *Context){
    		t := time.Now()
    		c.Next()
    		log.Printf("[%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t))	
    	}	
    }
    
  • context.go
    type Context struct{
    	Writer http.ResponseWriter
    	Req *http.Request
    	Path string
    	Method string
    	Params map[string]string
    	StatusCode int
    	handlers []HandlerFunc
    	index int	// 记录当前执行到第几个中间件
    }
    	
    func newContext(w http.ResponseWriter, req *http.Request)*Context{
    	return &Context{
    		Path: req.URL.Path,
    		Method: req.Method,
    		Req: req,
    		Writer: w,
    		index: -1,	
    	}	
    }
    	
    func (c *Context) Next(){
    	c.index++
    	s := len(c.handlers)
    	for ; c.index < s; c.index++{
    		c.handlers[c.index](c)	
    	}	
    }
    
  • gee.go
    func (group *RouterGroup) Use(middlewares ...HandlerFunc){
    	group.middlewares = append(group.middiewares, middlewares...)
    }
    	
    func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request){
    	var middlewares []HandlerFunc
    	for _, group := range engine.groups{
    		// 当接收到一个具体请求时,要判断该请求适用于哪些中间件
    		// 简单通过URL的前缀来判断,得到中间件列表,赋值给c.handlers
    		if strings.HasPrefix(req.URL.Path, group.prefix){
    			middlewares = append(middlewares, group.middlewares...)	
    		}	
    	}	
    	c := newContext(w, req)
    	c.handlers = middlewares
    	engine.router.handle(c)
    }
    
  • router.go
    // handle函数中,将从路由匹配得到的Handler添加到c.handlers列表中
    func (r *router) handle(c *Context){
    	n, params := r.getRoute(c.Method, c.Path)
    	if n != nil{
    		key := c.Method + "-" + n.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()
    }
    
  • main.go
    func onlyForV2() gee.HandlerFunc{
    	return func(c *gee.Context){
    		t := time.Now()
    		c.Fail(500, "Internal Server Error")
    		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())
    	r.GET("/", func(c *gee.Context){
    		c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")	
    	})	
    		
    	v2 := r.Group("/v2")
    	v2.Use(onlyForV2())
    	{
    		v2.GET("/hello/:name", func(c *gee.Context){
    			c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)	
    		})	
    	}
    	r.Run(":9999")
    }
    
  • 测试
    • curl http://localhost:9999/
    • curl http://localhost:9999/v2/hello/geektutu

参考

  1. https://geektutu.com/post/gee-day5.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值