文档章节

Martini 的工作方式

喻恒春
 喻恒春
发布于 2014/01/14 14:59
字数 1950
阅读 13333
收藏 92
点赞 17
评论 14

后续参见Martini 中的 Handler

匿名字段

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

Injector基础

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

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

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

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

MapTo的使用

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

<!-- lang: cpp -->
package main

import (
    "fmt"
    "github.com/codegangsta/inject"
)

// 自定义一个空interface{}, 必须是interface{}类型MapTo才能接受
type SpecialString interface{}

// 原定义Foo(s1,s2 string), 改成
func Foo(s1 string, s2 SpecialString) {
    fmt.Println(s1, s2.(string)) // type assertion
}

func main() {
    ij := inject.New()
    ij.Map("a")
    // 注意第二个参数的固定写法
    ij.MapTo("b", (*SpecialString)(nil))
    ij.Invoke(Foo)
}

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

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

<!-- lang: cpp -->
package main
import (
	"fmt"
	"github.com/codegangsta/inject"
)
type Foo interface {
	Foo()
}
type Bar struct{}
func (bar *Bar) Foo() {
}
func Handler(foo Foo) {
	fmt.Println(foo)
}
func main() {
	v := &Bar{}
	ij := inject.New()
	// ij.Map(v) // 错误的用法
	ij.MapTo(v, (*Foo)(nil))
	fmt.Println(ij.Invoke(Handler))
}

如果用 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 结构, 扩展自 Injector, 是个顶级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()

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

martini.Classic()

<!-- lang: cpp -->
func Classic() *ClassicMartini {
	r := NewRouter()
	m := New()
	// 内置的 handlers
	m.Use(Logger())          // 日志, 事实上配合了Context.Next(),
	m.Use(Recovery())       // 精彩的 panic 捕获, 其实也配合Context.Next(),和Logger形成嵌套
	m.Use(Static("public")) // 静态文件
	m.Action(r.Handle)      // 关键, Router.Handle
	return &ClassicMartini{m, r}
}
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 Recovery() Handler {
	return func(res http.ResponseWriter, c Context, logger *log.Logger) {
		defer func() {
			if err := recover(); err != nil {
				res.WriteHeader(http.StatusInternalServerError)
				logger.Printf("PANIC: %s\n%s", err, debug.Stack())
			}
		}(
		c.Next()
	}
}

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

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

martini.NewRouter()

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

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

Martini.ServeHTTP 方法

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

context.run()

<!-- lang: cpp -->
func (c *context) run() {
	for c.index < len(c.handlers) {
		_, err := c.Invoke(c.handlers[c.index]) // Invoke了所有的 handlers.
		if err != nil {
			panic(err)
		}
		c.index += 1 // 很有用的计数器, 配合 Next 方法会产生一些其他用法

		if c.Written() { // break for 条件
			return
		}
	}
}

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

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

Router.Handle

<!-- lang: cpp -->
func (r *router) Handle(res http.ResponseWriter, req *http.Request, context Context) {
	for _, route := range r.routes {
		ok, vals := route.Match(req.Method, req.URL.Path)
		if ok { // 路由匹配成功
			params := Params(vals)
			context.Map(params)
			r := routes{}
			context.MapTo(r, (*Routes)(nil)) // 为支持 Routes.URLFor 做准备
			_, err := context.Invoke(route.Handle) // route.Handle 内部 Invoke 了用户定义的路由 RESTful handlers
			if err != nil {
				panic(err)
			}
			return
		}
	}

	// no routes exist, 404 // 路由匹配失败
	c := &routeContext{context, 0, r.notFounds} // 设置 handlers 为 notFounds
	context.MapTo(c, (*Context)(nil))
	c.run() // 内部 Invoke notFounds
}

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 机制完成.

© 著作权归作者所有

共有 人打赏支持
喻恒春

喻恒春

粉丝 104
博文 29
码字总数 21951
作品 5
郑州
程序员
加载中

评论(14)

喻恒春
喻恒春

引用来自“吾爱”的评论

弱弱的问个基础问题: (*Context)(nil) 这个表达式是什么意思?
生成一个 变量 类型是 *Context, 值是 nil. 这相当于把下面两条简化成一条了 var c *Context call(c) // call((*Context)(nil)) 因为 MapTo 函数的第2参数, 关心的是类型, 这么做只是为了把类型传过去.
吾爱
吾爱
弱弱的问个基础问题: (*Context)(nil) 这个表达式是什么意思?
喻恒春
喻恒春

引用来自“唐阳”的评论

求春哥把Martini的设计流程画一画,给个完整一点的demo

流程图暂时没有, 完整的项目可以看这个
https://github.com/shxsun/gobuild
martini-contrib 是Martini 开设的社区贡献中间件组织,看里面的代码,就是最好的demo.
https://github.com/martini-contrib
五杀联盟
五杀联盟
求春哥把Martini的设计流程画一画,给个完整一点的demo
喻恒春
喻恒春

引用来自“陈一回”的评论

春神这么极力推荐,本菜一定要去研究研究。不知道跟revel比起来如何。春神可否作个比较,比如从性能,扩展以及开发速度方面。0

不了解 revel.无法比较. 事实上我一看到那些仍然延续其他语言OOP的编程方法就主观排斥.一个小package还可以接受.大的框架无法接受
陈亦
陈亦
春神这么极力推荐,本菜一定要去研究研究。不知道跟revel比起来如何。春神可否作个比较,比如从性能,扩展以及开发速度方面。0
林龙辉
林龙辉

引用来自“喻恒春”的评论

引用来自“林龙辉”的评论

基础略差 官方例子没有找到 不知道有没有实例性的。。。。是不是太伸手党了

https://github.com/shxsun/gobuild
这应该算是一个完整使用martini的项目

thanks
喻恒春
喻恒春

引用来自“林龙辉”的评论

基础略差 官方例子没有找到 不知道有没有实例性的。。。。是不是太伸手党了

https://github.com/shxsun/gobuild
这应该算是一个完整使用martini的项目
林龙辉
林龙辉
基础略差 官方例子没有找到 不知道有没有实例性的。。。。是不是太伸手党了
喻恒春
喻恒春

引用来自“Irony”的评论

阿春哥,上首页了,恭喜啊!

同喜同喜
希望有更多了人试用,找到更实用的场景
Martini 中的 Handler

前文参见 Martini 的工作方式 Handler 在Martini中是这样定义的 事实上 Handler 就是一个函数. Handler 贯穿于多个对象中. Martini Martini 对象设置Handler的方 Use 一次设置一个 handler ...

喻恒春
2014/02/07
0
1
Martini 极好的 Go WEB 框架

已知的其他框架看到的是传统OOP的影子, 到处充蚀 Class 风格的 OOP 方法. 而我们知道GoLang中是没有Class的. 笔者也曾努力用Go 的风格做WEB开发, 总感到力不从心. 写出的代码不能完全称之为框...

喻恒春
2014/01/07
0
16
技术晨读_20160217

技术导读 Build a RESTful API with Martini 使用martini搭建一个Restful API,使用的是简易的内存database,搭建了一套支持json和xml的RESTFUL的API http://0value.com/build-a-restful-API...

王二狗子11
01/07
0
0
国内唯一两部华为官方HCIE MPLS新书目录抢鲜暴光

历经一年多时间,在华为公司多位领导和一线产品技术专家严格审核、人民邮电出版社的指导下,本人编著的两部华为HCIE官方指定培训教材:《华为MPLS技术学习指南》和《华为MPLS VPN学习指南》即...

茶乡浪子
2017/12/24
0
0
Python/Ruby/Go/Node 之四国大战

Python Flask vs Ruby Sinatra vs Go Martini vs Node Express 本文授权转载自 zybuluo 博客。 题外话一:最近一段时间,Cloud Insight 接连发布了三种语言(Python, Node, Ruby)的SDK,Clo...

OneAPM蓝海讯通
2016/03/17
136
0
cordova-hcp server 报错

Running serverCould not create tunnel: Error: panic: runtime error: invalid memory address or nil pointer dereference github.com/inconshreveable/olive/recover.go:40runtime/asm_a......

mamie42
2016/10/17
121
0
Martini 组件和实用工具--martini-contrib

martini-contrib 是一个贡献 Martini 的处理程序和实用工具,这个包包括了各种添加到 Martini 上面的组件和一个高级的GO web 框架。

叶秀兰
2014/01/14
478
0
有起有伏,使用 Go 一年的体验分享

我们公司 Mobile Jazz 从一个内部试验性项目开始使用 Go。如公司名暗示的那样,我们是开发移动应用的。 在发布一个应用给公众后,我们很快意识到我们缺失一个工具来检查用户实际发生的情况以...

达尔文
2017/06/29
2.8K
18
Go 语言的 Web 框架--Martini

Martini 是一个非常新的 Go 语言的 Web 框架,使用 Go 的 net/http 接口开发,类似 Sinatra 或者 Flask 之类的框架,你可使用自己的 DB 层、会话管理和模板。 特性: 使用非常简单 无侵入设计...

红薯
2013/11/21
21.3K
13
Golang 建立RESTful webservice 接收客户端POST请求发送wav语音文件

首先看下服务器端,服务器端使用martini框架,仅建立一个简单的接收客户端post请求并保存客户端传过来的语音的后台服务: 原文地址:http://liuxp0827.blog.51cto.com/5013343/1412977 packa...

ponpon_
2014/05/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Spring基础

Spring是什么? Spring是一个开源框架,最早由Rod Johnson创建,它解决的是业务逻辑层和其他各层的松耦合问题。 经过十几年的发展,Spring正在扩展其他的领域,如:移动开发、社交API集成、N...

这很耳东先生
2分钟前
0
0
面试系列-40个Java多线程问题总结

前言 这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题。 这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也...

Ryan-瑞恩
16分钟前
0
0
微信分享的细节

分享的缩略图要求: 一、图片大小小于32k 二、图片的尺寸为 宽度 :128px 高度:128px 分享title 和 description 出现金额等 以上情况存在会导致触发分享按钮 但是页面没有反应...

Js_Mei
21分钟前
0
0
【2018.07.23学习笔记】【linux高级知识 Shell脚本编程练习】

1、编写shell脚本,计算1-100的和; #!/bin/bashsum=0for i in `seq 1 100`do sum=$[$sum+$i]doneecho $sum 2、编写shell脚本,要求输入一个数字,然后计算出从1到输入数字的和,要求...

lgsxp
24分钟前
0
0
xss攻防浅谈

导读 XSS (Cross-Site Script) 攻击又叫跨站脚本攻击, 本质是一种注入攻击. 其原理, 简单的说就是利用各种手段把恶意代码添加到网页中, 并让受害者执行这段脚本. XSS能做用户使用浏览器能做的...

吴伟祥
24分钟前
0
0
js回调的一次应用

function hideBtn(option) { if (option == 1) { $("#addBtn").hide(); $("#addSonBtn").hide(); }}$("body").on("click", "#selectBtn", function () {......

晨猫
30分钟前
0
0
C++_读写ini配置文件

1.WritePrivateProfileString:

一个小妞
30分钟前
0
0
通往阿里,BAT的50+经典Java面试题及答案解析(上)

Java是一个支持并发、基于类和面向对象的计算机编程语言。下面列出了面向对象软件开发的优点: 代码开发模块化,更易维护和修改。 代码复用。 增强代码的可靠性和灵活性。 增加代码的可理解性...

Java大蜗牛
31分钟前
1
0
数据库两大神器【索引和锁】

前言 只有光头才能变强 索引和锁在数据库中可以说是非常重要的知识点了,在面试中也会经常会被问到的。 本文力求简单讲清每个知识点,希望大家看完能有所收获 声明:如果没有说明具体的数据库...

Java3y
34分钟前
0
0
Application Express安装

Application Express安装文档 数据库选择和安装 数据库选择 Oracle建议直接12.2.0.1.0及以上的版本,12.1存在20618595bug(具体可参见官方文档) Oracle 12c 中安装oracle application expr...

youfen
46分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部