Martini源碼剖析

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

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.Routermartini.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.contextmartini.Context介面,將martini.ResponseWriterhttp.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.Invokeinject.Invoke方法,內部就是擷取c.hanlder()返回的martini.Handler(func)類型的傳入參數reflect.Type.In(),根據參數個數和類型去內部找對應的結構,然後拼裝成[]reflect.Value給函數的reflect.Value(func).Call()

c.handler()的返回來自兩個方面,c.hanldersc.actionc.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.Handlerm.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.rwNewReponseWriter(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很簡單,但是內部實現其實比較複雜的。需要仔細的閱讀,並且有一定標準庫的基礎,才能很好的理解他代碼的用意。

我這裡只是按照自己的理解說明,如果有錯誤請在評論中指正。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.