Martini 的工作方式

前端之家收集整理的这篇文章主要介绍了Martini 的工作方式前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

后续参见Martini 中的 Handler

匿名字段

因为golang中没有继承,golang中用的方法是匿名字段,很多golang开发者称之为复合,可是我没有发现官方文档中对此进行正规命名,用继承这个词肯定不合适,容易对初学者造成理解上的错误,复合这个词很多初学者不一定知道具体含义. 干脆直接写作扩展自.

Injector基础

Martini 极好的 Go WEB 框架一笔带过 Injector 的功能: 通过对被调用(Invoke)函数的参数类型匹,对函数进行调用.

这里重新列举Injector 在线文档中Injector的部分定义.

下面的代码是为了方便把匿名接口列举到了一起,必须注意事实上Injector是有多个匿名接口复合.实际运用也许会有更多变化.

  1. <!-- lang: cpp -->
  2. type Injector interface{
  3. // 设置父Injector
  4. SetParent(parent Injector)
  5. // Maps val. 以val的反射Type为key,反射Value为值
  6. Map(val interface{}) TypeMapper
  7. // Maps val. 以 ifacePtr 的反射Type为key,val的反射Value为值
  8. // ifacePtr 正如其名必须是个指针
  9. MapTo(val interface{},ifacePtr interface{}) TypeMapper
  10. // 在已经Maps中匹配 t 返回reflect.Value
  11. Get(t reflect.Type) reflect.Value
  12. // 调用函数 f,通过其参数类型定义,在Maps中匹配参数对应的反射值
  13. // 返回: 执行结果,错误
  14. Invoke(f interface{}) ([]reflect.Value,error)
  15. // 匹配已经Maps的值,赋值给对应的 val 字段.
  16. // val 必须是一个*struct,通过其字段定义中的 tag语法 `inject`
  17. Apply(val interface{}) error
  18. }
  19. // 默认的Injector实现struct的定义
  20. type injector struct {
  21. values map[reflect.Type]reflect.Value // Map,MapTo的参数val就保存在这里
  22. parent Injector
  23. }

MapTo的使用

Injector 的功能很简洁,很容易理解. values中同一种类型只保存一个. 而现实中一个函数的参数中可能有多个相同的类型.这需要用到MapTo来解决.

  1. <!-- lang: cpp -->
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "github.com/codegangsta/inject"
  7. )
  8.  
  9. // 自定义一个空interface{},必须是interface{}类型MapTo才能接受
  10. type SpecialString interface{}
  11.  
  12. // 原定义Foo(s1,s2 string),改成
  13. func Foo(s1 string,s2 SpecialString) {
  14. fmt.Println(s1,s2.(string)) // type assertion
  15. }
  16.  
  17. func main() {
  18. ij := inject.New()
  19. ij.Map("a")
  20. // 注意第二个参数的固定写法
  21. ij.MapTo("b",(*SpecialString)(nil))
  22. ij.Invoke(Foo)
  23. }

看上去为了解决这个问题,要多写一些代码. 这不是问题,多写的这几行代码给你带来的方便更多.

事实上MapTo还有其他的应用方法. Martini.go 中的 createContext 方法展示了用法. 这里举例一个比较明显的例子

  1. <!-- lang: cpp -->
  2. package main
  3. import (
  4. "fmt"
  5. "github.com/codegangsta/inject"
  6. )
  7. type Foo interface {
  8. Foo()
  9. }
  10. type Bar struct{}
  11. func (bar *Bar) Foo() {
  12. }
  13. func Handler(foo Foo) {
  14. fmt.Println(foo)
  15. }
  16. func main() {
  17. v := &Bar{}
  18. ij := inject.New()
  19. // ij.Map(v) // 错误用法
  20. ij.MapTo(v,(*Foo)(nil))
  21. fmt.Println(ij.Invoke(Handler))
  22. }

如果用 Map 会产生 Value not found for type main.Foo,因为 reflect 中的 Call 方法强制类型匹配

Martini 基础

Martini 在线文档

列举Martini中的部分type

  • Route 接口,一条具体的路由,由Router 的RESTful方法生成具体对象
  • Router 接口,http.Request 路由器,RESTful风格,在其Handle方法中进行路由匹配
  • Context 接口,扩展自 Injector,请求上下文,Martini.ServeHTTP 方法生成具体对象
  • Routes 接口,MapTo Context.
  • Martini 结构,是个顶级WEB应用,衔接上面的各种接口,完成需求. Action方法设定最后一个handle函数
  • ClassicMartini,扩展自*Martini和Router. 经典在于把 Router.Handle 设置为 Martini 的 action
  • Params map[string]string,保存路由定义中的 name/value,MapTo Context
  • ResponseWriter 接口,实现了 http.ResponseWriter,http.Flusher,http.CloseNotifier 接口,并扩展了几个方法.

如何工作

对应上述列举,我们来解释

martini.New()

  1. <!-- lang: cpp -->
  2. func New() *Martini {
  3. m := &Martini{inject.New(),[]Handler{},func() {},log.New(os.Stdout,"[martini] ",0)}
  4. m.Map(m.logger) // Map 了默认的log.Logger
  5. m.Map(defaultReturnHandler()) // Map 了默认的ReturnHandler 函数对象
  6. return m
  7. }

martini.Classic()

  1. <!-- lang: cpp -->
  2. func Classic() *ClassicMartini {
  3. r := NewRouter()
  4. m := New()
  5. // 内置的 handlers
  6. m.Use(Logger()) // 日志,事实上配合了Context.Next(),m.Use(Recovery()) // 精彩的 panic 捕获,其实也配合Context.Next(),和Logger形成嵌套
  7. m.Use(Static("public")) // 静态文件
  8. m.Action(r.Handle) // 关键,Router.Handle
  9. return &ClassicMartini{m,r}
  10. }
  11. func Logger() Handler {
  12. return func(res http.ResponseWriter,req *http.Request,c Context,log *log.Logger) {
  13. start := time.Now()
  14. log.Printf("Started %s %s",req.Method,req.URL.Path)
  15. rw := res.(ResponseWriter)
  16. c.Next()
  17. log.Printf("Completed %v %s in %v\n",rw.Status(),http.StatusText(rw.Status()),time.Since(start))
  18. }
  19. }
  20. func Recovery() Handler {
  21. return func(res http.ResponseWriter,logger *log.Logger) {
  22. defer func() {
  23. if err := recover(); err != nil {
  24. res.WriteHeader(http.StatusInternalServerError)
  25. logger.Printf("PANIC: %s\n%s",err,debug.Stack())
  26. }
  27. }(
  28. c.Next()
  29. }
  30. }

Logger 和 Recovery 的代码由于都用了 c.Next(),所以形成了嵌套调用.

Martini.Action 设置最后的 handler,多数情况下是 Router.Handle. 本篇暂时不讨论变化.

martini.NewRouter()

  1. <!-- lang: cpp -->
  2. func NewRouter() Router {
  3. return &router{notFounds: []Handler{http.NotFound}}
  4. }

主要设置了默认的 notFounds handler.可以通过 Router.NotFound 进行设置

Martini.ServeHTTP 方法

  1. <!-- lang: cpp -->
  2. func (m *Martini) ServeHTTP(res http.ResponseWriter,req *http.Request) {
  3. m.createContext(res,req).run()
  4. }
  5. func (m *Martini) createContext(res http.ResponseWriter,req *http.Request) *context {
  6. // 动态创建了 *context 对象,action 变成最后一个handlers
  7. c := &context{inject.New(),append(m.handlers,m.action),NewResponseWriter(res),0}
  8. c.SetParent(m) // 设置Injector的parent
  9. c.MapTo(c,(*Context)(nil)) // MapTo Context
  10. c.MapTo(c.rw,(*http.ResponseWriter)(nil)) // MapTo ResponseWriter
  11. c.Map(req) // Map http.Request
  12. return c
  13. }

context.run()

  1. <!-- lang: cpp -->
  2. func (c *context) run() {
  3. for c.index < len(c.handlers) {
  4. _,err := c.Invoke(c.handlers[c.index]) // Invoke了所有的 handlers.
  5. if err != nil {
  6. panic(err)
  7. }
  8. c.index += 1 // 很有用的计数器,配合 Next 方法会产生一些其他用法
  9.  
  10. if c.Written() { // break for 条件
  11. return
  12. }
  13. }
  14. }

关于 Context.Next() 的技巧,参考 martini.Recovery().

如果你不使用 ClassicMartini,那么你需要自己通过 Martini.Use/Martini.Action 控制 handlers.

Router.Handle

  1. <!-- lang: cpp -->
  2. func (r *router) Handle(res http.ResponseWriter,context Context) {
  3. for _,route := range r.routes {
  4. ok,vals := route.Match(req.Method,req.URL.Path)
  5. if ok { // 路由匹配成功
  6. params := Params(vals)
  7. context.Map(params)
  8. r := routes{}
  9. context.MapTo(r,(*Routes)(nil)) // 为支持 Routes.URLFor 做准备
  10. _,err := context.Invoke(route.Handle) // route.Handle 内部 Invoke 了用户定义的路由 RESTful handlers
  11. if err != nil {
  12. panic(err)
  13. }
  14. return
  15. }
  16. }
  17.  
  18. // no routes exist,404 // 路由匹配失败
  19. c := &routeContext{context,r.notFounds} // 设置 handlers 为 notFounds
  20. context.MapTo(c,(*Context)(nil))
  21. c.run() // 内部 Invoke notFounds
  22. }

Route 和 Routes 暴露出的接口只有URLWith和URLFor,Context. URLFor 比较有趣,提供了更多变化的可能,有时间单独介绍.

Martini 没有对 Route 对象进行Map/MapTo. 到不是 Martini 忘记了做了. 而是 Router 的RESTful 方法返回的就是 Route,如果需要 Map,应该由应用来完成.

总结

Martini 提供了 martini.Classic() 来支持常规的应用场景. 如果你有自己特殊的需求,那你需要自己控制 handlers,把Martini对象和Router对象联系起来.注意以下几点:

  • Action 方法的作用,通常应该是 Router.Handle
  • martini.Recovery() 捕获 panic 的技巧
  • 记得保证 handles 执行的时候要预先把参数用 Context 的 Map/MapTo 准备好

并发安全问题

对于WEB开发,Handler,Router多数都是固定,一般不会在运行期动态改变,Context 是在具体的请求中动态生成的,通常也不必考虑并发问题. Martini 依赖的 Injector 用了map,Injector 对map的操作没有考虑并发安全. 因此并发时与 Injector 相关 map 单纯的读并发问题应该是安全的. 也就是说 Injector 是非并发安全的,为了保证并发 map 安全,martini 应用在 server 运行期不要使用 Martini 对象进行 Map/MapTo/Get 这样的操作.

martini-contrib 中有一些足够好的package可以作为非常好的例子,比如 模板渲染 render,sessions,binding,strip的代码中有类似子路由的用法. 这里就不再copy代码了.

如果你担心默认Router正则的效率或者有复杂的需求,那类似子路由的用法你可以参考.

Martini的核心Injector简直是为WEB场景量身打造的. WEB场景中确实存在一个类型的控制变量只有一份的特点.

Martini 社区组件

开发者建立了 martini-contrib 组织. 这样的管理方式更开放. 事实上这样符合 Martini Injector 的风格,组件之间的依赖可以通过 Injector 的 Map/Invoke 机制完成.

猜你在找的Go相关文章