Iris - 路由(2)

Iris 2020-01-02 5281 字 1365 浏览 点赞

起步

上一小节写到了 Iris 路由注册,默认行为,离线路由,分组路由,以及路由参数。而路由参数仅仅简单提及到了,由它引发的更多常用知识下面接着说。

URL 参数类型

形如:

app.Get("/{name:string}", oneHandler)

表示要为 oneHandler 绑定一个路由,这个路由的 URL 有一部分是变量,变量名是 name。,这个变量 name 的类型是 string 类型。现在不论 URL 是 http://$your_host/user 还是 http://$your_host/name 都能被匹配到。在 handler 函数中,取 name 对应值的方式是 ctx.Get() 或者 ctx.GetString()。Iris 支持更多类型可以点击 Routing path parameter types 进行查看。

不同于市面上其他常见的 web 框架,Iris 可以灵活处理路由,保证路由之间不会有冲突发生。譬如下面两个路由:

app.Get("/{path:path}", func(ctx iris.Context) {
    ...
})

app.Get("/{path:string}", func(ctx iris.Context) {
    ...
})

当它们单独出现在一个 web 服务器中时,都能匹配到 url:$your_host/user 。而它们同时出现在一个 web 服务器时,访问 $your_host/user 会优先匹配到 path:string,而不是报路由冲突。Iris 在这方面的处理上像是“找近亲”,/user 既可以是 path 类型也可以是 string,它离 string 更近一些,于是匹配 string 对应的 route。同理,如果有 app.Get("/{path:int}", ...),那么访问 $your_host/1 将匹配 int 绑定的 route,而不是 string 绑定的 route。

当 param 为 string 类型时,:string 可以省略。{name} 等同于 {name:string}

URL 验证器

内嵌函数

为了更精细的控制路由,内嵌函数必不可少。譬如我们我们知道用户 id 一定是大于 1000,那么注册路由时可以这样写:

// 表示 id 的值最小为 1000,否则 404
app.Get("/user/{id:int min(1000)}", ...)

出双入对的是 max,意为最大值。说明:倘若被修饰的参数是 string 类型,min 和 max 将限制字符串的长度。range 限制数值范围,用法 range(minValue, maxValue)(左闭右闭),不支持 param 为字符串类型

其他内置函数仅支持 param 为 string 类型:

  • regexp(expr string) 正则
  • prefix(prefix string) 前缀
  • suffix(suffix string) 后缀
  • contains(s string) 包含

Regexp 实例:

// 访问 http://127.0.0.1:8080/expr/1919-11-12 
app.Get("/expr/{name:string regexp(\\d{4}-\\d{2}-\\d{2})}", 
func(ctx iris.Context) { ... })

自定义验证器

显然,iris 提供的内置函数还是偏少,但框架允许我们自定义验证器。仍拿日期格式的例子,我们希望用以下方式注册路由:

app.Get("/date/{date:string date()}", func(ctx iris.Context) { ... })
// 允许 http://127.0.0.1:8080/date/1998-12-01 访问成功

实现方式如下:

// 正则,制定校验标准
dateExpr := `\d{4}-\d{2}-\d{2}`  
// 为 :string 注册 date ,使用方式 date() 
app.Macros.Get("string").RegisterFunc("date", dateExpr.MatchString)
...

如果需要对验证器传参呢?比如,如何自己实现一个 range?完整的代码应该如下:

// 为 :int 注册验证器
app.Macros().Get("int").RegisterFunc("myRange",
    // 接收允许数值范围。minValue 最小;maxValue 最大。
    func(minValue, maxValue int) func(int) bool {
        // 接收实际的 param value
        return func(paramValue int) bool {
            // 根据限制要求,返回 true or false
            return paramValue >= minValue && paramValue <= maxValue
        }
    })
// 注册路由, param 范围限制在 10-100
app.Get("/num/{n:int myRange(10, 100)}", func(ctx iris.Context) {
    n, _ := ctx.Params().GetInt("n")
    ctx.HTML(fmt.Sprintln(n))
})

注意事项

这里简单提一下为 :string:int 绑定检验器的小差别。

当自定义的验证器不需要传参时,对 string 注册函数可以是:

app.Macros().Get("string").RegisterFunc("fnName", func(paramValue string) bool {...})

但对 int 注册必须是函数嵌套函数:

app.Macros().Get("int").RegisterFunc("fnName", func() func(int) bool {
    return func(paramValue int) { ... }
})

也就是说,如果你的代码形式如下,对不起,不会起任何作用

/* 目标期望:当 param > 10 的时候,才能得到正确响应 */

app.Macros().Get("int").RegisterFunc("moreThanTen", func(paramValue int) bool {
    return paramValue > 10
})
app.Get("/num/{n:int moreThanTen()}", func(ctx iris.Context) {
    n, _ := ctx.Params().GetInt("n")
    ctx.HTML(fmt.Sprintln(n))
})

为什么会这样呢?就不得不看看 RegisterFunc 方法的源码了。

// RegisterFunc 源码
func (m *Macro) RegisterFunc(funcName string, fn interface{}) *Macro {
    fullFn := convertBuilderFunc(fn)

    // if it's not valid then not register it at all.
    if fullFn != nil {
        m.registerFunc(funcName, fullFn)
    }

    return m
}

可以看到 RegisterFunc 会把第二个参数交给 convertBuilderFunc 处理,只有返回不为 nil 时,才会执行真正的注册逻辑(m.registerFunc(funcName, fullFn))。现在我们跳进 convertBuilderFunc 寻找端倪。

// convertBuilderFunc 源码。 省略了部分源码。
func convertBuilderFunc(fn interface{}) ParamFuncBuilder {
    typFn := reflect.TypeOf(fn)
    if !goodParamFunc(typFn) {
        if typFn.NumIn() == 1 && typFn.In(0).Kind() == reflect.String && typFn.NumOut() == 1 && typFn.Out(0).Kind() == reflect.Bool {
            ...
        }
        return nil
    }
    ...

事实上,当参数 func(paramValue int) bool { ... } 传进 convertBuilderFunc 处理后,!goodParamFunc(typFn) 值为 truetypFn.NumIn() 表示形参个数,typFn.In(0).Kind() 获取第一个参数的类型。func(paramValue int) bool { ... } 只有一个形参,所以 typFn.NumIn() == 1true,但它的形参为 int 型,因而 typFn.In(0).Kind() == reflect.Stringfalse,所以 convertBuilderFunc 返回 nil。验证器注册失败。

URL 逆向查找

iris 允许给路由命名:

User := app.Get("/user/{name}", func(ctx iris.Context) { ... })
User.Name = "user"

之后就可以通过名字找到对应的路由对象:

routeObj := app.GetRoute("user")
// 另:app.GetRoutes()  返回所有注册路由

在某些情况下,我们需要在代码里调用项目中的 url,手动拼接字符串并不优雅,借助 URL() 就好啦。

// import "github.com/kataras/iris/v12/core/router"

r := router.NewRoutePathReverser(app, router.WithHost("127.0.0.0"))
url := r.URL("user", "zty")
fmt.Println(url)
// http://127.0.0.1/user/zty
  • URL 方法定义:func (ps *RoutePathReverser) URL(routeName string, paramValues ...interface{}) (url string)
  • 第一个参数是路由的名字
  • 后面的参数是 url 中的 params(我习惯叫做位置参数)

参考



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论