- Created by 郭强, last modified on Nov 01, 2023
GoFrame
框架自建了非常强大的路由功能,提供了比任何同类框架更加出色的路由特性,支持流行的命名匹配规则、模糊匹配规则及字段匹配规则,并提供了优秀的优先级管理机制。
一个示例
在真正开启本章的核心内容之前,我们先来看一个简单的动态路由使用示例:
package main import ( "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/frame/g" ) func main() { s := g.Server() s.BindHandler("/:name", func(r *ghttp.Request){ r.Response.Writeln(r.Router.Uri) }) s.BindHandler("/:name/update", func(r *ghttp.Request){ r.Response.Writeln(r.Router.Uri) }) s.BindHandler("/:name/:action", func(r *ghttp.Request){ r.Response.Writeln(r.Router.Uri) }) s.BindHandler("/:name/*any", func(r *ghttp.Request){ r.Response.Writeln(r.Router.Uri) }) s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){ r.Response.Writeln(r.Router.Uri) }) s.SetPort(8199) s.Run() }
以上示例中展示了goframe
框架支持的三种模糊匹配路由规则,:name
、*any
、{field}
分别表示命名匹配规则、模糊匹配规则及字段匹配规则。不同的规则中使用/
符号来划分层级,路由检索采用深度优先算法,层级越深的规则优先级也会越高。我们运行以上示例,通过访问几个URL
来看看效果:
URL 结果 http://127.0.0.1:8199/user/list/2.html /user/list/{field}.html http://127.0.0.1:8199/user/update /:name/update http://127.0.0.1:8199/user/info /:name/:action http://127.0.0.1:8199/user /:name/*any
在这个示例中我们也可以看到,由于优先级的限制,路由规则/:name
会被/:name/*any
规则覆盖,将会无法被匹配到,所以在分配路由规则的时候,需要进行统一规划和管理,避免类似情况的产生。
注册规则
路由注册参数
最基础的路由绑定方法是BindHandler
方法,我们来看一下之前一直使用的BindHandler
的原型:
func (s *Server) BindHandler(pattern string, handler interface{})
pattern
参数
其中的pattern
为路由注册规则字符串,在其他路由注册方法中也会使用到,参数格式如下:
[HTTPMethod:]路由规则[@域名]
其中HTTPMethod
(GET/PUT/POST/DELETE/PATCH/HEAD/CONNECT/OPTIONS/TRACE
)和@域名
为非必需参数,大部分场景下直接给定路由规则参数即可,BindHandler
会自动绑定所有的请求方式,如果给定HTTPMethod
,那么路由规则仅会在该请求方式下有效。@域名
可以指定生效的域名名称,那么该路由规则仅会在该域名下生效。
BindHandler
是最原生的路由注册方法,在大部分场景中,我们通常使用 分组路由 方式来管理理由,后续章节将会介绍:路由注册-分组路由。
handler
参数
其中的handler
参数通常用于指定路由函数,我们最基础的示例都是使用函数来注册路由,一个路由函数需要满足以下定义,即只要能接收请求对象ghttp.Request
即可:
func(r *ghttp.Request) { // ... }
我们来看一个例子:
package main import ( "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/frame/g" ) func main() { s := g.Server() // 该路由规则仅会在GET请求下有效 s.BindHandler("GET:/{table}/list/{page}.html", func(r *ghttp.Request){ r.Response.WriteJson(r.Router) }) // 该路由规则仅会在GET请求及localhost域名下有效 s.BindHandler("GET:/order/info/{order_id}@localhost", func(r *ghttp.Request){ r.Response.WriteJson(r.Router) }) // 该路由规则仅会在DELETE请求下有效 s.BindHandler("DELETE:/comment/{id}", func(r *ghttp.Request){ r.Response.WriteJson(r.Router) }) s.SetPort(8199) s.Run() }
其中返回的参数r.Router
是当前匹配的路由规则信息,访问当该方法的时候,服务端会输出当前匹配的路由规则信息。执行后,我们在终端使用curl
命令进行测试:
$ curl -XGET http://127.0.0.1:8199/order/list/1.html {"Domain":"default","Method":"GET","Priority":3,"Uri":"/{table}/list/{page}.html"} $ curl -XGET http://127.0.0.1:8199/order/info/1 Not Found $ curl -XGET http://localhost:8199/order/info/1 {"Domain":"localhost","Method":"GET","Priority":3,"Uri":"/order/info/{order_id}"} $ curl -XDELETE http://127.0.0.1:8199/comment/1000 {"Domain":"default","Method":"DELETE","Priority":2,"Uri":"/comment/{id}"} $ curl -XGET http://127.0.0.1:8199/comment/1000 Not Found
精准匹配规则
精准匹配规则即未使用任何动态规则的规则,如:user
、order
、info
等等这种确定名称的规则。在大多数场景下,精准匹配规则会和动态规则一起使用来进行路由注册(例如:/:name/list
,其中层级1:name
为命名匹配规则,层级2list
是精准匹配规则)。
动态路由规则
动态路由规则分为三种:命名匹配规则、模糊匹配规则和字段匹配规则。动态路由的底层数据结构是由层级哈希表
和双向链表
构建的路由树
,层级哈希表便于高效率地层级匹配URI
;数据链表用于优先级控制,同一层级的路由规则按照优先级进行排序,优先级高的规则排在链表头。底层的路由规则与请求URI
的匹配计算采用的是正则表达式,并充分使用了缓存机制,执行效率十分高效。
所有匹配到的参数都将会以Router
参数的形式传递给业务层,可以通过ghttp.Request
对象的以下方法获取匹配到的路由参数:
func (r *Request) GetRouter(key string, def ...interface{}) *gvar.Var
也可以使用ghttp.Request.Get
方法获取匹配到的路由参数。
命名匹配规则
使用:name
方式进行匹配(name
为自定义的匹配名称),对URI
指定层级的参数进行命名匹配(类似正则([^/]+)
,该URI
层级必须有值),对应匹配参数会被解析为Router
参数并传递给注册的服务接口使用。
匹配示例1:
rule: /user/:user /user/john match /user/you match /user/john/profile no match /user/ no match
匹配示例2:
rule: /:name/action /john/name no match /john/action match /smith/info no match /smith/info/age no match /smith/action match
匹配示例3:
rule: /:name/:action /john/name match /john/info match /smith/info match /smith/info/age no match /smith/action/del no match
模糊匹配规则
使用*any
方式进行匹配(any
为自定义的匹配名称),对URI
指定位置之后的参数进行模糊匹配(类似正则(.*)
,该URI
层级可以为空),并将匹配参数解析为Router
参数并传递给注册的服务接口使用。
匹配示例1:
rule: /src/*path /src/ match /src/somefile.go match /src/subdir/somefile.go match /user/ no match /user/john no match
匹配示例2:
rule: /src/*path/:action /src/ no match /src/somefile.go match /src/somefile.go/del match /src/subdir/file.go/del match
匹配示例3:
rule: /src/*path/show /src/ no match /src/somefile.go no match /src/somefile.go/del no match /src/somefile.go/show match /src/subdir/file.go/show match /src/show match
字段匹配规则
使用{field}
方式进行匹配(field
为自定义的匹配名称),可对URI
任意位置的参数进行截取匹配(类似正则([\w\.\-]+)
,该URI
层级必须有值,并且可以在同一层级进行多个字段匹配),并将匹配参数解析为Router
参数并传递给注册的服务接口使用。
匹配示例1:
rule: /order/list/{page}.php /order/list/1.php match /order/list/666.php match /order/list/2.php5 no match /order/list/1 no match /order/list no match
匹配示例2:
rule: /db-{table}/{id} /db-user/1 match /db-user/2 match /db/user/1 no match /db-order/100 match /database-order/100 no match
匹配示例3:
rule: /{obj}-{act}/*param /user-delete/10 match /order-update/20 match /log-list match /log/list/1 no match /comment/delete/10 no match
动态路由示例
package main import ( "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/frame/g" ) func main() { s := g.Server() // 一个简单的分页路由示例 s.BindHandler("/user/list/{page}.html", func(r *ghttp.Request){ r.Response.Writeln(r.Get("page")) }) // {xxx} 规则与 :xxx 规则混合使用 s.BindHandler("/{object}/:attr/{act}.php", func(r *ghttp.Request){ r.Response.Writeln(r.Get("object")) r.Response.Writeln(r.Get("attr")) r.Response.Writeln(r.Get("act")) }) // 多种模糊匹配规则混合使用 s.BindHandler("/{class}-{course}/:name/*act", func(r *ghttp.Request){ r.Response.Writeln(r.Get("class")) r.Response.Writeln(r.Get("course")) r.Response.Writeln(r.Get("name")) r.Response.Writeln(r.Get("act")) }) s.SetPort(8199) s.Run() }
执行后,我们可以通过curl
命令或者浏览器访问的方式进行测试,以下为测试结果:
$ curl -XGET http://127.0.0.1:8199/user/list/1.html 1 $ curl -XGET http://127.0.0.1:8199/user/info/save.php user info save $ curl -XGET http://127.0.0.1:8199/class3-math/john/score class3 math john score
优先级控制
优先级控制按照深度优先策略,简要计算策略:
- 层级越深的规则优先级越高;
- 同一层级下,精准匹配优先级高于模糊匹配;
- 同一层级下,模糊匹配优先级:字段匹配 > 命名匹配 > 模糊匹配;
我们来看示例(左边的规则优先级比右边高):
/:name > /*any /user/name > /user/:action /:name/info > /:name/:action /:name/:action > /:name/*action /:name/{action} > /:name/:action /src/path/del > /src/path /src/path/del > /src/path/:action /src/path/*any > /src/path
- No labels
15 Comments
白夜
看起来命名匹配与字段匹配的区别就是:命名匹配([\w\.\-]+)是字段匹配([^/]+)的子集。
abchen
[HTTPMethod:]路由规则[@域名]
这个地方为什么不在抽象出对应的GET/POST/[HTTPMETHOD]的方法?
白夜
路由注册参数
我们来看一下之前一直使用的
BindHandler
的原型:该方法是路由注册的最基础方法,其中的
pattern
为路由注册规则字符串,在其他路由注册方法中也会使用到,参数格式如下:其中
HTTPMethod
(支持的Method:GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE
)和@域名
为非必需参数,一般来说直接给定路由规则参数即可,BindHandler
会自动绑定所有的请求方式,如果给定HTTPMethod
,那么路由规则仅会在该请求方式下有效支持的Method:
GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE
支持的Method:
GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE
支持的Method:
GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE
重要的事情说三遍。
大兄弟,认真看书好不啦
张金富
放在了group里
quede
可以给路由命名么?
或者有没有 权限控制发案例
yidashi
路由太弱了,不能自定义正则匹配吗
json
为什么不在抽象出对应的GET/POST/[HTTPMETHOD]的方法,例如r.Get("/user/list"), r.Any("/user"), httpmethod跟路由规则一个字符串拼接,太弱了
张金富
group 有这些方法
牛嘿
希望路由规则支持自定义正则语句。
yidashi
发现一个路由包比较强大,支持正则匹配,实在不行用这个吧 go-chi/chi: lightweight, idiomatic and composable router for building Go HTTP services (github.com)
吴大山
goframe没找到接入prometheus的例子,路由怎么接可以说下吗?小白不太懂。
邱一二
我也找不到,同求大佬给个例子
邱一二
Before80
“BindHandler
是最原生的路由注册方法,在大部分场景中,我们通常使用 分组路由 方式来管理理由,后续章节将会介绍:路由注册-分组路由。” 其中 “管理理由” 应该修改成 “管理路由”。叮当猫
感觉很多东西用不上啊。