Tango,微內核可擴展的Go語言Web框架

jopen 9年前發布 | 35K 次閱讀 Tango Google Go/Golang開發

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() }</pre>

這種其實大家都支持的。再看這個吧:

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() }</pre>

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,默認狀態碼為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自動或強制使用某種壓縮。比如:

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(Static("./public", "", []string{"index.html", "index.htm"}))
    t.Run()
}

然后,將你的文件放到./public目錄下,你就可以通過瀏覽器放問到他們。比如:

http://localhost/images/logo.png  --> ./public/images/logo.png

當然,你也可以加入你basicauth或者你自己的認證中間件,這樣就變為了一個私有的文件服務器。

func main() {
    t := tango.New()
    t.Use(AuthHandler)
    t.Use(Static("./public", "", []string{"index.html", "index.htm"}))
    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,那么當前請求將會被立即停止,之后的所有代碼將不會被執行。

注入

更多的注入方式參見以下示例代碼:

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 - 一個新型博客
 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!