起步
上一小节写到了 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)
值为 true。 typFn.NumIn()
表示形参个数,typFn.In(0).Kind()
获取第一个参数的类型。func(paramValue int) bool { ... } 只有一个形参,所以 typFn.NumIn() == 1
为 true,但它的形参为 int 型,因而 typFn.In(0).Kind() == reflect.String
为 false,所以 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(我习惯叫做位置参数)
还不快抢沙发