# Baa 路由
baa 基于 http resetfull 模式設計了路由管理器,提供了常規路由,參數路由,文件路由,靜態文件路由,還有組路由。
## 常規路由
```
func (b *Baa) Delete(pattern string, h ...HandlerFunc) RouteNode
func (b *Baa) Get(pattern string, h ...HandlerFunc) RouteNode
func (b *Baa) Head(pattern string, h ...HandlerFunc) RouteNode
func (b *Baa) Options(pattern string, h ...HandlerFunc) RouteNode
func (b *Baa) Patch(pattern string, h ...HandlerFunc) RouteNode
func (b *Baa) Post(pattern string, h ...HandlerFunc) RouteNode
func (b *Baa) Put(pattern string, h ...HandlerFunc) RouteNode
```
接受兩個參數,一個是URI路徑,另一個是 [HandlerFunc](https://godoc.org/github.com/go-baa/baa#HandlerFunc) 類型,設定匹配到該路徑時執行的方法;允許多個,按照設定順序進行鏈式處理。
返回一個 [RouteNode](https://godoc.org/github.com/go-baa/baa#RouteNode),該Node只有一個方法,`Name(name string)` 用于命名該條路由規則,以備后用。
除了以上幾個標準方法,還支持多個method設定的路由姿勢:
```
func (b *Baa) Route(pattern, methods string, h ...HandlerFunc) RouteNode
func (b *Baa) Any(pattern string, h ...HandlerFunc) RouteNode
```
## 路由語法
### 靜態路由
靜態路由語法就是沒有任何參數變量,pattern是一個固定的字符串。
使用示例:
```
package main
import (
"gopkg.in/baa.v1"
)
func main() {
app := baa.New()
app.Get("/foo", func(c *baa.Context) {
c.String(200, c.URL(true))
})
app.Get("/bar", func(c *baa.Context) {
c.String(200, c.URL(true))
})
app.Run("1323")
}
```
測試:
```
curl http://127.0.0.1:1323/foo
curl http://127.0.0.1:1323/bar
```
### 參數路由
靜態路由是最基礎的,但顯然滿足不了需求的,我們在程序中通常相同的資源訪問規則相同,不同的只是資源的編號,這時就該參數路由出場了。
> 參數路由以 `/` 為拆分單位,故每兩個斜線區間中只能有一個參數存在,更復雜的規則需要 正則路由。
參數路由以冒號 `:` 后面跟一個字符串作為參數名稱,可以通過 `Context`的 `Param` 系列方法獲取路由參數的值。
使用示例:
```
package main
import (
"fmt"
"gopkg.in/baa.v1"
)
func main() {
app := baa.New()
app.Get("/user/:id", func(c *baa.Context) {
c.String(200, "My user id is: " + c.Param("id"))
})
app.Get("/user/:id/project/:pid", func(c *baa.Context) {
id := c.ParamInt("id")
pid := c.ParamInt("pid")
c.String(200, fmt.Sprintf("user id: %d, project id: %d", id, pid))
})
app.Run("1323")
}
```
測試:
```
curl http://127.0.0.1:1323/user/101
curl http://127.0.0.1:1323/user/101/project/201
```
### 正則路由
`正則路由,默認的baa中不支持正則表達式路由,需要一個增強組件來支持。`
語法和參數路由接近,并兼容參數路由,可以直接使用 正則路由替換默認路由。
參數路由以冒號 `:` 后面跟一個字符串作為參數名稱,再加一對括號中間可以寫正則;如果省略括號默認為 `.*` 的正則匹配。
> 使用正則路由要先引入新的路由器,并通過DI替換掉內置路由。
使用示例:
```
package main
import (
"fmt"
"gopkg.in/baa.v1"
"github.com/go-baa/router/regtree"
)
func main() {
app := baa.New()
app.SetDI("router", regtree.New(app))
app.Get("/user/:id", func(c *baa.Context) {
c.String(200, "My user id is: " + c.Param("id"))
})
app.Get("/user/:id/project/:pid", func(c *baa.Context) {
id := c.ParamInt("id")
pid := c.ParamInt("pid")
c.String(200, fmt.Sprintf("user id: %d, project id: %d", id, pid))
})
app.Get("/user-:id(\\d+)", func(c *baa.Context) {
c.String(200, "My user id is: "+c.Param("id"))
})
app.Get("/user-:id(\\d+)-project-:pid(\\d+)", func(c *baa.Context) {
id := c.ParamInt("id")
pid := c.ParamInt("pid")
c.String(200, fmt.Sprintf("user id: %d, project id: %d", id, pid))
})
app.Run("1323")
}
```
測試:
```
curl http://127.0.0.1:1323/user/101
curl http://127.0.0.1:1323/user-101
curl http://127.0.0.1:1323/user/101/project/201
curl http://127.0.0.1:1323/user-101-project-201
```
## 路由選項
```
func (b *Baa) SetAutoHead(v bool)
```
我記得搜索引擎很喜歡用HEAD方法來檢查一個網頁是否能正常訪問。但我們一般又不會單獨寫一個HEAD的處理方法,一般行為是GET返回的數據不要內容。
使用 `app.SetAutoHead(true)` 將在設置 `GET` 方法時,自動添加 `HEAD` 路由,綁定和GET一樣的處理。
```
func (b *Baa) SetAutoTrailingSlash(v bool)
```
在URL訪問中,一個目錄要帶不帶最后的斜線也有很多爭議,google站長工具明確表示,帶不帶斜線將表示不同的URL資源,但是瀏覽習慣問題,很多時候帶不帶都能訪問到相同的資源目錄。
使用 `app.SetAutoTrailingSlash(true)` 將處理最后的斜線,將帶和不帶都統一行為,自動補全最后一個斜線。
## 組路由
```
func (b *Baa) Group(pattern string, f func(), h ...HandlerFunc)
```
組路由,常常被同事問道,這個功能太好用了,你是怎么想到這樣的設計,我說,我抄的,我抄的 [macaron](https://github.com/go-macaron/macaron),就是這么`直白`。
組路由,顧名思義,用來處理一組路由的需求,可以設定統一的前綴,統一的前置方法。
使用示例:
```
package main
import (
"fmt"
"gopkg.in/baa.v1"
)
func main() {
app := baa.New()
app.Group("/group", func() {
app.Get("/", func(c *baa.Context) {
c.String(200, "我是組的首頁")
})
app.Group("/user", func() {
app.Get("/", func(c *baa.Context) {
c.String(200, "我是組的用戶")
})
app.Get("/:id", func(c *baa.Context) {
c.String(200, "in group, user id: "+c.Param("id"))
})
})
app.Get("/:gid", func(c *baa.Context) {
c.String(200, "in group, group id: "+c.Param("gid"))
})
}, func(c *baa.Context) {
// 我是組內的前置檢測,過不了我這關休想訪問組內的資源
})
app.Run("1323")
}
```
測試:
```
curl http://127.0.0.1:1323/group/
curl http://127.0.0.1:1323/group/user/
curl http://127.0.0.1:1323/group/user/101
curl http://127.0.0.1:1323/group/111
```
### 鏈式處理
一個URL請求可以先處理A,根據A的結果再執行B。
**舉個例子:**
一個URL要先判斷你登錄過才可以訪問,就可以設定兩個Handler,第一個 判斷是否登錄,如果沒登錄就調到登錄界面,否則繼續執行第二個真正的內容。
使用示例:
```
package main
import (
"gopkg.in/baa.v1"
)
func main() {
app := baa.Default()
app.Get("/", func(c *baa.Context) {
c.String(200, "Hello, 世界")
})
app.Post("/", func(c *baa.Context) {
c.String(200, c.Req.Method)
})
app.Get("/admin", func(c *baa.Context) {
if c.GetCookie("login_id") != "admin" {
c.Redirect(302, "/login")
c.Break()
}
}, func(c *baa.Context) {
c.String(200, "恭喜你,看到后臺了")
})
app.Get("/login", func(c *baa.Context) {
c.Resp.Header().Set("Content-Type", "text/html; charset=utf-8")
c.SetCookie("login_id", "admin", 3600, "/")
c.Resp.Write([]byte("登錄成功,<a href=\"/admin\">點擊進入后臺</a>"))
})
app.Run(":1323")
}
```
## 命名路由
```
func (n *Node) Name(name string)
func (b *Baa) URLFor(name string, args ...interface{}) string
```
前面可以看到添加路由后,返回了一個 `RouteNode` 說可以做命名路由,有什么用呢?
就是給一個URL起個名字,然后在程序中可以通過 `URLFor`方法來生成這個符合這個路由的URL路徑。
舉個栗子:
```
app := baa.New()
app.Get("/user/:id/project", func(c *baa.Context) {
c.String(200, c.Baa().URLFor("user_project", c.Param("id")))
}).Name("user_project")
```
執行上面的方法,會輸出你當前訪問的URL,就是這個姿勢。
## 文件路由
```
func (b *Baa) Static(prefix string, dir string, index bool, h HandlerFunc)
func (b *Baa) StaticFile(pattern string, path string) RouteNode
```
在一個完整的應用中,我們除了業務邏輯,還有訪問圖片/CSS/JS等需求,通過文件路由,可以直接訪問文件或文件夾。
`app.StaticFile` 可以讓你直接訪問一個具體的文件,比如: robots.txt
`app.Static` 可以訪問一個目錄下所有的資源,甚至列出目錄結構,類似文件服務器。
舉個例子:
```
app := baa.New()
app.Static("/assets", "/data/www/public/asssets", true, func(c *baa.Context) {
// 你可以對輸出的結果干點啥的
})
app.Static("/robots.txt", "/data/www/public/robots.txt")
```
就是醬樣子,第一條路由就可以列出目錄和訪問下面的資源了。第二條路由可以直接返回一個靜態文件。
## 自定義錯誤
### 500錯誤
```
func (b *Baa) SetError(h ErrorHandleFunc)
```
要是運行過程中程序出錯了,怎么辦,會不會泄露你的隱私,能不能提供點錯誤日志?
baa 默認在 `debug` 模式下向瀏覽器發送具體的錯誤信息,線上運行只顯示 `Internal Server Error` 并返回 `500` 錯誤頭。
可以通過 `app.SetError` 來設置錯誤處理方法,該方法接受一個 [ErrorHandleFunc](https://godoc.org/github.com/go-baa/baa#ErrorHandleFunc) 類型。
### 404錯誤
```
func (b *Baa) SetNotFound(h HandlerFunc)
```
baa默認返回 `Not Found` 和 `404` 錯誤頭,你也可以通過 `app.SetNotFound`來自定義錯誤處理,該方法接受一個 [HandlerFunc](https://godoc.org/github.com/go-baa/baa#HandlerFunc) 類型。
舉個栗子:
```
app := baa.New()
app.SetError(func(err error, c *baa.Context) {
c.Baa().Logger().Println("記錄日志", err)
c.String(500, "出錯了")
})
app.SetNotFound(func(c *baa.Context) {
c.String(404, "頁面放假了,請稍后再來。")
})
app.Run(":1323")
```
## Websocket
```
func (b *Baa) Websocket(pattern string, h func(*websocket.Conn)) RouteNode
```
Websocket 用于和瀏覽器進行保持通話。
在這里我們嘗試了 官方的 `golang.org/x/net/websocket` 不好封裝,放棄了。
官方推薦了 `github.com/gorilla/websocket` 我們試了下,不錯哦,就用他了。
baa 的websocket路由,用于快速開始一個 websocket 服務,混合現有應用編程。
該方法有兩個參數,一個 `pattern` 路徑,一個 [*websocket.Conn]() 類型的鏈接。
舉個例子:
```
package main
import (
"fmt"
"time"
"gopkg.in/baa.v1"
"github.com/gorilla/websocket"
)
func main() {
app := baa.Default()
app.Get("/", func(c *baa.Context) {
c.String(200, "index")
})
app.Websocket("/socket", func(ws *websocket.Conn) {
for {
fmt.Println("websocket retry read...")
messageType, data, err := ws.ReadMessage()
if err != nil {
if websocket.IsCloseError(err) {
app.Logger().Println("websocket ReadMessage error: connection is closed")
} else {
app.Logger().Println("websocket ReadMessage error:", err)
}
ws.Close()
return
}
fmt.Println("websocket receive: ", messageType, string(data))
err = ws.WriteMessage(messageType, data)
if err != nil {
app.Logger().Println("websocket WriteMessage error:", err)
ws.Close()
return
}
}
})
app.Run(":1234")
fmt.Println("end")
}
```
含js和go代碼的完整示例:[example/websocket](https://github.com/go-baa/example/tree/master/websocket)
websocket的具體使用請參考 [gorilla/websocket](http://godoc.org/github.com/gorilla/websocket)