GoFrame 路由管理-路由規(guī)則

2022-04-13 10:13 更新

?goframe?框架自建了非常強大的路由功能,提供了比任何同類框架更加出色的路由特性,支持流行的命名匹配規(guī)則、模糊匹配規(guī)則及字段匹配規(guī)則,并提供了優(yōu)秀的優(yōu)先級管理機制。

一個示例

在真正開啟本章的核心內容之前,我們先來看一個簡單的動態(tài)路由使用示例:

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?框架支持的三種模糊匹配路由規(guī)則,?:name?、?*any?、?{field}?分別表示命名匹配規(guī)則、模糊匹配規(guī)則及字段匹配規(guī)則。不同的規(guī)則中使用?/?符號來劃分層級,路由檢索采用深度優(yōu)先算法,層級越深的規(guī)則優(yōu)先級也會越高。我們運行以上示例,通過訪問幾個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

在這個示例中我們也可以看到,由于優(yōu)先級的限制,路由規(guī)則?/:name?會被?/:name/*any?規(guī)則覆蓋,將會無法被匹配到,所以在分配路由規(guī)則的時候,需要進行統(tǒng)一規(guī)劃和管理,避免類似情況的產生。

注冊規(guī)則

路由注冊參數

我們來看一下之前一直使用的?BindHandler?的原型:

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

該方法是路由注冊的最基礎方法,其中的?pattern?為路由注冊規(guī)則字符串,在其他路由注冊方法中也會使用到,參數格式如下:

[HTTPMethod:]路由規(guī)則[@域名]

其中?HTTPMethod?(支持的Method:?GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE?)和?@域名?為非必需參數,一般來說直接給定路由規(guī)則參數即可,?BindHandler?會自動綁定所有的請求方式,如果給定?HTTPMethod?,那么路由規(guī)則僅會在該請求方式下有效。?@域名?可以指定生效的域名名稱,那么該路由規(guī)則僅會在該域名下生效。

?BindHandler?是最原生的路由注冊方法,在大部分場景中,我們通常使用分組路由方式來管理理由。

我們來看一個例子:

package main

import (
    "github.com/gogf/gf/v2/net/ghttp"
    "github.com/gogf/gf/v2/frame/g"
)

func main() {
    s := g.Server()
    // 該路由規(guī)則僅會在GET請求下有效
    s.BindHandler("GET:/{table}/list/{page}.html", func(r *ghttp.Request){
        r.Response.WriteJson(r.Router)
    })
    // 該路由規(guī)則僅會在GET請求及l(fā)ocalhost域名下有效
    s.BindHandler("GET:/order/info/{order_id}@localhost", func(r *ghttp.Request){
        r.Response.WriteJson(r.Router)
    })
    // 該路由規(guī)則僅會在DELETE請求下有效
    s.BindHandler("DELETE:/comment/{id}", func(r *ghttp.Request){
        r.Response.WriteJson(r.Router)
    })
    s.SetPort(8199)
    s.Run()
}

其中返回的參數?r.Router?是當前匹配的路由規(guī)則信息,訪問當該方法的時候,服務端會輸出當前匹配的路由規(guī)則信息。執(zhí)行后,我們在終端使用?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

值得說明的是,在大多數場景下,我們很少直接在路由規(guī)則中使用?@域名?這樣的規(guī)則來限定路由注冊的域名,而是使用?ghttp.Server.Domain(domains string)?方法來獲得指定域名列表的管理對象,隨后使用該域名對象進行路由注冊,域名對象即可實現對指定域名的綁定操作。

精準匹配規(guī)則

精準匹配規(guī)則即未使用任何動態(tài)規(guī)則的規(guī)則,如:?user?、?order?、?info?等等這種確定名稱的規(guī)則。在大多數場景下,精準匹配規(guī)則會和動態(tài)規(guī)則一起使用來進行路由注冊(例如:?/:name/list?,其中層級1?:name?為命名匹配規(guī)則,層級2?list?是精準匹配規(guī)則)。

動態(tài)路由規(guī)則

動態(tài)路由規(guī)則分為三種:命名匹配規(guī)則、模糊匹配規(guī)則和字段匹配規(guī)則。動態(tài)路由的底層數據結構是由層級哈希表和雙向鏈表構建的路由樹,層級哈希表便于高效率地層級匹配?URI?;數據鏈表用于優(yōu)先級控制,同一層級的路由規(guī)則按照優(yōu)先級進行排序,優(yōu)先級高的規(guī)則排在鏈表頭。底層的路由規(guī)則與請求?URI?的匹配計算采用的是正則表達式,并充分使用了緩存機制,執(zhí)行效率十分高效。

所有匹配到的參數都將會以?Router?參數的形式傳遞給業(yè)務層,可以通過?ghttp.Request?對象的以下方法獲?。?/p>

func (r *Request) GetRouterValue(key string, def ...interface{}) interface{}
func (r *Request) GetRouterVar(key string, def ...interface{}) *gvar.Var
func (r *Request) GetRouterString(key string, def ...interface{}) string

也可以使用?ghttp.Request.Get*?方式進行獲取。

命名匹配規(guī)則

使用?: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

模糊匹配規(guī)則

使用?*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

字段匹配規(guī)則

使用?{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

動態(tài)路由示例

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} 規(guī)則與 :xxx 規(guī)則混合使用
    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"))
    })
    // 多種模糊匹配規(guī)則混合使用
    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()
}

執(zhí)行后,我們可以通過?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

優(yōu)先級控制

優(yōu)先級控制按照深度優(yōu)先策略,主要的幾點因素:

  1. 層級越深的規(guī)則優(yōu)先級越高;
  2. 同一層級下,精準匹配優(yōu)先級高于模糊匹配;
  3. 同一層級下,模糊匹配優(yōu)先級:字段匹配 > 命名匹配 > 模糊匹配;

我們來看示例(左邊的規(guī)則優(yōu)先級比右邊高):

/: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


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號