GoFrame 框架自建了非常强大的路由功能,提供了比任何同类框架更加出色的路由特性,支持流行的命名匹配规则、模糊匹配规则及字段匹配规则,并提供了优秀的优先级管理机制。

一个示例

在真正开启本章的核心内容之前,我们先来看一个简单的动态路由使用示例:

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/net/ghttp"
  4. "github.com/gogf/gf/v2/frame/g"
  5. )
  6. func main() {
  7. s := g.Server()
  8. s.BindHandler("/:name", func(r *ghttp.Request){
  9. r.Response.Writeln(r.Router.Uri)
  10. })
  11. s.BindHandler("/:name/update", func(r *ghttp.Request){
  12. r.Response.Writeln(r.Router.Uri)
  13. })
  14. s.BindHandler("/:name/:action", func(r *ghttp.Request){
  15. r.Response.Writeln(r.Router.Uri)
  16. })
  17. s.BindHandler("/:name/*any", func(r *ghttp.Request){
  18. r.Response.Writeln(r.Router.Uri)
  19. })
  20. s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){
  21. r.Response.Writeln(r.Router.Uri)
  22. })
  23. s.SetPort(8199)
  24. s.Run()
  25. }

以上示例中展示了 goframe 框架支持的三种模糊匹配路由规则, :name*any{field} 分别表示 命名匹配规则模糊匹配规则字段匹配规则。不同的规则中使用 / 符号来划分层级,路由检索采用 深度优先算法,层级越深的规则优先级也会越高。我们运行以上示例,通过访问几个 URL 来看看效果:

  1. URL 结果
  2. http://127.0.0.1:8199/user/list/2.html /user/list/{field}.html
  3. http://127.0.0.1:8199/user/update /:name/update
  4. http://127.0.0.1:8199/user/info /:name/:action
  5. http://127.0.0.1:8199/user /:name/*any

在这个示例中我们也可以看到,由于优先级的限制,路由规则 /:name 会被 /:name/*any 规则覆盖,将会无法被匹配到,所以在分配路由规则的时候,需要进行统一规划和管理,避免类似情况的产生。

注册规则

路由注册参数

最基础的 路由绑定方法是 BindHandler 方法,我们来看一下之前一直使用的 BindHandler 的原型:

  1. func (s *Server) BindHandler(pattern string, handler interface{})

pattern 参数

其中的 pattern 为路由注册规则字符串,在其他路由注册方法中也会使用到,参数格式如下:

  1. [HTTPMethod:]路由规则[@域名]

其中 HTTPMethodGET/PUT/POST/DELETE/PATCH/HEAD/CONNECT/OPTIONS/TRACE)和 @域名非必需参数,大部分场景下直接给定路由规则参数即可, BindHandler 会自动绑定 所有的 请求方式,如果给定 HTTPMethod,那么路由规则仅会在该请求方式下有效。 @域名 可以指定生效的域名名称,那么该路由规则仅会在该域名下生效。

路由管理-路由规则 - 图1提示

BindHandler 是最原生的路由注册方法,在大部分场景中,我们通常使用 分组路由 方式来管理理由,后续章节将会介绍: 路由注册-分组路由

handler 参数

其中的 handler 参数通常用于指定路由函数,我们最基础的示例都是使用函数来注册路由,一个路由函数需要满足以下定义,即只要能接收请求对象 ghttp.Request 即可:

  1. func(r *ghttp.Request) {
  2. // ...
  3. }

我们来看一个例子:

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/net/ghttp"
  4. "github.com/gogf/gf/v2/frame/g"
  5. )
  6. func main() {
  7. s := g.Server()
  8. // 该路由规则仅会在GET请求下有效
  9. s.BindHandler("GET:/{table}/list/{page}.html", func(r *ghttp.Request){
  10. r.Response.WriteJson(r.Router)
  11. })
  12. // 该路由规则仅会在GET请求及localhost域名下有效
  13. s.BindHandler("GET:/order/info/{order_id}@localhost", func(r *ghttp.Request){
  14. r.Response.WriteJson(r.Router)
  15. })
  16. // 该路由规则仅会在DELETE请求下有效
  17. s.BindHandler("DELETE:/comment/{id}", func(r *ghttp.Request){
  18. r.Response.WriteJson(r.Router)
  19. })
  20. s.SetPort(8199)
  21. s.Run()
  22. }

其中返回的参数 r.Router 是当前匹配的路由规则信息,访问当该方法的时候,服务端会输出当前匹配的路由规则信息。执行后,我们在终端使用 curl 命令进行测试:

  1. $ curl -XGET http://127.0.0.1:8199/order/list/1.html
  2. {"Domain":"default","Method":"GET","Priority":3,"Uri":"/{table}/list/{page}.html"}
  3. $ curl -XGET http://127.0.0.1:8199/order/info/1
  4. Not Found
  5. $ curl -XGET http://localhost:8199/order/info/1
  6. {"Domain":"localhost","Method":"GET","Priority":3,"Uri":"/order/info/{order_id}"}
  7. $ curl -XDELETE http://127.0.0.1:8199/comment/1000
  8. {"Domain":"default","Method":"DELETE","Priority":2,"Uri":"/comment/{id}"}
  9. $ curl -XGET http://127.0.0.1:8199/comment/1000
  10. Not Found

精准匹配规则

精准匹配规则即 未使用任何动态规则 的规则,如: userorderinfo 等等这种 确定名称 的规则。在大多数场景下,精准匹配规则会和动态规则一起使用来进行路由注册(例如: /:name/list,其中层级1 :name 为命名匹配规则,层级2 list 是精准匹配规则)。

动态路由规则

动态路由规则分为三种: 命名匹配规则模糊匹配规则字段匹配规则。动态路由的底层数据结构是由层级 哈希表双向链表 构建的 路由树,层级哈希表便于高效率地层级匹配 URI;数据链表用于优先级控制,同一层级的路由规则按照优先级进行排序,优先级高的规则排在链表头。底层的路由规则与请求 URI 的匹配计算采用的是正则表达式,并充分使用了缓存机制,执行效率十分高效。

所有匹配到的参数都将会以 Router 参数的形式传递给业务层,可以通过 ghttp.Request 对象的以下方法获取匹配到的路由参数:

  1. func (r *Request) GetRouter(key string, def ...interface{}) *gvar.Var

也可以使用 ghttp.Request.Get 方法获取匹配到的路由参数。

命名匹配规则

使用 :name 方式进行匹配( name 为自定义的匹配名称),对 URI 指定层级的参数进行命名匹配(类似正则 ([^/]+),该 URI 层级必须有值),对应匹配参数会被解析为 Router 参数并传递给注册的服务接口使用。

匹配示例1:

  1. rule: /user/:user
  2. /user/john match
  3. /user/you match
  4. /user/john/profile no match
  5. /user/ no match

匹配示例2:

  1. rule: /:name/action
  2. /john/name no match
  3. /john/action match
  4. /smith/info no match
  5. /smith/info/age no match
  6. /smith/action match

匹配示例3:

  1. rule: /:name/:action
  2. /john/name match
  3. /john/info match
  4. /smith/info match
  5. /smith/info/age no match
  6. /smith/action/del no match

模糊匹配规则

使用 *any 方式进行匹配( any 为自定义的匹配名称),对 URI 指定位置之后的参数进行模糊匹配(类似正则 (.*),该 URI 层级可以为空),并将匹配参数解析为 Router 参数并传递给注册的服务接口使用。

匹配示例1:

  1. rule: /src/*path
  2. /src/ match
  3. /src/somefile.go match
  4. /src/subdir/somefile.go match
  5. /user/ no match
  6. /user/john no match

匹配示例2:

  1. rule: /src/*path/:action
  2. /src/ no match
  3. /src/somefile.go match
  4. /src/somefile.go/del match
  5. /src/subdir/file.go/del match

匹配示例3:

  1. rule: /src/*path/show
  2. /src/ no match
  3. /src/somefile.go no match
  4. /src/somefile.go/del no match
  5. /src/somefile.go/show match
  6. /src/subdir/file.go/show match
  7. /src/show match

字段匹配规则

使用 {field} 方式进行匹配( field 为自定义的匹配名称),可对 URI 任意位置 的参数进行截取匹配(类似正则 ([\w\.\-]+),该 URI 层级必须有值,并且可以在同一层级进行多个字段匹配),并将匹配参数解析为 Router 参数并传递给注册的服务接口使用。

匹配示例1:

  1. rule: /order/list/{page}.php
  2. /order/list/1.php match
  3. /order/list/666.php match
  4. /order/list/2.php5 no match
  5. /order/list/1 no match
  6. /order/list no match

匹配示例2:

  1. rule: /db-{table}/{id}
  2. /db-user/1 match
  3. /db-user/2 match
  4. /db/user/1 no match
  5. /db-order/100 match
  6. /database-order/100 no match

匹配示例3:

  1. rule: /{obj}-{act}/*param
  2. /user-delete/10 match
  3. /order-update/20 match
  4. /log-list match
  5. /log/list/1 no match
  6. /comment/delete/10 no match

动态路由示例

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/net/ghttp"
  4. "github.com/gogf/gf/v2/frame/g"
  5. )
  6. func main() {
  7. s := g.Server()
  8. // 一个简单的分页路由示例
  9. s.BindHandler("/user/list/{page}.html", func(r *ghttp.Request){
  10. r.Response.Writeln(r.Get("page"))
  11. })
  12. // {xxx} 规则与 :xxx 规则混合使用
  13. s.BindHandler("/{object}/:attr/{act}.php", func(r *ghttp.Request){
  14. r.Response.Writeln(r.Get("object"))
  15. r.Response.Writeln(r.Get("attr"))
  16. r.Response.Writeln(r.Get("act"))
  17. })
  18. // 多种模糊匹配规则混合使用
  19. s.BindHandler("/{class}-{course}/:name/*act", func(r *ghttp.Request){
  20. r.Response.Writeln(r.Get("class"))
  21. r.Response.Writeln(r.Get("course"))
  22. r.Response.Writeln(r.Get("name"))
  23. r.Response.Writeln(r.Get("act"))
  24. })
  25. s.SetPort(8199)
  26. s.Run()
  27. }

执行后,我们可以通过 curl 命令或者浏览器访问的方式进行测试,以下为测试结果:

  1. $ curl -XGET http://127.0.0.1:8199/user/list/1.html
  2. 1
  3. $ curl -XGET http://127.0.0.1:8199/user/info/save.php
  4. user
  5. info
  6. save
  7. $ curl -XGET http://127.0.0.1:8199/class3-math/john/score
  8. class3
  9. math
  10. john
  11. score

优先级控制

优先级控制按照 深度优先策略,简要计算策略:

  1. 层级越深的规则优先级越高
  2. 同一层级下,精准匹配优先级高于模糊匹配
  3. 同一层级下,模糊匹配优先级:字段匹配 > 命名匹配 > 模糊匹配

我们来看示例(左边的规则优先级比右边高):

  1. /:name > /*any
  2. /user/name > /user/:action
  3. /:name/info > /:name/:action
  4. /:name/:action > /:name/*action
  5. /:name/{action} > /:name/:action
  6. /src/path/del > /src/path
  7. /src/path/del > /src/path/:action
  8. /src/path/*any > /src/path