手把手和你一起實現一個Web框架實戰——EzWeb框架(五)[Go語言筆記]Go項目實戰

手把手和你一起實現一個Web框架實戰——EzWeb框架(五)[Go語言筆記]Go項目實戰

程式碼倉庫:
github
gitee
中文注釋,非常詳盡,可以配合食用
本篇程式碼,請選擇demo5

中間件實現

一、Context設計

type Context struct {
   Writer http.ResponseWriter
   Req *http.Request
   //請求的資訊,包括路由和方法
   Path string
   Method string
   Params map[string]string   /*用於存儲外面拿到的參數 ":xxx" or "*xxx" */
   //響應的狀態碼
   StatusCode int
   //中間件
   handlers []HandlerFunc
   index    int   /* 用於記錄當前執行到第幾個中間件 */
}

我們每次請求生成的context,我們選擇在其中放入和我們中間件和執行控制變數index

二、中間件對路由組的註冊方法

// 將路中間件,放入路由組的中間件方法切片中
func (group *RouterGroup) Use(middlewares ...HandlerFunc)  {
	group.middlewares = append(group.middlewares, middlewares...)
}

當每個請求到來後,ServeHTTP函數執行時,它將該生成一個context並將進行傳入的url和路由組前綴做前綴對比,找到滿足條件的路由組,取出它的中間件,然後存入到生成的context。

// ServeHTTP 方法的實現,用於實現處理HTTP請求
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...)
		}
	}
	//根據req和w實例一個context
	c := newContext(w, req)
	//將取道的中間件賦給這個context
	c.handlers = middlewares
	//通過封裝好的context執行處理
	engine.router.handle(c)
}

三、處理函數

在處理函數handle()中,我們根據路由拿到已經註冊的方法,放入到中間件後,在通過Next函數進行處理

//根據context中存儲的 c.Method 和 c.Path 拿到對應的處理方法,進行執行,如果拿到的路由沒有註冊,則返回404
func (r *router) handle(c *Context)  {
	//獲取匹配到的節點,同時也拿到兩類動態路由中參數
	n, params := r.getRoute(c.Method, c.Path)
	if n != nil {
		c.Params = params
		//拿目的節點中的path做key來找handlers
		key := c.Method + "-" + n.path
		//根據路徑拿到處理器
		c.handlers = append(c.handlers, r.handlers[key])
	}else {
		//不存在節點的情況下,給生成的c加入一個404方法
		c.handlers = append(c.handlers, func(c *Context) {
			c.String(http.StatusNotFound, "404 NOT FOUND: ", c.Path)
		})
	}
	c.Next()
}

四、Next方法執行處理

index進行控制,遍歷完c.handlers中存儲的方法執行。

// 當中間件調用了 Next 方法時,就往後執行下一個,同時index交由下一個中間件控制
func (c *Context) Next()  {
	c.index++
	//執行完之後所有的handlers
	for ;c.index < len(c.handlers); c.index++{
		c.handlers[c.index](c)
	}
}

這裡的日誌中間件在Next中被執行時,也調用了Next函數。這裡第四行的執行巧妙的做到了,控制index++,執行下一個中間件,直到執行完了又回到Logger執行,回到原來的Next的for循環,發現不滿足繼續循環的條件,然後退出。

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

demo測試:

/*
@Time : 2021/8/16 下午4:01
@Author : mrxuexi
@File : main
@Software: GoLand
*/
package main

import (
	"Ez"
	"net/http"
)
func main() {
	r := Ez.New()
	//給所有的路由組都添加了中間件logger
	r.Use(Ez.Logger())
	api := r.Group("/api")

	api.POST("/hello", func(c *Ez.Context) {
		c.JSON(http.StatusOK,Ez.H{
				"message" : "hello",
		})
	})
//next的應用
	api.Use(func(c *Ez.Context) {
		c.JSON(200,Ez.H{
			"test" : "middleware2-1",
		})
		c.Next()
		c.JSON(200, Ez.H{
			"test" : "middleware2-2",
		})
	})

	r.Run(":9090")
}

成功!

參考:

[1]: //github.com/geektutu/7days-golang/tree/master/gee-web “”gee””
[2]: //github.com/gin-gonic/gin “”gin””