第四章-(中介軟體)
在上一章中已經實現了分組控制,在這一章中我們實現中介軟體的功能
中介軟體是什麼
中介軟體(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
|
func (r *router) handle(c *Context) {
|
使用 Demo
|
func onlyForV2() gee.HandlerFunc {
|
awd