Tango,微內核可擴展的Go語言Web框架
Golang的web框架基本上處于一個井噴期,那么為什么要再造一個輪子。這是因為,目前可擴展性比較強的都是基于函數作為可執行體的,而以結構體作為執行體的框架目前可擴展性都不夠強,包括我原先寫的框架xweb也是如此。因此,一個全新的框架出來了,先上地址:https://github.com/lunny/tango。
初看Tango框架,感覺和Martini及Macaron非常的像。比如這段代碼:
package mainimport "github.com/lunny/tango"
func main() { t := tango.Classic() t.Get("/", func() string { return "Hello tango!" }) t.Run() }</pre>
這種其實大家都支持的。再看這個吧:
package mainimport "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 }
第三方插件
目前已經有了一批第三方插件,更多的插件正在陸續開發中,歡迎大家進行貢獻:
- session - Session管理
- xsrf - 生成csrf token和進行校驗
- bind - 自動綁定參數到結構體
- renders - Go模板引擎插件
- dispatch - 多應用支持
- tpongo2 - Pongo2 模板引擎支持
- captcha - 驗證碼插件
- events - 事件插件
- flash - 在requests之間共享數據