# 路由模块

在 Macaron 中, 路由是一个 HTTP 方法配对一个 URL 匹配模型. 每一个路由可以对应一个或多个处理器方法:

```go
m.Get("/", func() {
    // show something
})

m.Patch("/", func() {
    // update something
})

m.Post("/", func() {
    // create something
})

m.Put("/", func() {
    // replace something
})

m.Delete("/", func() {
    // destroy something
})

m.Options("/", func() {
    // http options
})

m.Any("/", func() {
    // do anything
})

m.Route("/", "GET,POST", func() {
    // combine something
})

m.Combo("/").
    Get(func() string { return "GET" }).
    Patch(func() string { return "PATCH" }).
    Post(func() string { return "POST" }).
    Put(func() string { return "PUT" }).
    Delete(func() string { return "DELETE" }).
    Options(func() string { return "OPTIONS" }).
    Head(func() string { return "HEAD" })

m.NotFound(func() {
    // 自定义 404 处理逻辑
})
```

几点说明：

* 路由匹配的顺序是按照他们被定义的顺序执行的，
* ...但是，匹配范围较小的路由优先级比匹配范围大的优先级高（详见 **匹配优先级**）。
* 最先被定义的路由将会首先被用户请求匹配并调用。

在一些时候，每当 GET 方法被注册的时候，都会需要注册一个一模一样的 HEAD 方法。为了达到减少代码的目的，您可以使用一个名为 [`SetAutoHead`](https://gowalker.org/gopkg.in/macaron.v1#Router_SetAutoHead) 的方法来协助您自动注册：

```go
m := New()
m.SetAutoHead(true)
m.Get("/", func() string {
    return "GET"
}) // 路径 "/" 的 HEAD 也已经被自动注册
```

如果您想要使用子路径但让路由代码保持简洁，可以调用 `m.SetURLPrefix(suburl)`。

## 命名参数

路由模型可能包含参数列表, 可以通过 [`*Context.Params`](https://gowalker.org/gopkg.in/macaron.v1#Context_Params) 来获取:

### 占位符

使用一个特定的名称来代表路由的某个部分：

```go
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"))
})
```

当然，想要偷懒的时候可以将 `:` 前缀去掉：

```go
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"))
})
```

### 全局匹配

路由匹配可以通过全局匹配的形式:

```go
m.Get("/hello/*", func(ctx *macaron.Context) string {
    return "Hello " + ctx.Params("*")
})
```

那么，如果将 `*` 放在路由中间会发生什么呢？

```go
m.Get("/date/*/*/*/events", func(ctx *macaron.Context) string {
    return fmt.Sprintf("Date: %s/%s/%s", ctx.Params("*0"), ctx.Params("*1"), ctx.Params("*2"))
})
```

### 正则表达式

您还可以使用正则表达式来书写路由规则：

* 常规匹配：

  ```go
    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"))
    })
  ```
* 混合匹配：

  ```go
    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]+)` 正则的简写。
  * `/user/:name:string`：`:string` 是 `([\w]+)` 正则的简写。

## 匹配优先级

以下为从高到低的不同模式的匹配优先级：

* 静态路由：
  * `/`
  * `/home`
* 正则表达式路由：
  * `/(.+).html`
  * `/([0-9]+).css`
* 路径-后缀路由：
  * `/*.*`
* 占位符路由：
  * `/:id`
  * `/:name`
* 全局匹配路由：
  * `/*`

其它说明：

* 相同模式的匹配优先级是根据添加的先后顺序决定的。
* 层级相对明确的模式匹配优先级要高于相对模糊的模式：
  * `/*/*/events` > `/*`

### 构建 URL 路径

您可以通过 [`*Route.Name`](https://gowalker.org/gopkg.in/macaron.v1#Route_Name) 方法配合命名参数来构建 URL 路径，不过首先需要为路由命名：

```go
// ...
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")
// ...
```

然后通过 [`*Router.URLFor`](https://gowalker.org/gopkg.in/macaron.v1#Router_URLFor) 方法来为指定名称的路由构建 URL 路径：

```go
// ...
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 模板引擎使用

```go
// ...
m.Use(macaron.Renderer(macaron.RenderOptions{
    Funcs:      []template.FuncMap{map[string]interface{}{
        "URLFor": m.URLFor,    
    }},
}))
// ...
```

#### 配合 Pongo2 模板引擎使用

```go
// ...
ctx.Data["URLFor"] = ctx.URLFor
ctx.HTML(200, "home")
// ...
```

## 高级路由定义

路由处理器可以被相互叠加使用, 例如很有用的地方可以是在验证和授权的时候:

```go
m.Get("/secret", authorize, func() {
    // this will execute as long as authorize doesn't write a response
})
```

让我们来看一个比较极端的例子：

```go
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) {
            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 又是怎么知道什么时候停止调用下一个处理器的呢？

想要回答这个问题，我们先来看下下一个例子：

```go
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 将不会再调用下一个处理器。

### 组路由

路由还可以通过 [`macaron.Group`](https://gowalker.org/gopkg.in/macaron.v1#Router_Group) 来注册组路由：

```go
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)
    })
})
```

同样的，您可以为某一组路由设置集体的中间件：

```go
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)
    }, MyMiddleware3, MyMiddleware4)
}, MyMiddleware1, MyMiddleware2)
```

同样的，Macaron 不在乎您使用多少层嵌套的组路由，或者多少个组级别处理器（中间件）。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://go-macaron.com/zh-cn/middlewares/routing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
