路由模块
几点说明:
- 路由匹配的顺序是按照他们被定义的顺序执行的,
- …但是,匹配范围较小的路由优先级比匹配范围大的优先级高(详见 匹配优先级)。
- 最先被定义的路由将会首先被用户请求匹配并调用。
在一些时候,每当 GET 方法被注册的时候,都会需要注册一个一模一样的 HEAD 方法。为了达到减少代码的目的,您可以使用一个名为 的方法来协助您自动注册:
m := New()
m.SetAutoHead(true)
m.Get("/", func() string {
return "GET"
}) // 路径 "/" 的 HEAD 也已经被自动注册
如果您想要使用子路径但让路由代码保持简洁,可以调用 m.SetURLPrefix(suburl)
。
路由模型可能包含参数列表, 可以通过 来获取:
使用一个特定的名称来代表路由的某个部分:
m.Get("/hello/:name", func(ctx *macaron.Context) string {
return "Hello " + ctx.Params(":name")
})
m.Get("/date/:year/:month/:day", func(ctx *macaron.Context) string {
return fmt.Sprintf("Date: %s/%s/%s", ctx.Params(":year"), ctx.Params(":month"), ctx.Params(":day"))
})
当然,想要偷懒的时候可以将 :
前缀去掉:
m.Get("/hello/:name", func(ctx *macaron.Context) string {
return "Hello " + ctx.Params("name")
})
m.Get("/date/:year/:month/:day", func(ctx *macaron.Context) string {
return fmt.Sprintf("Date: %s/%s/%s", ctx.Params("year"), ctx.Params("month"), ctx.Params("day"))
})
路由匹配可以通过全局匹配的形式:
m.Get("/hello/*", func(ctx *macaron.Context) string {
return "Hello " + ctx.Params("*")
})
您还可以使用正则表达式来书写路由规则:
常规匹配:
m.Get("/user/:username([\\w]+)", func(ctx *macaron.Context) string {
return fmt.Sprintf("Hello %s", ctx.Params(":username"))
})
m.Get("/user/:id([0-9]+)", func(ctx *macaron.Context) string {
return fmt.Sprintf("User ID: %s", ctx.Params(":id"))
})
m.Get("/user/*.*", func(ctx *macaron.Context) string {
return fmt.Sprintf("Last part is: %s, Ext: %s", ctx.Params(":path"), ctx.Params(":ext"))
})
混合匹配:
m.Get("/cms_:id([0-9]+).html", func(ctx *macaron.Context) string {
return fmt.Sprintf("The ID is %s", ctx.Params(":id"))
})
可选匹配:
/user/?:id
可同时匹配/user/
和/user/123
。
- 简写:
/user/:id:int
::int
是([0-9]+)
正则的简写。
以下为从高到低的不同模式的匹配优先级:
- 静态路由:
/
/home
- 正则表达式路由:
/(.+).html
- 路径-后缀路由:
/*.*
- 占位符路由:
/:id
/:name
- 全局匹配路由:
/*
其它说明:
- 相同模式的匹配优先级是根据添加的先后顺序决定的。
- 层级相对明确的模式匹配优先级要高于相对模糊的模式:
/*/*/events
>/*
您可以通过 *Route.Name
方法配合命名参数来构建 URL 路径,不过首先需要为路由命名:
// ...
m.Get("/users/:id([0-9]+)/:name:string.profile", handler).Name("user_profile")
m.Combo("/api/:user/:repo").Get(handler).Post(handler).Name("user_repo")
// ...
// ...
func handler(ctx *macaron.Context) {
// /users/12/unknwon.profile
userProfile := ctx.URLFor("user_profile", ":id", "12", ":name", "unknwon")
// /api/unknwon/macaron
userRepo := ctx.URLFor("user_repo", ":user", "unknwon", ":repo", "macaron")
}
// ...
配合 Go 模板引擎使用
配合 Pongo2 模板引擎使用
// ...
ctx.Data["URLFor"] = ctx.URLFor
ctx.HTML(200, "home")
// ...
路由处理器可以被相互叠加使用, 例如很有用的地方可以是在验证和授权的时候:
m.Get("/secret", authorize, func() {
// this will execute as long as authorize doesn't write a response
})
让我们来看一个比较极端的例子:
package main
import (
"fmt"
"gopkg.in/macaron.v1"
)
func main() {
m := macaron.Classic()
m.Get("/",
func(ctx *macaron.Context) {
ctx.Data["Count"] = 1
},
func(ctx *macaron.Context) {
ctx.Data["Count"] = ctx.Data["Count"].(int) + 1
},
func(ctx *macaron.Context) {
ctx.Data["Count"] = ctx.Data["Count"].(int) + 1
},
ctx.Data["Count"] = ctx.Data["Count"].(int) + 1
},
func(ctx *macaron.Context) {
ctx.Data["Count"] = ctx.Data["Count"].(int) + 1
func(ctx *macaron.Context) string {
return fmt.Sprintf("There are %d handlers before this", ctx.Data["Count"])
},
)
m.Run()
}
先意淫下结果?没错,输出结果会是 There are 5 handlers before this
。Macaron 并没有对您可以使用多少个处理器有一个硬性的限制。不过,Macaron 又是怎么知道什么时候停止调用下一个处理器的呢?
想要回答这个问题,我们先来看下下一个例子:
package main
import (
"fmt"
"gopkg.in/macaron.v1"
)
func main() {
m := macaron.Classic()
m.Get("/",
func(ctx *macaron.Context) {
ctx.Data["Count"] = 1
},
func(ctx *macaron.Context) {
ctx.Data["Count"] = ctx.Data["Count"].(int) + 1
},
func(ctx *macaron.Context) {
ctx.Data["Count"] = ctx.Data["Count"].(int) + 1
},
func(ctx *macaron.Context) {
ctx.Data["Count"] = ctx.Data["Count"].(int) + 1
},
func(ctx *macaron.Context) string {
return fmt.Sprintf("There are %d handlers before this", ctx.Data["Count"])
},
func(ctx *macaron.Context) string {
return fmt.Sprintf("There are %d handlers before this", ctx.Data["Count"])
},
)
m.Run()
}
在这个例子中,输出结果将会变成 There are 4 handlers before this
,而最后一个处理器永远也不会被调用。这是为什么呢?因为我们已经在第 5 个处理器中向响应流写入了内容。所以说,一旦任一处理器向响应流写入任何内容,Macaron 将不会再调用下一个处理器。
路由还可以通过 来注册组路由:
同样的,您可以为某一组路由设置集体的中间件:
m.Group("/books", func() {
m.Get("/:id", GetBooks)
m.Post("/new", NewBook)
m.Put("/update/:id", UpdateBook)
m.Delete("/delete/:id", DeleteBook)
m.Group("/chapters", func() {
m.Get("/:id", GetBooks)
m.Post("/new", NewBook)
m.Put("/update/:id", UpdateBook)
m.Delete("/delete/:id", DeleteBook)