Skip to content

Latest commit

 

History

History
424 lines (311 loc) · 11.3 KB

router.md

File metadata and controls

424 lines (311 loc) · 11.3 KB

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 类型,设定匹配到该路径时执行的方法;允许多个,按照设定顺序进行链式处理。

返回一个 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 (
	"github.com/go-baa/baa"
)

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

参数路由

静态路由是最基础的,但显然满足不了需求的,我们在程序中通常相同的资源访问规则相同,不同的只是资源的编号,这时就该参数路由出场了。

参数路由以 / 为拆分单位,故每两个斜线区间中只能有一个参数存在,更复杂的规则需要 正则路由。

参数路由以冒号 : 后面跟一个字符串作为参数名称,可以通过 ContextParam 系列方法获取路由参数的值。

使用示例:

package main

import (
	"fmt"
	"github.com/go-baa/baa"
)

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"
	"github.com/go-baa/baa"
	"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,就是这么直白

组路由,顾名思义,用来处理一组路由的需求,可以设定统一的前缀,统一的前置方法。

使用示例:

package main

import (
	"fmt"
	"github.com/go-baa/baa"
)

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 (
	"github.com/go-baa/baa"
)

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 类型。

404错误

func (b *Baa) SetNotFound(h HandlerFunc)

baa默认返回 Not Found404 错误头,你也可以通过 app.SetNotFound来自定义错误处理,该方法接受一个 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"

	"github.com/go-baa/baa"
	"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

websocket的具体使用请参考 gorilla/websocket