這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
martini是非常優雅的Go Web架構。他基於依賴注入的思想,仿照Sinatra的路由設計,參考Express的中介軟體設計,而且核心微小,擴充方便,非常值得學習。但是由於本身API設計簡潔,使很多細節無法從代碼理解。所以,我寫一點筆記記錄martini
的工作方式。
Martini核心
我們從最簡單的官方執行個體入手:
package mainimport "github.com/go-martini/martini"func main() { m := martini.Classic() m.Get("/", func() string { return "Hello world!" }) m.Run()}
martini.Martini
是內建的核心結構,負責完成依賴注入和調用的過程。martini.ClassicMartini
是路由martini.Router
和martini.Martini
的組合,實現路由分發和邏輯調用的過程。m := martini.Classic()
返回的就是martini.ClassicMartini
。具體在martini.go#L104:
func Classic() *ClassicMartini {r := NewRouter()m := New()m.Use(Logger())m.Use(Recovery())m.Use(Static("public"))m.MapTo(r, (*Routes)(nil))m.Action(r.Handle)return &ClassicMartini{m, r}}
裡面的m := New()
定義在martini.go#L38:
func New() *Martini {m := &Martini{Injector: inject.New(), action: func() {}, logger: log.New(os.Stdout, "[martini] ", 0)}m.Map(m.logger)m.Map(defaultReturnHandler())return m}
依賴注入
上面很明顯的看到兩個奇特方法:m.Map()
、m.MapTo()
。這裡,需要注意martini
的一個最重要原則,注入的任何類型的結構,都是唯一的。即如:
type User struct{ Id int}m.Map(&User{Id:1})m.Map(&User{Id:2})
martini
在尋找&User
類型的時候,只能擷取到&User{Id:2}
結構(最後註冊的)。Map
的作用,就是向內部註冊對應類型的具體對象或者值。類型索引是reflect.Type
。從而我們可以理解到m.New()
的代碼中將m.Logger(*log.Logger)
和defaultReturnHandler(martini.ReturnHandler)
(括弧中是類型索引)注入到內部。
這裡出現了一個問題。介面這種類型,是無法用reflect.Type()
直接擷取的(因為傳來的都是已經實現介面的具體結構)。解決方案就是m.MapTo()
。
m.MapTo(r, (*Routes)(nil))
即將r(martini.router)
按照martini.Router
介面(注意大小寫)類型注入到內部。
(*Routes)(nil)
也是高明的構造。介面的預設值不是nil,無法直接new。但是指標的預設值是nil,可以直接賦值,比如var user *User; user = nil
。因此他註冊一個介面指標類型的null 指標,用reflect.Type.Elem()
方法就可以擷取到指標的內部類型,即介面類型,並以介面類型索引注入到內部。
路由過程
HTTP處理
martini.Martini
實現了http.Handler
方法,實際的HTTP執行過程在代碼martini.go#L68:
func (m *Martini) ServeHTTP(res http.ResponseWriter, req *http.Request) {m.createContext(res, req).run()}
這裡需要我們關注m.createContext
,它返回*martini.context
類型,代碼martini.go#L87:
func (m *Martini) createContext(res http.ResponseWriter, req *http.Request) *context {c := &context{inject.New(), m.handlers, m.action, NewResponseWriter(res), 0}c.SetParent(m)c.MapTo(c, (*Context)(nil))c.MapTo(c.rw, (*http.ResponseWriter)(nil))c.Map(req)return c}
建立*martini.context
類型;然後SetParent
設定尋找注入對象的時候同時從m(*martini.Martini)
中尋找(*martini.context
和*martini.Martini
兩個獨立的inject
),這樣就可以擷取m.Map
注入的資料。
這裡叉出來說:從代碼看出實際上注入的資料有兩層,分別在*martini.context
和*martini.Martini
。*martini.context
中的是當前請求可以擷取的(每個請求都會m.createContext()
,都是新的對象);martini.Martini
是全域的,任何請求都可以擷取到。
回到上一段,c.MapTo
把*martini.context
按martini.Context
介面,將martini.ResponseWriter
按http.ResponseWriter
介面,把req(*http.Request)
注入到當前上下文。
context.run
方法定義在martini.go#L163:
func (c *context) run() {for c.index <= len(c.handlers) {_, err := c.Invoke(c.handler())if err != nil {panic(err)}c.index += 1if c.Written() {return}}}
它在迴圈c.handlers
(來自m.handlers
,createContext代碼中)。這裡想解釋三個細節。
c.Invoke
是inject.Invoke
方法,內部就是擷取c.hanlder()
返回的martini.Handler(func)
類型的傳入參數reflect.Type.In()
,根據參數個數和類型去內部找對應的結構,然後拼裝成[]reflect.Value
給函數的reflect.Value(func).Call()
。
c.handler()
的返回來自兩個方面,c.hanlders
和c.action
。c.handlers
來自m.Use()
添加,c.action
來自r.Handle(*martini.router.Handle)
(見上文martini.ClassicMartini.New
中的m.Action(r.Handle)
)。因此,可以發現實際上handlers是有兩個列表,一個是c.handlers([]martini.handler)
和r.handlers(martini.routerContext.handlers)
。而且前者先執行。也就是說無論m.Use
寫在哪兒,都要比router添加的func先執行。
c.Written
判斷請求是否已經發送。他實際上是判斷martini.ResponseWriter.status
是否大於0。因此只要發送了response status,handlers過程就會停止。
路由調用
從上面可以知道,路由調用過程有兩個方面:一是m.Use()
添加的handlers,二是路由添加比如m.Get("/",handlers...)
中的handlers。m.Use
的handlers調用就是上文的*martini.context.run
方法,不再贅述。路由中的handlers執行是在router.go#L218:
func (r *route) Handle(c Context, res http.ResponseWriter) {context := &routeContext{c, 0, r.handlers}c.MapTo(context, (*Context)(nil))context.run()}
和router.go#L315:
func (r *routeContext) run() {for r.index < len(r.handlers) {handler := r.handlers[r.index]vals, err := r.Invoke(handler)if err != nil {panic(err)}r.index += 1// if the handler returned something, write it to the http responseif len(vals) > 0 {ev := r.Get(reflect.TypeOf(ReturnHandler(nil)))handleReturn := ev.Interface().(ReturnHandler)handleReturn(r, vals)}if r.Written() {return}}}
如果你已經理解上文中說明,這個過程和martini.context.run
是一樣的。唯一這裡要解釋的是martini.ReturnHandler
。它與很上文中的m.Map(defaultReturnHandler())
遙相呼應。
中介軟體
從上文不難理解,中介軟體其實就是martini.Handler
被m.Use
添加到m.handlers
中。這裡我們來說明官方的一個中介軟體martini.Logger()
,實現代碼在logger.go:
func Logger() Handler {return func(res http.ResponseWriter, req *http.Request, c Context, log *log.Logger) {start := time.Now()log.Printf("Started %s %s", req.Method, req.URL.Path)rw := res.(ResponseWriter)c.Next()log.Printf("Completed %v %s in %v\n", rw.Status(), http.StatusText(rw.Status()), time.Since(start))}}
首先看func的傳入參數,http.ResponseWriter
和*http.Request
來自:
c := &context{inject.New(), m.handlers, m.action, NewResponseWriter(res), 0}// ...c.MapTo(c.rw, (*http.ResponseWriter)(nil))c.Map(req)
Context
來自:
context := &routeContext{c, 0, r.handlers}c.MapTo(context, (*Context)(nil))
*log.Logger
來自:
m := &Martini{Injector: inject.New(), action: func() {}, logger: log.New(os.Stdout, "[martini] ", 0)}m.Map(m.logger)
然後看rw := res.(ResponseWriter)
。實際上c.rw
是NewReponseWriter(res)
返回的martini.ResponseWriter
類型,一次可以在這裡直接轉換(注意在外部調用,不是martini包中,要import並寫res.(martini.ResponseWriter)
)。
最後是c.Next()
方法,源碼在martini.go#L154:
func (c *context) Next() {c.index += 1c.run()}
意思就是index自增,指向下一個handler,c.run
走完所有handler,然後繼續中介軟體裡的log.Printf...
。
總結
martini的對外API很簡單,但是內部實現其實比較複雜的。需要仔細的閱讀,並且有一定標準庫的基礎,才能很好的理解他代碼的用意。
我這裡只是按照自己的理解說明,如果有錯誤請在評論中指正。