1. 程式人生 > 其它 >第四章-(中介軟體)

第四章-(中介軟體)

在上一章中已經實現了分組控制,在這一章中我們實現中介軟體的功能

中介軟體是什麼

中介軟體(middlewares),簡單說,就是非業務的技術類元件。Web 框架本身不可能去理解所有的業務,因而不可能實現所有的功能。因此,框架需要有一個插口,允許使用者自己定義功能,嵌入到框架中,彷彿這個功能是框架原生支援的一樣。因此,對中介軟體而言,需要考慮2個比較關鍵的點:

  • 插入點在哪?使用框架的人並不關心底層邏輯的具體實現,如果插入點太底層,中介軟體邏輯就會非常複雜。如果插入點離使用者太近,那和使用者直接定義一組函式,每次在 Handler 中手工呼叫沒有多大的優勢了。
  • 中介軟體的輸入是什麼?中介軟體的輸入,決定了擴充套件能力。暴露的引數太少,使用者發揮空間有限。

那對於一個 Web 框架而言,中介軟體應該設計成什麼樣呢?接下來的實現,基本參考了 Gin 框架。

Finto 的中介軟體的定義與路由對映的 Handler 一致,處理的輸入是Context物件。插入點是框架接收到請求初始化Context物件後,允許使用者使用自己定義的中介軟體做一些額外的處理,例如記錄日誌等,以及對Context進行二次加工。另外通過呼叫(*Context).Next()函式,中介軟體可等待使用者自己定義的Handler處理結束後,再做一些額外的操作,例如計算本次處理所用時間等。即 Finto 的中介軟體支援使用者在請求被處理的前後,做一些額外的操作。舉個例子,我們希望最終能夠支援如下定義的中介軟體,c.Next()

表示等待執行其他的中介軟體或使用者的Handler

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

另外,支援設定多箇中間件,依次進行呼叫。

上一章中講到,中介軟體是應用在RouterGroup上的,應用在最頂層的 Group,相當於作用於全域性,所有的請求都會被中介軟體處理。那為什麼不作用在每一條路由規則上呢?作用在某條路由規則,那還不如使用者直接在 Handler 中呼叫直觀。只作用在某條路由規則的功能通用性太差,不適合定義為中介軟體。

我們之前的框架設計是這樣的,當接收到請求後,匹配路由,該請求的所有資訊都儲存在Context中。中介軟體也不例外,接收到請求後,應查詢所有應作用於該路由的中介軟體,儲存在Context中,依次進行呼叫。為什麼依次呼叫後,還需要在Context中儲存呢?因為在設計中,中介軟體不僅作用在處理流程前,也可以作用在處理流程後,即在使用者定義的 Handler 處理完畢後,還可以執行剩下的操作。

為此,我們給Context添加了2個引數,定義了Next方法:

type Context struct {
// origin objects
Writer http.ResponseWriter
Req *http.Request
// request info
Path string
Method string
Params map[string]string
// response info
StatusCode int
// middleware
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)
}
}
index是記錄當前執行到第幾個中介軟體,當在中介軟體中呼叫Next方法時,控制權交給了下一個中介軟體,直到呼叫到最後一箇中間件,然後再從後往前,呼叫每個中介軟體在Next方法之後定義的部分。
接下來定義Use函式,將中介軟體應用到group上.
// Use is defined to add middleware to the group
func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
group.middlewares = append(group.middlewares, middlewares...)
}

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

ServeHTTP 函式也有變化,當我們接收到一個具體請求時,要判斷該請求適用於哪些中介軟體,在這裡我們簡單通過 URL 的字首來判斷。得到中介軟體列表後,賦值給c.handlers

  • handle 函式中,將從路由匹配得到的 Handler 新增到c.handlers列表中,執行c.Next()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()
}



使用 Demo


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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))
}
}

func main() {
r := gee.New()
r.Use(gee.Logger()) // global midlleware
r.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})

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

r.Run(":9999")
}
awd