Tango,微内核可扩展的Go语言Web框架

xiaolunwen · · 5596 次点击 · 开始浏览    置顶
这是一个创建于 的主题,其中的信息可能已经有所发展或是发生改变。

转自 [lunny的博客](http://lunny.info/2015/01/07/1-Tango-micro-and-pluggable-web-framework-for-Go.html) # 简介 Golang的web框架基本上处于一个井喷期,那么为什么要再造一个轮子。这是因为,目前可扩展性比较强的都是基于函数作为可执行体的,而以结构体作为执行体的框架目前可扩展性都不够强,包括我原先写的框架xweb也是如此。因此,一个全新的框架出来了,先上地址:[https://github.com/lunny/tango](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")```来获得 * Catch-All路由 ``` tg.Get("/*name", new(Action)) ``` 命名路由对应的参数内容可通过```ctx.Params().Get("*name")```来获得 * 正则路由 ``` tg.Get("/(:name.*)", new(Action)) ``` 正则路由对应的参数内容可通过```ctx.Params().Get(":name")```来获得 # 执行体 同时支持函数执行体和结构体执行体,支持的函数执行体形式如下,可以有零个或一个返回值,返回值由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,默认状态码为200 * ```error``` 返回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自动或强制使用某种压缩。比如: ```Go 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压缩。 ```Go 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压缩,否则不压缩。 ```Go 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 让你用一行代码可以完成一个静态服务器。 ```Go func main() { t := tango.New(tango.Static()) t.Run() } ``` 然后,将你的文件放到 `./public` 目录下,你就可以通过浏览器放问到他们。比如: ``` http://localhost/images/logo.png --> ./public/images/logo.png ``` 当然,你也可以加入你```basicauth```或者你自己的认证中间件,这样就变为了一个私有的文件服务器。 ```Go func main() { t := tango.New() t.Use(AuthHandler) t.Use(tango.Static()) t.Run() } ``` # Handler Handler 是tango的中间件。在tango中,几乎所有的事情都由中间件来完成。撰写一个你自己的中间件非常简单,并且我们鼓励您只加载需要的中间件。 tango的中间件只需要符合以下接口即可。 ```Go type Handler interface { Handle(*tango.Context) } ``` 同时,tango也提供了```tango.HandlerFunc```,以方便你将一个函数包装为中间件。比如: ```Go 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指针的函数作为中间件,如: ```Go tg.Use(func(ctx *tango.Context){ fmt.Println("before") ctx.Next() fmt.Println("after") }) ``` 为了和标准库兼容,tango通过UseHandler支持http.Handler作为中间件,如: ```Go 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,那么当前请求将会被立即停止,之后的所有代码将不会被执行。 # 注入 更多的注入方式参见以下示例代码: ## Request ```Go type Action struct { tango.Req } ``` ## Response ```Go type Action struct { tango.Resp } ``` ## Context ```Go type Action struct { tango.Ctx } ``` ## Logger ```Go type Action struct { tango.Log } ``` ## Params ```Go type Action struct { tango.Params } ``` ## Json ```Go type Action struct { tango.Json } ``` ## Xml ```Go type Action struct { tango.Xml } ``` # 第三方插件 目前已经有了一批第三方插件,更多的插件正在陆续开发中,欢迎大家进行贡献: - [session](https://github.com/tango-contrib/session) - Session管理 - [xsrf](https://github.com/tango-contrib/xsrf) - 生成csrf token和进行校验 - [bind](https://github.com/tango-contrib/bind) - 自动绑定参数到结构体 - [renders](https://github.com/tango-contrib/renders) - Go模板引擎插件 - [dispatch](https://github.com/tango-contrib/dispatch) - 多应用支持 - [tpongo2](https://github.com/tango-contrib/tpongo2) - [Pongo2](https://github.com/flosch/pongo2) 模板引擎支持 - [captcha](https://github.com/tango-contrib/captcha) - 验证码插件 - [events](https://github.com/tango-contrib/events) - 事件插件 - [flash](https://github.com/tango-contrib/flash) - 在requests之间共享数据 # 案例 - [Wego](https://github.com/go-tango/wego) - golanghome.com论坛的修改版 - [ABlog](https://github.com/fuxiaohei/ablog) - 一个新型博客

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

5596 次点击  
加入收藏 微博
4 回复  |  直到 2015-01-15 01:57:54
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传