可以不需要第三方路由了:Go1.22 的路由增强功能
这是官方博文,原文地址:https://go.dev/blog/routing-enhancements。
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.HandleFunc("/posts/", handlePost)
斜杠/
路由所有以/posts/
开头的请求到handlePost
函数,该函数必须检查 HTTP 方法是 GET,提取标识符,并检索帖子。由于方法检查并不是满足请求的严格要求,至少可以说这是一个自然的错误,如果忽略了它,那么像DELETE /posts/234
这样的请求将获取帖子,这至少是令人惊讶的。
在 Go 1.22 中,现有代码将继续工作,或者你可以改为这样写:
http.HandleFunc("GET /posts/{id}", handlePost2)
这个模式匹配一个 GET 请求,其路径以“/posts/”开头并有两个段。(作为一个特殊情况,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 一直允许重叠,并选择无论注册顺序如何都选择更长的模式。对我们来说,保持顺序独立性很重要(并且对向后兼容是必要的),但我们需要的是一个比“最长者胜”更好的规则。那个规则会选择/posts/latest
而不是/posts/{id}
,但会选择/posts/{identifier}
而不是两者。那看起来是错误的:通配符名称不应该有关系。感觉/posts/latest
应该总是赢得这场比赛,因为它匹配单个路径而不是多个。
我们寻找一个好的优先级规则的过程中,考虑了许多模式的属性。例如,我们考虑过更喜欢具有最长字面量(非通配符)前缀的模式。那会选择/posts/latest
而不是/posts/{id}
。但它不能区分/users/{u}/posts/latest
和/users/{u}/posts/{id}
,而且前者应该优先。
我们最终选择了一个基于模式意义而非外观的规则。每个有效模式都匹配一组请求。例如,/posts/latest
匹配路径为/posts/latest
的请求,而/posts/{id}
匹配任何第一个段是“posts”的两段路径的请求。我们说一个模式比另一个模式更具体,如果它匹配的是请求的严格子集。模式/posts/latest
比/posts/{id}
更具体,因为后者匹配前者所做的每个请求,以及更多。
优先级规则很简单:最具体的模式获胜。这个规则符合我们的直觉,即posts/latest
应该优先于posts/{id}
,/users/{u}/posts/latest
应该优先于/users/{u}/posts/{id}
。它对方法也有意义。例如,GET /posts/{id}
优先于/posts/{id}
,因为前者只匹配 GET 和 HEAD 请求,而后者匹配任何方法的请求。
如果两个模式重叠但都不是更具体的,怎么办?例如,/posts/{id}
和/{resource}/latest
都匹配/posts/latest
。没有明显的答案来决定哪个具有优先权,所以我们认为这些模式彼此冲突。注册它们两个(无论顺序如何!)将会导致 panic。
优先级规则对方法和路径的工作方式完全相同,但我们不得不为主机做一个例外,以保持兼容性:如果两个模式在其他方面会冲突,而一个有主机而另一个没有,那么有主机的模式具有优先权。
计算机科学的学生可能记得正则表达式和正则语言的美丽理论。每个正则表达式都挑选出一个正则语言,即表达式匹配的字符串集。有些问题通过谈论语言而不是表达式来提出和回答更容易。我们的优先级规则就是受到这个理论的启发。实际上,每个路由模式都对应一个正则表达式,匹配请求的集合扮演着正则语言的角色。
通过语言而不是表达式来定义优先级使得它容易陈述和理解。但基于潜在无限集的规则有一个缺点:不清楚如何有效地实现它。事实证明,我们可以通过逐段检查模式来确定两个模式是否冲突。大致来说,如果一个模式在另一个模式有通配符的地方有字面量段,那么它更具体;但如果字面量在两个方向上都与通配符对齐,那么模式就冲突了。
当在ServeMux
上注册新模式时,它会检查与先前注册的模式的冲突。但检查每对模式将需要两次时间。我们使用一个索引来跳过不能与新模式冲突的模式;在实践中,它工作得很好。无论如何,这个检查发生在模式注册时,通常在服务器启动时。在 Go 1.22 中匹配传入请求的时间与以前版本相比没有太大变化。
兼容性
我们尽了最大努力使新功能与 Go 的旧版本兼容。新模式语法是旧模式的超集,新优先级规则推广了旧规则。但有一些边缘情况。例如,以前的 Go 版本接受带有大括号的模式,并将它们视为字面量,但 Go 1.22 使用大括号作为通配符。GODEBUG 设置httpmuxgo121
恢复旧行为。
有关这些路由增强功能的更多详细信息,请参见net/http.ServeMux
文档。
转载:站长 polarisxu
作者:漏网的鱼
链接:https://www.csev.cn/it/20240402147.html
来源:彩色动力-测试分享
版权声明:本文欢迎任何形式转载,转载时完整保留本声明信息(包含原文链接、原文出处、原文作者、版权声明)即可。本文后续所有修改都会第一时间在原始地址更新。
共有 0 条评论