Tango,微内核可扩展的Go语言Web框架
简介
Golang的web框架基本上处于一个井喷期,那么为什么要再造一个轮子。这是因为,目前可扩展性比较强的都是基于函数作为可执行体的,而以结构体作为执行体的框架目前可扩展性都不够强,包括我原先写的框架xweb也是如此。因此,一个全新的框架出来了,先上地址:https://github.com/lunny/tango。
初看Tango框架,感觉和Martini及Macaron非常的像。比如这段代码:
package main
import "github.com/lunny/tango"
func main() {
t := tango.Classic()
t.Get("/", func() string {
return "Hello tango!"
})
t.Run()
}
这种其实大家都支持的。再看这个吧:
package main
import "github.com/lunny/tango"
type Action struct {}
func (Action) Get() string {
return "Hello tango!"
}
func main() {
t := tango.Classic()
t.Get("/", new(Action))
t.Run()
}
Tango同时支持函数和结构体作为执行体,不过主打是结构体,函数只作为兼容而用。下面对Tango的各种功能一一道来。
路由
Tango目前同时支持3种路由规则:
静态路由
tg.Get("/", new(Action))
命名路由
tg.Get("/:name", new(Action))
命名路由对应的参数内容可通过
ctx.Params().Get(":name")
来获得正则路由
tg.Get("/(.*)", new(Action))
正则路由对应的参数内容可通过ctx.Params().Get(":0")
来获得
这里要注意命名路由和正则路由不可混用。
执行体
同时支持函数执行体和结构体执行体,支持的函数执行体形式如下,可以有零个或一个返回值,返回值由Return插件提供,后面会再提:
func()
func(http.ResponseWriter, *http.Request)
func(*tango.Context)
func(http.Response.Writer)
func(*http.Request)
同时支持包含Get,Post,Head,Options,Trace,Patch,Delete,Put
作为成员方法的结构体作为执行体。
type Action struct {}
func (Action) Get() string {
return "Get"
}
func (Action) Post() string {
return "Post"
}
func (Action) Head() string {
return "Head"
}
func (Action) Options() string {
return "Options"
}
func (Action) Trace() string {
return "Trace"
}
func (Action) Patch() string {
return "Patch"
}
func (Action) Delete() string {
return "Delete"
}
func (Action) Put() string {
return "Put"
}
在使用路由时,可以用对应的方法或者Any来加路由:
t := tango.Classic()
t.Get("/1", new(Action))
t.Any("/2", new(Action))
路由分组
Tango提供了Group来进行路由分组,Group的最简单形式如下:
g := tango.NewGroup()
g.Get("/1", func() string {
return "/1"
})
g.Post("/2", func() string {
return "/2"
})
t := tango.Classic()
t.Group("/api", g)
这样访问/api/1就会返回/1,访问/api/2就会返回/2
同时也可以这样:
t := tango.Classic()
t.Group("/api", func(g *tango.Group) {
g.Get("/1", func() string {
return "/1"
})
g.Post("/2", func() string {
return "/2"
})
})
Group也支持子Group:
t := tango.Classic()
t.Group("/api", func(g *tango.Group) {
g.Group("/v1", func(cg *tango.Group) {
cg.Get("/1", func() string {
return "/1"
})
cg.Post("/2", func() string {
return "/2"
})
})
})
最后,Group也支持逻辑分组:
o := tango.Classic()
o.Group("", func(g *tango.Group) {
g.Get("/1", func() string {
return "/1"
})
})
o.Group("", func(g *tango.Group) {
g.Post("/2", func() string {
return "/2"
})
})
这样,即使路由间只是逻辑上分开,而并没有上级路径分开,也是可以分成不同的组。
返回值
通过前面的例子,我们会发现,所有执行体函数,可以有一个返回值或者没有返回值。Tango的内部插件ReturnHandler来负责根据函数的第一个返回值的类型来自动的生成输出,默认规则如下:
string
返回string,则string会被转换为bytes并写入到ResponseWriter,默认状态码为200[]byte
返回[]byte, 则会直接写入ResponseWriter,默认状态码为200error
返回error接口,如果不为nil, 则返回状态码为500,内容为error.Error()
AbortError
返回tango.AbortError接口,如果不为nil,则返回状态码为AbortError.Code
,内容为AbortError.Error()
当然了,你可以撰写你自己的插件来判断更多的返回值类型。返回值结合tango的一些tricker,可以极大的简化代码,比如:
如果Action结构体包含匿名结构体tango.Json
或者tango.Xml
,则返回值结果如下:
如果包含tango.Json
匿名结构体,则返回头的Content-Type
会自动设置为:application/json
:
如果返回值为error,则返回值为{“err”: err.Error()},状态码为200
如果返回值为AbortError,则返回值为{“err”: err.Error()},状态码为err.Code()
如果返回值为string,则返回值为{“content”: content},状态码为200
如果返回值为[]byte,则返回值为{“content”: string(content)},状态码为200
如果返回值为map,slice,结构体或其它可自动Json化的内容,则返回值为map自动json对应的值,状态码为200
例如:
type Action struct {
tango.Json
}
var i int
func (Action) Get() interface{} {
if i == 0 {
i = i + 1
return map[string]interface{}{"i":i}
}
return errors.New("could not visit")
}
func main() {
t := tango.Classic()
t.Any("/", new(Action))
t.Run()
}
以上例子,访问时会始终返回json,第一次访问会返回map,第二次返回error。(注:这里只是演示代码,实际执行i必须加锁)
- Compress
Tango拥有一个默认的压缩中间件,可以按照扩展名来进行文件的压缩。同时,你也可以要求某个Action自动或强制使用某种压缩。比如:
type CompressExample struct {
tango.Compress // 添加这个匿名结构体,要求这个结构体的方法进行自动检测压缩
}
func (CompressExample) Get() string {
return fmt.Sprintf("This is a auto compress text")
}
o := tango.Classic()
o.Get("/", new(CompressExample))
o.Run()
以上代码默认会检测浏览器是否支持压缩,如果支持,则看是否支持gzip,如果支持gzip,则使用gzip压缩,如果支持deflate,则使用deflate压缩。
type GZipExample struct {
tango.GZip // add this for ask compress to GZip, if accept-encoding has no gzip, then not compress
}
func (GZipExample) Get() string {
return fmt.Sprintf("This is a gzip compress text")
}
o := tango.Classic()
o.Get("/", new(GZipExample))
o.Run()
以上代码默认会检测浏览器是否支持gzip压缩,如果支持gzip,则使用gzip压缩,否则不压缩。
type DeflateExample struct {
tango.Deflate // add this for ask compress to Deflate, if not support then not compress
}
func (DeflateExample) Get() string {
return fmt.Sprintf("This is a deflate compress text")
}
o := tango.Classic()
o.Get("/", new(DeflateExample))
o.Run()
以上代码默认会检测浏览器是否支持deflate压缩,如果支持deflate,则使用deflate压缩,否则不压缩。
Static
Static 让你用一行代码可以完成一个静态服务器。
func main() {
t := tango.New(tango.Static())
t.Run()
}
然后,将你的文件放到 ./public
目录下,你就可以通过浏览器放问到他们。比如:
http://localhost/images/logo.png --> ./public/images/logo.png
当然,你也可以加入basicauth
或者你自己的认证中间件,这样就变为了一个私有的文件服务器。
func main() {
t := tango.New()
t.Use(AuthHandler)
t.Use(tango.Static())
t.Run()
}
Handler
Handler 是tango的中间件。在tango中,几乎所有的事情都由中间件来完成。撰写一个你自己的中间件非常简单,并且我们鼓励您只加载需要的中间件。
tango的中间件只需要符合以下接口即可。
type Handler interface {
Handle(*tango.Context)
}
同时,tango也提供了tango.HandlerFunc
,以方便你将一个函数包装为中间件。比如:
func MyHandler() tango.HandlerFunc {
return func(ctx *tango.Context) {
fmt.Println("this is my first tango handler")
ctx.Next()
}
}
t := tango.Classic()
t.Use(MyHandler())
t.Run()
正常的形式也可以是:
type HelloHandler struct {}
func (HelloHandler) Handle(ctx *tango.Context) {
fmt.Println("before")
ctx.Next()
fmt.Println("after")
}
t := tango.Classic()
t.Use(new(HelloHandler))
t.Run()
当然,你可以直接将一个包含tango.Context指针的函数作为中间件,如:
tg.Use(func(ctx *tango.Context){
fmt.Println("before")
ctx.Next()
fmt.Println("after")
})
为了和标准库兼容,tango通过UseHandler支持http.Handler作为中间件,如:
tg.UseHandler(http.Handler(func(resp http.ResponseWriter, req *http.Request) {
}))
老的中间件会被action被匹配之前进行调用。
Call stack
以下是中间件的调用顺序图:
tango.ServeHttp
|--Handler1
|--Handler2
|-- ...HandlerN
|---Action(If matched)
...HandlerN--|
Handler2 ----|
Handler1--|
(end)--|
在中间件中,您的中间件代码可以在Next()
被调用之前或之后执行,Next表示执行下一个中间件或Action被执行(如果url匹配的话)。如果不调用Next,那么当前请求将会被立即停止,之后的所有代码将不会被执行。
注入
更多的注入方式参见以下示例代码:
Context
type Action struct {
tango.Ctx
}
Logger
type Action struct {
tango.Log
}
Params
type Action struct {
tango.Params
}
Json
type Action struct {
tango.Json
}
Xml
type Action struct {
tango.Xml
}
第三方插件
目前已经有了一批第三方插件,更多的插件正在陆续开发中,欢迎大家进行贡献:
- session - Session管理
- xsrf - 生成csrf token和进行校验
- bind - 自动绑定参数到结构体
- renders - Go模板引擎插件
- dispatch - 多应用支持
- tpongo2 - Pongo2 模板引擎支持
- captcha - 验证码插件
- events - 事件插件
- flash - 在requests之间共享数据
案例
- Wego - golanghome.com论坛的修改版
- Blog - 一个新型博客
- Godaily - github.com/godaily/news - Go新闻
- dbweb - go语言写的数据库管理web界面