很多人为了使用 Go web 中更好的路由,会使用第三方的库 httprouter
、gorilla/mux
等。在明年的春节左右发布的 Go 1.22 中,Go 官方终于对标准库中的http.ServeMux
下手了,对它的功能进行了优化,终于可以抛弃第三方库了。
一个令人兴奋的提案预计将在 Go 1.22 中实现—— 增强标准库net/http
包中默认 HTTP 服务多路复用器的模式匹配能力。
现有的多路复用器(http.ServeMux
)提供了基本的路径匹配,但除此之外没有太多。这导致了一堆的第三方库实现了更强大的功能。
Go 1.22 中的新多路复用器将通过提供高级匹配能力来显著弥合与第三方库的差距。在这篇短文中,我将快速介绍新的多路复用器(mux
)。我还将给出 REST 服务器的示例,并比较新的标准库mux
与gorilla/mux
的性能。
使用新的 mux
如果你曾经在 Go 中使用过第三方 mux/router(比如gorilla/mux
),那么使用新的标准 mux 将是简单而熟悉的。从阅读它的文档开始——它简短丝滑。
让我们来看几个基本用法示例。我们的第一个示例演示了 mux 的一些新模式匹配功能:
package main import ( "fmt" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("GET /path/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "got path\n") }) mux.HandleFunc("/task/{id}/", func(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") fmt.Fprintf(w, "handling task with id=%v\n", id) }) http.ListenAndServe("localhost:8090", mux) }
经验丰富的 Go 程序员会立即注意到两个新功能:
- 在第一个处理程序中,HTTP method(在本例中为
GET
)被明确指定为模式的一部分。这意味着该处理程序将只触发以/path/
开头的路径的GET
请求,而不触发其他 HTTP method。 - 在第二个处理程序中,第二个路径组件 -
{id}
中有一个通配符,这是以前不支持的。通配符将匹配单个路径组件,然后处理程序可以通过请求的PathValue
方法访问匹配的值。
由于 Go 1.22 尚未发布,你可以使用gotip
运行此示例。请参阅完整的代码示例以及运行此程序的完整说明。让我们来试试这个服务器:
$ gotip run sample.go
在一个单独的终端中,我们可以发出一些 curl 调用来测试它:
$ curl localhost:8090/what/ 404 page not found $ curl localhost:8090/path/ got path $ curl -X POST localhost:8090/path/ Method Not Allowed $ curl localhost:8090/task/f0cd2e/ handling task with id=f0cd2e
请注意,服务器如何拒绝对/path/
的 POST 请求,同时允许(curl 的默认值)GET
请求。还要注意,当请求匹配时,id
通配符是如何被分配一个值的。我再次鼓励您查看新ServeMux的文档。您将了解其他功能,如将尾随路径与带有{id}
的通配符匹配,路径以{$}
结尾的严格匹配以及其他规则。
提案中特别注意不同模式之间的潜在冲突。请考虑此设置:
mux := http.NewServeMux() mux.HandleFunc("/task/{id}/status/", func(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") fmt.Fprintf(w, "handling task status with id=%v\n", id) }) mux.HandleFunc("/task/0/{action}/", func(w http.ResponseWriter, r *http.Request) { action := r.PathValue("action") fmt.Fprintf(w, "handling task 0 with action=%v\n", action) })
假设服务器收到/task/0/status/
的请求——它应该转到哪个处理程序?两者都匹配!因此,新的ServeMux
文档仔细地描述了模式的优先级规则以及潜在的冲突。如果发生冲突,注册会 panic。事实上,对于上面的例子,我们得到了如下内容:
panic: pattern "/task/0/{action}/" (registered at sample-conflict.go:14) conflicts with pattern "/task/{id}/status/" (registered at sample-conflict.go:10): /task/0/{action}/ and /task/{id}/status/ both match some paths, like "/task/0/status/". But neither is more specific than the other. /task/0/{action}/ matches "/task/0/action/", but /task/{id}/status/ doesn't. /task/{id}/status/ matches "/task/id/status/", but /task/0/{action}/ doesn't.
该信息详细且有用。如果我们在复杂的注册方案中遇到冲突(尤其是当模式在源代码中的多个位置注册时),这些细节会非常有用。
用新的 mux 实现服务器
REST Servers in Go series
的 REST 服务器使用几种不同的方法为 Go 中的任务/待办事项列表应用程序实现了一个简单的服务器。第 1 部分从标准库开始,第 2 部分使用gorilla/mux
路由器重新实现了相同的服务器。
现在是再次实现它的好时机,但有了 Go 1.22 的增强mux
;将该解决方案与使用gorilla/mux
的解决方案进行比较将特别有趣。
此项目的完整代码可在此处获得。让我们看看几个有代表性的代码示例,从模式注册开始:
mux := http.NewServeMux() server := NewTaskServer() mux.HandleFunc("POST /task/", server.createTaskHandler) mux.HandleFunc("GET /task/", server.getAllTasksHandler) mux.HandleFunc("DELETE /task/", server.deleteAllTasksHandler) mux.HandleFunc("GET /task/{id}/", server.getTaskHandler) mux.HandleFunc("DELETE /task/{id}/", server.deleteTaskHandler) mux.HandleFunc("GET /tag/{tag}/", server.tagHandler) mux.HandleFunc("GET /due/{year}/{month}/{day}/", server.dueHandler)
就像在gorilla/mux
示例中一样,这里我们使用特定的 HTTP method 将请求(具有相同路径)路由到不同的处理程序;使用旧的http.ServeMux
这样的匹配器就必须转到同一个处理程序,然后由该处理程序根据该方法决定要做什么。
让我们看看其中一个处理程序:
func (ts *taskServer) getTaskHandler(w http.ResponseWriter, req *http.Request) { log.Printf("handling get task at %s\n", req.URL.Path) id, err := strconv.Atoi(req.PathValue("id")) if err != nil { http.Error(w, "invalid id", http.StatusBadRequest) return } task, err := ts.store.GetTask(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } renderJSON(w, task) }
它从req.PathValue("id")
中提取 ID 值。类似于 Gorilla 方法;然而,由于我们没有正则表达式指定{id}
只匹配整数,因此我们必须注意strconv.Atoi
返回的错误。
总之,最终结果与第 2 部分中使用gorilla/mux
的解决方案非常相似。与普通的标准库方法相比,处理程序的方式要好得多,因为 mux 现在可以进行更复杂的路由,而不会将许多路由决策留给处理程序本身。
结论
“我应该使用哪个 router 库?”一直是 Go 初学者的常见问题。我相信在 Go 1.22 发布后,这个问题的常见答案会发生变化,因为许多人会发现新的标准库 mux 足以满足他们的需求,而无需求助于第三方软件包。
其他人会坚持使用熟悉的第三方库,这完全没关系。像gorilla/mux
这样的路由器仍然提供比标准库更多的功能;除此之外,许多 Go 程序员选择了像 Gin 这样的轻量级框架,它提供了一个路由器,但也提供了用于构建 web 后端的额外工具。
总而言之,这对所有 Go 用户来说无疑是一个积极的变化。无论人们是使用第三方软件包还是坚持使用标准库,让标准库更有能力对整个社区来说都是一个积极的方面。
翻译自Better HTTP server routing in Go 1.22。