Go语言版本1.22的路由增强功能
Go 1.22对 net/http
包中的路由器进行了两项增强:方式匹配和通配符。这些特性允许你将常见的路由表示为模式,而非Go代码。尽管这些功能简单易解释和使用,但想选择成功模式的正确规则(当多个模式匹配一个请求时)依然是个挑战。我们作出这些改变是为了持续让Go成为构建生产系统的优秀语言。我们研究了许多第三方web框架,提取出我们认为最常用的特性,并将它们集成进 net/http
。然后我们通过在GitHub上与社区进行讨论和问题建议,验证了我们的选择并改进了我们的设计。将这些特性添加到标准库意味着许多项目少了一个依赖性。但对于当前用户或带有高级路由需求的程序来说,第三方Web框架仍然是一个很好的选择。
新增功能
新的路由功能几乎完全影响到了 net/http.ServeMux
方法 Handle
和 HandleFunc
传递的模式字符串,以及顶层函数 http.Handle
和 http.HandleFunc
。唯一的API变化是在 net/http.Request
上添加了两个新方法,用于处理通配符匹配。我们将通过一个假设的博客服务器来说明这些变化,其中每篇帖子都有一个整数标识符。请求如GET /posts/234
取回ID为234的帖子。在Go 1.22之前,处理这些请求的代码将从这样的一行开始:
-
http . Handle ( "/posts/" , handlePost )
尾部的斜杠将所有以 /posts/
开始的请求路由到 handlePost
函数,该函数将需要检查HTTP方法是否为GET,提取标识符,并取回帖子。由于方法检查并非严格必要以满足请求,忽略它将是一个自然的错误。这将意味着像 DELETE/posts/234
这样的请求将取回帖子,这至少是令人惊讶的。而在Go 1.22中,现有代码将继续工作,或者你可以改写成这样:
-
http . Handle ( "GET /posts/{id}" , handlePost2 )
这个模式匹配路径开始以 /posts/
的请求并且有两个段的GET请求将匹配到这个模式(特别是,GET也可以匹配HEAD; 其他所有的方法都仅仅匹配一次)。handlePost2
这个函数不再需要检查方法,并且可以通过在Request上使用新的 PathValue
方法来编写提取标识符字符串:
-
idString := req . PathValue ( "id" )
剩下的 handlePost2
将会行为类似 handlePost
,将字符串标识符转换为整数并获取帖子。如DELETE /posts/234
这样的请求在没有其他匹配模式被注册的情况下将失败。根据HTTP语法,一个 net/http
服务器将会回复这样的请求一个405 Method Not Allowed错误并在Allow头中列出可用的方法。一个通配符可以匹配一个全部的段,就像上面的例子中的 {id}
,或者,如果它以 ...
结束,它可以匹配路径中所有剩余的段,就像这个模式:/files/{pathname...}
。还有最后一点语法。正如我们上面展示的,以斜杠结尾的模式,像 /posts/
,匹配所有以该字符串开始的路径。要只匹配结尾有斜杠的路径,你可以写成 /posts/{$}
。这将匹配到 /posts/
但不会匹配到 /posts
或 /posts/{234}
。还有最后一点API:net/http.Request
有一个 SetPathValue
方法,以便标准库之外的路由器可以通过Request.PathValue使他们自己路径解析的结果可用。
优先级
每一个HTTP路由器必须处理重叠的模式,就像 /posts/{id}
和 /posts/latest
。这两种模式都匹配“posts/latest”路径,但最多只能有一个处理请求。哪一个模式优先呢?一些路由器不允许重叠;其它的使用最后注册的模式。Go一直允许重叠,并且有选择更长的模式的优先级规则,不论注册顺序。保证顺序不依赖很重要并且必须向后兼容,但我们需要一个比“最长的赢”更好的规则。这个规则会选择 /post/latest
而不是 /post/{id}
,但却会选择 /posts/{id}/{category}
而不是 /posts/{id}
。当我们选择新的规则时,有几点我们想要:我们想要一个可以预测的规则,这样当你看到一系列模式注册的时候,你可以知道哪一个模式会匹配给定路径。如果这个规则对于模式注册的顺序没有依赖性,那么它就是“顺序不依赖”的,用户将更容易理解和处理。因为所以这个匹配的路径在处理程序内部都能被看到,一个好的规则需要对于所有请求一视同仁,其他规则则可能给攻击者提供利用程序隐藏bug的机会。最后,我们希望新规则能够尽可能的保留现有的行为,以支持向后兼容性。目前, net/http
的优先级规则被定义为,“更长的元素会匹配”。换句话说,我们会比较模式中的元素总数量,包括各种元素类型,即使他们不在同级别。例如,模式 /a/b/c
和 /a/b
之间的比较,将会有 /a/b/c
胜出,因为它有3个元素,而 /a/b
只有2个。在新的规则下,我们依然遵循旧规定,长优先匹配,但引入了明显的层级关系。在进行比较的时候,我们会先看模式元素类型的数量是否有不同, 然后才看元素的总数量。所以 /a/b/c
仍然会优先匹配,因为 /a/b
只有两层, "这很直观。然而,当涉及到带有通配符的模式时,这种层级关系就显得非常重要了。如 /a/{p}/c
和 /a/b
, /a/b
优先匹配,尽管总元素数量较少, 因为它包含更多的文字元素。带有方法和通配符的新模式让我们更有信心地使用新匹配规则,新规则会尽量匹配文本元素,而适当地降低了通配符元素优先级。特定的元素(文本和方法)在具体的元素(通配符)之前,文本元素优先于方法元素。在比较完所有这些情况后,我们最后才进行长度比较。这种新的优先级规则在实践中很容易理解和预测。
总结
这些新特性让Go的内置HTTP库更强大,能更清晰地表达日常web编程任务。这些特性是 net/http
包长期沉淀的结果,经过了多次反复考量,包括研究web框架的API,同社区反复交流,以及我们认为对于大部分Go web编程来说是有意义的特性。如果你对这些特性有任何的反馈,欢迎在我们的issue tracker上提出。我们期待你在Go1.22版本中试用这些新功能!