当前位置: 首页 > 工具软件 > Martini > 使用案例 >

Martini源码解析-实例1

郗学
2023-12-01

1)实例解析

https://github.com/go-martini/martini

我们按照git主页的一些示例深入源码解读。在阅读此文之前,你需要了解Inject的原理,可以参阅golang: Martini之inject源码分析

1-1) 实例1

首先来看一个最基本的实例:

package main

import "github.com/go-martini/martini"

func main() {
  m := martini.Classic()
  m.Get("/", func() string {
    return "Hello world!"
  })
  m.Run()
}

此处使用了3个方法:

  • martini.Classic()
  • m.Get()
  • m.Run()

我们依次解释

1-1-1) Classic()

首先介绍Martini结构体:

// Martini represents the top level web application. inject.Injector methods can be invoked to map services on a global level.
type Martini struct {          
        inject.Injector //用于注入方法等       
        handlers []Handler   //存储中间件列表
        action   Handler  //处理handler,一般赋值路由处理方法
        logger   *log.Logger   //日志处理对象
}  

在martini.Classic()中,我们新建了一个经典的martini对象,用来支持常规的应用场景。当然你可以根据自己的需求定制。

// Classic creates a classic Martini with some basic default middleware - martini.Logger, martini.Recovery and martini.Static.
// Classic also maps martini.Routes as a service.
func Classic() *ClassicMartini {
        r := NewRouter() 新建标准路由
        m := New() 新建Martini对象
        m.Use(Logger())//注册中间件,标准logger
        m.Use(Recovery()) //注册中间件,标准recover
        m.Use(Static("public")) //注册中间件Static
        m.MapTo(r, (*Routes)(nil)) //Injector的Mapto方法,实现类型和对象的关联注入
        m.Action(r.Handle) //设置处理函数为路由处理函数
        return &ClassicMartini{m, r} //返回一个ClassMartini实例,继承了Martini的结构, 提升了martini以及Router的相关方法
}

1-1-1-1) NewRouter()

其中NewRouter新建一个路由空对象:

// NewRouter 创建一个路由实例
// 如果你不使用 ClassicMartini, 你需要手动注册路由(Classic会自动为你做这个):
//
//      m := martini.New()
//      r := martini.NewRouter()
//      m.MapTo(r, (*martini.Routes)(nil))
//
func NewRouter() Router {
        return &router{notFounds: []Handler{http.NotFound}, groups: make([]group, 0)} 
}

1-1-1-2) New()

martini.New()会新建一个Martini实例,注册一个写到标准输出的logger,注册一个defaultReturnHandler()

func New() *Martini {   
        m := &Martini{Injector: inject.New(), action: func() {}, logger: log.New(os.Stdout, "[martini] ", 0)}
        m.Map(m.logger) // log.Logger对应 m.logger
        m.Map(defaultReturnHandler()) //ReturnHandler对应defaultReturnHandler()的返回函数
        return m
}   

这个defaultRetrunHandler()是什么鬼。。。。。。。

type ReturnHandler func(Context, []reflect.Value)

func defaultReturnHandler() ReturnHandler {
        return func(ctx Context, vals []reflect.Value) { //vals应该就是返回值了
                rv := ctx.Get(inject.InterfaceOf((*http.ResponseWriter)(nil))) //从ctx中取出http.ResponseWriter类型的对象
                res := rv.Interface().(http.ResponseWriter)//从reflect.Value转化为http.ResponseWriter
                var responseVal reflect.Value
                if len(vals) > 1 && vals[0].Kind() == reflect.Int { //返回值第一个如果是int就将其写到返回的http头当中
                        res.WriteHeader(int(vals[0].Int())) 
                        responseVal = vals[1]
                } else if len(vals) > 0 {
                        responseVal = vals[0]
                } //接下来的vals写到responseVal
                if canDeref(responseVal) {//如果返回值是接口活指针则解引用到其包含或者指向对象
                        responseVal = responseVal.Elem()
                }
                if isByteSlice(responseVal) {输出结果到http返回的body
                        res.Write(responseVal.Bytes())
                } else {
                        res.Write([]byte(responseVal.String()))
                }
        }       
} 

这里的defaultReturnHandler就解释了实例中如下的结果,可以直接把状态码由第一个参数返回

m.Get("/", func() (int, string) {
  return 418, "i'm a teapot" // HTTP 418 : "i'm a teapot"
})

1-1-1-3) Use()

注册一个中间件, 将一个可处理的handler添加到Martini.handlers的数组之后:

func (m *Martini) Use(handler Handler) {
        validateHandler(handler)

        m.handlers = append(m.handlers, handler)
}

1-1-1-4) MapTo()

实际是Injector.Mapto的方法提升,注册路由

1-1-1-5) Action()

简单的注册路由处理函数为该Martini处理实例的处理函数

func (m *Martini) Action(handler Handler) {
        validateHandler(handler)
        m.action = handler
}

1-1-2) Get()

实际上是Router.Get()

首先我们先来看一下Router接口:

type Router interface {        
        Routes

        // Group adds a group where related routes can be added.
        Group(string, func(Router), ...Handler)
        // Get adds a route for a HTTP GET request to the specified matching pattern.
        Get(string, ...Handler) Route   
        // Patch adds a route for a HTTP PATCH request to the specified matching pattern.
        Patch(string, ...Handler) Route 
        // Post adds a route for a HTTP POST request to the specified matching pattern.
        Post(string, ...Handler) Route  
        // Put adds a route for a HTTP PUT request to the specified matching pattern.
        Put(string, ...Handler) Route   
        // Delete adds a route for a HTTP DELETE request to the specified matching pattern.
        Delete(string, ...Handler) Route
        // Options adds a route for a HTTP OPTIONS request to the specified matching pattern.
        Options(string, ...Handler) Route
        // Head adds a route for a HTTP HEAD request to the specified matching pattern.
        Head(string, ...Handler) Route  
        // Any adds a route for any HTTP method request to the specified matching pattern.
        Any(string, ...Handler) Route   
        // AddRoute adds a route for a given HTTP method request to the specified matching pattern.
        AddRoute(string, string, ...Handler) Route

        // NotFound sets the handlers that are called when a no route matches a request. Throws a basic 404 by default.
        NotFound(...Handler)   

        // Handle is the entry point for routing. This is used as a martini.Handler
        Handle(http.ResponseWriter, *http.Request, Context)
} 

type router struct {           
        routes     []*route    
        notFounds  []Handler
        groups     []group
        routesLock sync.RWMutex
}

其中的router接口包含了很多http方法的注入函数,其中的Get() 就是处理Get方法的函数注入:

func (r *router) Get(pattern string, h ...Handler) Route {
        return r.addRoute("GET", pattern, h)
}
func (r *router) addRoute(method string, pattern string, handlers []Handler) *route {
        if len(r.groups) > 0 {
                /**遍历当前路由中的groups,可以看到
                *  type group struct {
                *  pattern  string
                *  handlers []Handler
                *  }
                * 每个group都是对应的pattern和一堆的handlers
                * 将目前路由中的所有pattern连接起来,作为当前新建的路由规则的pattern
                * 同样handlers也需要将之前的所有handler累积起来
                * */
                groupPattern := ""
                h := make([]Handler, 0)
                for _, g := range r.groups {
                        groupPattern += g.pattern
                        h = append(h, g.handlers...)
                }

                pattern = groupPattern + pattern
                h = append(h, handlers...)
                handlers = h
        }

        route := newRoute(method, pattern, handlers)//根据加入的pattern来新建路由规则
        route.Validate()
        r.appendRoute(route) //将新生成的路由规则加入路由表
        return route
}

1-1-2-1) groups

可以看到,在上面的addRoute的过程中group中的pattern和handlers会添加到所有的路由规则中,group可以通过显示调用:

func (r *router) Group(pattern string, fn func(Router), h ...Handler) {
        r.groups = append(r.groups, group{pattern, h})
        fn(r)
        r.groups = r.groups[:len(r.groups)-1]
}

在实例代码中,也有两则简易的group使用示例:

m.Group("/books", func(r martini.Router) {
    r.Get("/:id", GetBooks)
    r.Post("/new", NewBook)
    r.Put("/update/:id", UpdateBook)
    r.Delete("/delete/:id", DeleteBook)
}, MyMiddleware1, MyMiddleware2)
//依据上面的语义,上面的代码的效果在于
//分别实现了"/books/:id","/books/new", "/books/update/:id", "/books/delete/:id"这几种pattern及其对应的handler,
//同时将[MyMiddleware1, MyMiddleware2]两个handler加到每个route规则的handlers数组当中

1-1-2-2) newRoute()

首先,一条单独的路由规则如下定义:

type route struct {
        method   string
        regex    *regexp.Regexp
        handlers []Handler
        pattern  string
        name     string
}

在新建一条路由规则的时候:

func newRoute(method string, pattern string, handlers []Handler) *route {
        route := route{method, nil, handlers, pattern, ""} 
        pattern = routeReg1.ReplaceAllStringFunc(pattern, func(m string) string {
                return fmt.Sprintf(`(?P<%s>[^/#?]+)`, m[1:])
        })  
        var index int 
        pattern = routeReg2.ReplaceAllStringFunc(pattern, func(m string) string {
                index++
                return fmt.Sprintf(`(?P<_%d>[^#?]*)`, index)
        })  
        pattern += `\/?`
        route.regex = regexp.MustCompile(pattern)
        return &route
}

大概的意思就是将pattern需要进行一些正则处理并编译成自己的正则处理引擎。

1-1-3) Run()

在设置好路由和handle之后,run就可以了:

func (m *Martini) RunOnAddr(addr string) {
        //从injector中取出对应(*log.Logger)类型的对象,即找出日志对象用来在开始和失败的时候打日志
        logger := m.Injector.Get(reflect.TypeOf(m.logger)).Interface().(*log.Logger)
        logger.Printf("listening on %s (%s)\n", addr, Env)
        logger.Fatalln(http.ListenAndServe(addr, m)) 
}

  func (m *Martini) Run() {
        port := os.Getenv("PORT")
        if len(port) == 0 { 
                port = "3000"
        }   

        host := os.Getenv("HOST")

        m.RunOnAddr(host + ":" + port)
}

这里需要注意的是

1-1-3-1) logger

此处的logger和Martini.Classic()中的m.Use(Logger())有所不同,此处取出的logger诞生于martini.New()中的logger: log.New(os.Stdout, “[martini]”, 0),故会打印到标准输出
而Martini.Classic()中的m.Use(Logger())是这样定义的:

func Logger() Handler {
        return func(res http.ResponseWriter, req *http.Request, c Context, log *log.Logger) {
                start := time.Now()

                addr := req.Header.Get("X-Real-IP")
                if addr == "" {
                        addr = req.Header.Get("X-Forwarded-For")
                        if addr == "" {
                                addr = req.RemoteAddr
                        }
                }

                log.Printf("Started %s %s for %s", req.Method, req.URL.Path, addr)

                rw := res.(ResponseWriter)
                c.Next()//调用context.handlers[]中的下一个handler,这样便实现了中间件的功能

                log.Printf("Completed %v %s in %v\n", rw.Status(), http.StatusText(rw.Status()), time.Since(start))
        } 
}      

1-1-3-2) ServeHTTP()

RunOnAddr()中的logger.Fatalln(http.ListenAndServe(addr, m)),要求m实现Handler接口:

type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

那么martini是何时实现该接口的呢?
如下,在martini.go当中有:

func (m *Martini) ServeHTTP(res http.ResponseWriter, req *http.Request) {
        m.createContext(res, req).run()
}

本质是针对http的一个requeset来返回一个responseWriter
新建一个martini.context并运行run()
那么我们现在看一下context的结构:

type Context interface {
        inject.Injector
        Next()
        Written() bool //用来标识是否往responsewriter输出结果
}

type context struct {
        inject.Injector
        handlers []Handler
        action   Handler
        rw       ResponseWriter
        index    int
}

martini.context实现了以下的方法:

func (c *context) handler() Handler
func (c *context) Next() 
func (c *context) Written()
func (c *context) run()

其中最重要的run()方法:

func (c *context) run() {
        for c.index <= len(c.handlers) {
                _, err := c.Invoke(c.handler())//依次调用,参数已经由injector注入,先调用handlers,最后调用action
                if err != nil {
                        panic(err)
                }
                c.index += 1

                if c.Written() {
                        return
                }
        }
}

依次调用context.handlers中的handler,最后调用action,直到有的返回error引发panic,或者有的往ResponseWriter()输出结果,则结束。
action在martini.Classic()时被设置为m.Action(r.Handle),即router的handle,我们去看一下

func (r *router) Handle(res http.ResponseWriter, req *http.Request, context Context) {                                                                                                           
        bestMatch := NoMatch   
        var bestVals map[string]string  
        var bestRoute *route   
        // 查找最match的路由规则
        for _, route := range r.getRoutes() {   
                match, vals := route.Match(req.Method, req.URL.Path)
                if match.BetterThan(bestMatch) {        
                        bestMatch = match               
                        bestVals = vals                 
                        bestRoute = route               
                        if match == ExactMatch {                
                                break                   
                        }      
                }
        }
        //如果找到则执行其handle
        if bestMatch != NoMatch {               
                params := Params(bestVals)      
                context.Map(params)             
                bestRoute.Handle(context, res) //其实就是建立一个路由上下文,routeContext,注入context和路由规则,然后run
                return
        }

        // no routes exist, 404
        c := &routeContext{context, 0, r.notFounds}
        context.MapTo(c, (*Context)(nil))
        c.run()// 设置上下文为notfounds方法
} 

设置完路由之后的执行run为:

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 response
                if len(vals) > 0 {
                        ev := r.Get(reflect.TypeOf(ReturnHandler(nil)))// ReturnHandler这个类型就是刚开始martini.New()中设置的defaultReturnHandler()
                        //原来在此处调用,哈哈哈哈
                        handleReturn := ev.Interface().(ReturnHandler)
                        handleReturn(r, vals)
                }

                if r.Written() {
                        return
                }
        }
}

最后我们来看下createContext()的方法:

//创建一个请求的上下文,可以说一个http请求都是在这个上下文环境中执行的
func (m *Martini) createContext(res http.ResponseWriter, req *http.Request) *context {
        c := &context{inject.New(), m.handlers, m.action, NewResponseWriter(res), 0}//其handlers和action都是继承自martini,NewResponseWriter()只是原先的http.ResponseWriter()增加了一些状态的包装
        c.SetParent(m)
        c.MapTo(c, (*Context)(nil))
        c.MapTo(c.rw, (*http.ResponseWriter)(nil))
        c.Map(req)
        return c
}

1-1-4) 实例1总结:

我们依照定义顺序来梳理下整个实例1中,use和map以及get等定义:

Classic():
    New():
        Map(m.logger)//标准输出的logger
        Map(defaultReturnHandler())//简单的返回值和状态码,实际是这样的函数type ReturnHandler func(Context, []reflect.Value),调用c.Next()陷入下一个中间件
    Use(Logger())//logger中间件,前后打印日志,需要类型有res http.ResponseWriter, req *http.Request, c Context, log *log.Logger,调用c.Next()陷入下一个中间件
    Use(Recovery()) //recovery中间件,从各种panic中恢复回来并设置返回头和body
    Use(Static("public")) //静态文件服务,执行完之后不陷入c.Next(),貌似是直接返回的,然后执行下个handle
    MapTo(r, (*Routes)(nil))
    Action(r.Handle)
Get()://注册路由规则
Run():
    ServeHTTP():
        c.MapTo(c, (*Context)(nil))
        c.MapTo(c.rw, (*http.ResponseWriter)(nil))
        c.Map(req)

执行顺序是:

  • 注册相关中间件和函数
  • 执行中间件(可以用context.Next()来陷入执行)
  • 执行action(一般是路由处理函数,如果有返回值还要执行下handleReturn函数)

实例1的解释就到这个程度啦,关于路由和中间件的详细内容还没有说,等待后文吧

 类似资料: