Tango,微内核可扩展的 Go 语言 Web 框架 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
lunny
V2EX    程序员

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

  •  1
     
  •   lunny
    lunny 2015-01-27 10:12:08 +08:00 4264 次点击
    这是一个创建于 3916 天前的主题,其中的信息可能已经有所发展或是发生改变。

    转自 lunny的博客

    简介

    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种路由规则:

    • 静态路由
      Go
      tg.Get("/", new(Action))

    • 命名路由
      Go
      tg.Get("/:name", new(Action))

    命名路由对应的参数内容可通过ctx.Params().Get(":name")来获得

    • 正则路由 Go 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作为成员方法的结构体作为执行体。
    ```Go
    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

    同时也可以这样:
    Go
    t := tango.Classic()
    t.Group("/api", func(g *tango.Group) {br> g.Get("/1", func() string {
    return "/1"
    })
    g.Post("/2", func() string {
    return "/2"
    })
    })

    Group也支持子Group:
    Go
    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也支持逻辑分组:
    ```Go
    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会被转换为bytes并写入到ResponseWriter,默认状态码为200 
      Error: language "string```" is not supported
    • 返回[]byte, 则会直接写入ResponseWriter,默认状态码为200 
      Error: language "[]byte```" is not supported
    • 返回error接口,如果不为nil, 则返回状态码为500,内容为```error.Error()``` 
      Error: language "error```" is not supported
    • 返回tango.AbortError接口,如果不为nil,则返回状态码为```AbortError.Code```,内容为```AbortError.Error()``` 
      Error: language "AbortError```" is not supported

    当然了,你可以撰写你自己的插件来判断更多的返回值类型。返回值结合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

    例如:
    ```Go
    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必须加锁)

    压缩

    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或者你自己的认证中间件,这样就变为了一个私有的文件服务器。
    Go
    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,以方便你将一个函数包装为中间件。比如:
    ```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()
    ```

    正常的形式也可以是:
    ```Go
    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

    type Action struct { tango.Req } 

    Response

    type Action struct { tango.Resp } 

    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 } 

    第三方插件

    目前已经有了一批第三方插件,更多的插件正在陆续开发中,欢迎大家进行贡献:

    案例

    • Wego - golanghome.com论坛的修改版
    • ABlog - 一个新型博客
    7 条回复    2015-01-28 17:55:15 +08:00
    Bearox
        1
    Bearox  
       2015-01-27 15:35:17 +08:00
    你好,我是一个大四的计算机相关学生。没有任何项目经验,出于兴趣,想学一下GO语言,有什么建议么?还有Go语言的前景如何,我纯粹是因为看好google,所以才看好GO语言。对此不是很了解。
    jinzhe
        2
    jinzhe  
       2015-01-27 23:25:55 +08:00
    感觉语法不爽,为啥初始化不是tango.Init()而是tango.Classic(),还有o.Get("/", new(CompressExample))其中的new感觉多余
    lunny
        3
    lunny  
    OP
       2015-01-28 10:04:55 +08:00
    @Bearox 个人感觉Go语言前景广阔啊。学一学你就知道了。
    lunny
        4
    lunny  
    OP
       2015-01-28 10:06:48 +08:00
    @jinzhe 用Classic而不用Init是因为,tango.Classic()包含了一些内置中间件,如果你要完全写自己的中间件,那么可以tango.New(),o.Get("/", new(CompressExample)),这个new是Go的新建对象语法。
    lunny
        5
    lunny  
    OP
       2015-01-28 10:07:10 +08:00
    有些Markdown格式化不正确,但是系统不让我修改了。。。
    jinzhe
        6
    jinzhe  
       2015-01-28 11:43:24 +08:00
    @lunny 感觉和马卡龙和martini差不多
    lunny
        7
    lunny  
    OP
       2015-01-28 17:55:15 +08:00
    @jinzhe 不一样,martini和macaron都是以函数作为控制器载体,tango主要是以结构体作为控制器载体。中间件这块形式上和martini和macaron有点像。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2670 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 21ms UTC 12:15 PVG 20:15 LX 05:15 JFK 08:15
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86