Categorygithub.com/issue9/mux/v4
modulepackage
4.2.1
Repository: https://github.com/issue9/mux.git
Documentation: pkg.go.dev

# README

mux

Go Go version Go Report Card license codecov PkgGoDev

mux 功能完备的 Go 路由器:

  1. 路由参数;
  2. 支持正则表达式作为路由项匹配方式;
  3. 丰富的 OPTIONS 请求处理方式;
  4. 自动生成 HEAD 请求内容;
  5. 根据路由反向生成地址;
  6. 任意风格的路由,比如 discuz 这种不以 / 作为分隔符的;
  7. 分组路由,比如按域名,或是版本号等;
  8. 支持中间件;
import "github.com/issue9/middleware/v4/header"

h := header.New(map[string]string{
	"Access-Control-Allow-Origin": "*"
})

m := mux.New(false, false, false, nil, nil)
m.AddMiddleware(h.Middleware) // 中间件,输出跨域的报头。

router, ok := m.New("example.com", group.NewHosts("example.com"))
router.Get("/users/1", h).
    Post("/login", h).
    Get("/pages/{id:\\d+}.html", h). // 匹配 /pages/123.html 等格式,path = 123
    Get("/posts/{path}.html", h).    // 匹配 /posts/2020/11/11/title.html 等格式,path = 2020/11/11/title
    Options("/users/1", "GET").     // OPTIONS /user/1 手动指定该路由项的 OPTIONS 请求方法返回内容

// 统一前缀路径的路由
p := m.Prefix("/api")
p.Get("/logout", h) // 相当于 m.Get("/api/logout", h)
p.Post("/login", h) // 相当于 m.Get("/api/login", h)

// 对同一资源的不同操作
res := p.Resource("/users/{id:\\d+}")
res.Get(h)   // 相当于 m.Get("/api/users/{id:\\d+}", h)
res.Post(h)  // 相当于 m.Post("/api/users/{id:\\d+}", h)
res.URL(map[string]string{"id": "5"}) // 构建一条基于此路由项的路径:/users/5

http.ListenAndServe(":8080", m)

语法

正则表达式

路由中支持以正则表达式的方式进行匹配,表达式以大括号包含,内部以冒号分隔, 前半部分为变量的名称,后半部分为变量可匹配类型的正则表达式。比如:

/posts/{id:\\d+} // 将被转换成 /posts/(?P<id>\\d+)
/posts/{:\\d+}   // 将被转换成 /posts/\\d+

命名参数

若路由字符串中,所有的正则表达式冒号之后的内容是特定的内容,或是无内容, 则会被转换成命名参数,因为有专门的验证方法,性能会比较正则稍微好上一些。

 /posts/{id}.html                  // 匹配 /posts/1.html
 /posts-{id}-{page}.html           // 匹配 /posts-1-10.html
 /posts/{id:digit}.html            // 匹配 /posts/1.html
 /posts/{path}.html                // 匹配 /posts/2020/11/11/title.html

目前支持以下作为命名参数的内容约束:

  • digit 限定为数字字符,相当于正则的 [0-9]
  • word 相当于正则的 [a-zA-Z0-9]
  • any 表示匹配任意非空内容;
  • "" 为空表示任意内容,包括非内容;

用户也可以自行添加新的约束符。具体可参考 https://pkg.go.dev/github.com/issue9/mux/v4/interceptor

在路由字符串中若是以命名参数结尾的,则表示可以匹配之后的任意字符。

/blog/assets/{path}       // 可以匹配 /blog/assets/2020/11/11/file.ext 等格式
/blog/{tags:\\w+}/{path}
/blog/assets{path}

路径匹配规则

可能会出现多条记录与同一请求都匹配的情况,这种情况下, 系统会找到一条认为最匹配的路由来处理,判断规则如下:

  1. 普通路由优先于正则路由;
  2. 正则路由优先于命名路由;

比如:

/posts/{id}.html              // 1
/posts/{id:\\d+}.html         // 2
/posts/1.html                 // 3

/posts/1.html      // 匹配 3
/posts/11.html     // 匹配 2
/posts/index.html  // 匹配 1

路由参数

通过正则表达式匹配的路由,其中带命名的参数可通过 Params() 获取:

params := Params(r)

id, err := params.Int("id")
 // 或是
id := params.MustInt("id", 0) // 0 表示在无法获取 id 参数的默认值

高级用法

分组路由

可以通过匹配 group.Matcher 接口,定义了一组特定要求的路由项。

// server.go

m := mux.Default()

def, ok := m.NewRouter("default", group.NewPathVersion("v1"))
def.Get("/path", h1)

host, ok := m.NewRouter("host", group.NewHosts("*.example.com"))
host.Get("/path", h2)

http.ListenAndServe(":8080", m)

// client.go

// 访问 h2 的内容
r := http.NewRequest(http.MethodGet, "https://abc.example.com/path", nil)
r.Do()

// 访问 h1 的内容
r := http.NewRequest(http.MethodGet, "https://other_domain.com/v1/path", nil)
r.Do()

interceptor

正常情况下,/posts/{id:\d+} 或是 /posts/{id:[0-9]+} 会被当作正则表达式处理, 但是正则表达式的性能并不是很好,这个时候我们可以通完 interceptor 包进行拦截, 采用自己的特定方法进行处理:

import "github.com/issue9/mux/v4/interceptor"

func digit(path string) bool {
    for _, c := range path {
        if c < '0' || c > '9' {
            return false
        }
    }
    return len(path) > 0
}

// 路由中的 \d+ 和 [0-9]+ 均采用 digit 函数进行处理,不再是正则表达式。
interceptor.Register(digit, "\\d+", "[0-9]+")

OPTIONS

默认情况下,用户无须显示地实现它,系统会自动实现。 当然用户也可以使用 *.Options() 函数指定特定的数据; 或是直接使用 *.Handle() 指定一个自定义的实现方式。

如果不需要的话,也可以在 New() 中将 disableOptions 设置为 true。 显示设定 OPTIONS,不受 disableOptions 的影响。

m := mux.Default()
r, ok := m.NewRouter("default", group.Any)

r.Get("/posts/{id}", nil)     // 默认情况下, OPTIONS 的报头为 GET, OPTIONS
r.Options("/posts/{id}", "*") // 强制改成 *
r.Delete("/posts/{id}", nil)  // OPTIONS 依然为 *

r.Remove("/posts/{id}", http.MethodOptions)    // 在当前路由上禁用 OPTIONS
r.Handle("/posts/{id}", h, http.MethodOptions) // 显示指定一个处理函数 h

HEAD

默认情况下,用户无须显示地实现 HEAD 请求, 系统会为每一个 GET 请求自动实现一个对应的 HEAD 请求, 当然也与 OPTIONS 一样,你也可以自通过 mux.Handle() 自己实现 HEAD 请求。

中间件

mux 本身就是一个实现了 http.Handler 接口的中间件, 所有实现官方接口 http.Handler 的都可以附加到 mux 上作为中间件使用。

mux 本身也提供了对中间件的管理功能,同时 middleware 提供了常用的中间件功能。

import "github.com/issue9/middleware/header"
import "github.com/issue9/middleware/compress"

h := header.New(map[string]string{
    "Access-Control-Allow-Origin": "*"
}

c := compress.New(log.Default(), "*")

m := Default()

// 添加中间件
m.AddMiddleware(h.Middleware).
	AddMiddleware(c.Middleware)

r, ok := m.NewRouter("def", group.NewHost("example.com"))

性能

https://caixw.github.io/go-http-routers-testing/ 提供了与其它几个框架的对比情况。

版权

本项目采用 MIT 开源授权许可证,完整的授权说明可在 LICENSE 文件中找到。

# Packages

Package group 提供了按条件进行分组路由的功能.
Package interceptor 针对带参数类型路由的拦截处理 在解析诸如 /authors/{id:\\d+} 带参数的路由项时, 用户可以通过拦截并自定义对参数部分 {id:\\d+} 的解析, 从而不需要走正则表达式的那一套解析流程,可以在一定程度上增强性能。 一旦正则表达式被拦截,则节点类型也将不再是正则表达式, 其处理优先级会比正则表达式类型高。 在某些情况下,可能会造成处理结果不相同。比如: /authors/{id:\\d+} // 1 /authors/{id:[0-9]+} // 2 以上两条记录是相同的,但因为表达式不同,也能正常添加, 处理流程,会按添加顺序优先比对第一条,所以第二条是永远无法匹配的。 但是如果你此时添加了 Register(MatchDigit, "[0-9]+"), 将第二个记录的优先级作为提升,以后的匹配都是优先第二条, 造成第一条永远无法匹配到数据。 除非是改造旧有的项目,否则建议自定义一些约束符来处理。比如: /authors/{id:digit} 用户只要注册一个 Register(MatchDigit, "digit") 即可拦截针对 digit 的项, 且不会影响正则表达式的处理。 interceptor 也是本着这样的原则,添加了以下拦截器: - digit 数字; - word 单词,即 [a-zA-Z0-9]+; - any 任意非空内容;.
Package params 获取和转换路由中的参数信息.

# Functions

ApplyMiddlewares 按顺序将所有的中间件应用于 h.
ApplyMiddlewaresFunc 按顺序将所有的中间件应用于 h.
Default New 的默认参数版本.
IsWell 语法格式是否正确 如果出错,则会返回具体的错误信息。.
Methods 返回所有支持的请求方法.
New 声明一个新的 Mux disableOptions 是否禁用自动生成 OPTIONS 功能; disableHead 是否禁用根据 Get 请求自动生成 HEAD 请求; skipCleanPath 是否不对访问路径作处理,比如 "//api" ==> "/api"; notFound 404 页面的处理方式,为 nil 时会调用默认的方式进行处理; methodNotAllowed 405 页面的处理方式,为 nil 时会调用默认的方式进行处理;.
NewMiddlewares 声明新的 Middlewares 实例.
Params 获取路由的参数集合.

# Structs

Middlewares 中间件管理.
Mux 提供了强大的路由匹配功能 可以对路径按正则或是请求方法进行匹配。用法如下: m := mux.Default() router, ok := m.NewRouter("default", group.Any) router.Get("/abc/h1", h1).
Prefix 可以将具有统一前缀的路由项集中在一起操作 example: p := srv.Prefix("/api") p.Get("/users") // 相当于 srv.Get("/api/users") p.Get("/user/1") // 相当于 srv.Get("/api/user/1").
Resource 以资源地址为对象的路由配置 r, _ := srv.Resource("/api/users/{id}") r.Get(h) // 相当于 srv.Get("/api/users/{id}") r.Post(h) // 相当于 srv.Post("/api/users/{id}") url := r.URL(map[string]string{"id":5}) // 获得 /api/users/5.
Router 提供了基本的路由项添加和删除等功能.

# Type aliases

MiddlewareFunc 将一个 http.Handler 封装成另一个 http.Handler.