This is a creation in Article, where the information may have evolved or changed.
Martini is a very elegant go web framework. He is based on the idea of dependency injection, modeled on the Sinatra routing design, reference express middleware design, and the core is small, easy to expand, it is worth learning. But because of the simplicity of its API design, many details cannot be understood from the code. So, I'm writing a little note-taking record martini of how it works.
Martini Core
We start with the simplest official examples:
package mainimport "github.com/go-martini/martini"func main() { m := martini.Classic() m.Get("/", func() string { return "Hello world!" }) m.Run()}
martini.MartiniIs the core structure that comes with it, and is responsible for the process of dependency injection and invocation. martini.ClassicMartiniis a martini.Router combination of routes and martini.Martini processes that implement routing distributions and logical calls. m := martini.Classic()the return is martini.ClassicMartini . Specifically in 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}}
Inside the m := New() definition in 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}
Dependency Injection
It is obvious that two strange methods are seen: m.Map() m.MapTo() . Here, one of the most important principles to be aware of martini , injected into any type of structure, is unique . namely
type User struct{ Id int}m.Map(&User{Id:1})m.Map(&User{Id:2})
martiniWhen looking for &User a type, you can only get to the &User{Id:2} structure (last registered). Mapis to register the specific object or value of the corresponding type internally. The type index is reflect.Type . Thus we can understand m.New() that the code will m.Logger(*log.Logger) be defaultReturnHandler(martini.ReturnHandler) injected into the inner and (in parentheses, the type index).
There's a problem here. This type of interface is not available reflect.Type() directly (because it is the concrete structure that has implemented the interface). The solution is m.MapTo() .
m.MapTo(r, (*Routes)(nil))
Will r(martini.router) be martini.Router injected into the interior by the interface (note case) type.
(*Routes)(nil)
is also a clever construct. The default value of the interface is not nil and cannot be directly new. However, the default value of the pointer is nil and can be assigned directly, for example var user *User; user = nil . So he registers a null pointer of the interface pointer type, which can be used to get the internal type of the pointer, the reflect.Type.Elem() interface type, and inject it internally with an interface type index.
Routing process
HTTP processing
martini.MartiniImplementation http.Handler of the method, the actual HTTP execution process in code martini.go#l68:
func (m *Martini) ServeHTTP(res http.ResponseWriter, req *http.Request) {m.createContext(res, req).run()}
Here's what we need to focus on m.createContext , it returns the *martini.context type, code 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}
Create the *martini.context type, and then set the search for the SetParent injected object while m(*martini.Martini) looking for ( *martini.context and *martini.Martini two separate inject ), so that m.Map the injected data can be obtained.
Here fork out said: From the code to see actually injected data there are two layers, respectively in *martini.context and *martini.Martini . The *martini.context current request can be obtained (each request will be a m.createContext() new object); martini.Martini Is global and can be obtained by any request.
Go back to the last paragraph, put on the c.MapTo *martini.context interface, martini.Context will press the martini.ResponseWriter http.ResponseWriter interface, req(*http.Request) inject into the current context.
context.runMethod definitions in 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}}}
It is in the loop c.handlers (from m.handlers the Createcontext code). Here are some three details to explain.
c.InvokeIs the inject.Invoke method, internal is c.hanlder() to get the returned martini.Handler(func) type of the incoming parameters reflect.Type.In() , according to the number and type of parameters to find the corresponding structure inside, and then assembled into []reflect.Value the function reflect.Value(func).Call() .
c.handler()The return comes from two aspects, c.hanlders and c.action . c.handlersfrom the m.Use() Add, c.action from r.Handle(*martini.router.Handle) (see above martini.ClassicMartini.New m.Action(r.Handle) ). Thus, it can be found that actually handlers is there are two lists, one is c.handlers([]martini.handler) and r.handlers(martini.routerContext.handlers) . And the former executes first. This means m.Use that no matter where it is written, it is performed before the func added by router.
c.WrittenDetermines whether the request has been sent. He was actually judging martini.ResponseWriter.status whether it was greater than 0. So as soon as the response status,handlers is sent, the process stops.
Routing calls
As can be known from the above, there are two aspects of the routing call process: One is m.Use() the added handlers, and the other is the m.Get("/",handlers...) handlers in the route. m.UseThe handlers call is the *martini.context.run method above, no longer repeat. The handlers execution in the route is in 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()}
and 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}}}
If you have understood the above, the process martini.context.run is the same. The only thing to explain here is martini.ReturnHandler . It echoes the very above m.Map(defaultReturnHandler()) .
Middleware
From the above, it is not difficult to understand that middleware is actually martini.Handler m.Use added to m.handlers the. Here we explain the official middleware martini.Logger() , implementation code in 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))}}
First look at the incoming parameters of Func, http.ResponseWriter and *http.Request from:
c := &context{inject.New(), m.handlers, m.action, NewResponseWriter(res), 0}// ...c.MapTo(c.rw, (*http.ResponseWriter)(nil))c.Map(req)
ContextFrom:
context := &routeContext{c, 0, r.handlers}c.MapTo(context, (*Context)(nil))
*log.LoggerFrom:
m := &Martini{Injector: inject.New(), action: func() {}, logger: log.New(os.Stdout, "[martini] ", 0)}m.Map(m.logger)
Then look rw := res.(ResponseWriter) . is actually the c.rw NewReponseWriter(res) type of return, which martini.ResponseWriter can be converted directly here at a time (note the external call, not the Martini package, to import and write res.(martini.ResponseWriter) ).
Finally is the c.Next() method, source code in martini.go#l154:
func (c *context) Next() {c.index += 1c.run()}
The meaning is index increment, point to the next handler, c.run go through all the handler, and then continue in the middleware log.Printf... .
Summarize
Martini's external API is simple, but the internal implementation is actually more complex. Need to read carefully, and have the basis of a certain standard library, in order to understand the purpose of his code well.
I'm just following my own understanding that if there is an error, please correct it in the comments.