应该使用哪个Go路由器?(附流程图)

615 阅读10分钟

当你开始用Go构建网络应用时,你可能会问的第一个问题是*"我应该使用哪个路由器?"。

这也不是一个容易回答的问题。可能有超过100种不同的路由器,都有不同的API、功能和行为。因此,在这篇博文中,我评估了30种流行的路由器,并创建了一个最佳选择的短名单,以及一个流程图,你可以用它来帮助指导你的选择。

注意:如果你不想看讨论,你可以跳到流程图上

在我们开始之前,对术语做一些说明:

  • 我所说的支持基于方法的路由,是指路由器可以很容易地根据请求方法("GET","POST", 等等)将HTTP请求分派给不同的处理程序。
  • 支持URL路径中的变量,我的意思是,路由器可以很容易地声明路由,如/movies/{id} ,其中{id} 是URL路径中的一个动态值。
  • 支持 regexp 路由模式,我的意思是,路由器可以很容易地声明像/movies/{[a-z-]+} 这样的路由,其中[a-z-]+ 是 URL 路径中的一个必要的 regexp 匹配。
  • 支持基于主机的路由是指路由器可以很容易地根据URL主机(如www.example.com )而不仅仅是URL路径将HTTP请求分配给不同的处理程序。
  • 支持自定义路由规则的意思是,路由器可以很容易地添加自定义规则来路由请求(比如根据IP地址或Authorization 头中的值来路由到不同的处理程序)。
  • 我所说的冲突路由是指当你注册了两个(或更多)有可能匹配同一请求URL路径的路由模式。例如,如果你注册了路由/blog/{slug}/blog/new ,那么一个HTTP请求的路径/blog/new ,就会与这两个路由匹配。

注意:从软件工程的角度来看,冲突的路由是一件坏事。它们可能是错误和混乱的来源,一般来说,你应该在你的应用程序中尽量避免它们。

入围的路由器

有四个不同的路由器进入了候选名单。它们是 http.ServeMux, julienschmidt/httprouter, go-chi/chigorilla/mux.所有这四个都是经过良好测试的,有很好的文档,并积极维护。它们(大部分)都有稳定的API,并且与http.Handlerhttp.HandlerFunc ,以及标准的中间件模式兼容。

就速度而言,所有这四种路由器的速度都足以满足(几乎)每一种应用,我建议根据你需要的具体功能而不是性能来选择它们。我个人曾在不同时期在生产应用中使用这四种路由器,并对它们感到满意。

http.ServeMux

首先,我要说的是,如果你能使用 http.ServeMux的话,你也许应该使用。

作为Go标准库的一部分,它经过了非常多的测试和良好的记录。使用它意味着你不需要导入任何第三方依赖,而且大多数其他Go开发者也会熟悉它的工作方式。Go 1的兼容性承诺也意味着你应该能够长期依赖http.ServeMux ,以同样的方式工作。所有这些都是在应用维护方面的一大积极因素。

与其他大多数路由器不同的是,它还支持基于主机的路由,传入的请求URL被自动净化,而且它匹配路由的方式也很聪明:较长的路由模式总是优先于较短的。这有一个很好的副作用,就是你可以以任何顺序注册模式*,而不会改变你的应用程序的行为方式*。

http.ServeMux 的两个主要限制是,它不支持基于方法的路由或URL路径中的变量。但是,不支持基于方法的路由并不总是一个避免它的好理由--它很容易通过一些像这样的代码来解决:

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", index)

    err := http.ListenAndServe(":3000", mux)
    log.Fatal(err)
}

func index(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/" {
        http.NotFound(w, r)
        return
    }

    // Common code for all requests can go here...

    switch r.Method {
    case http.MethodGet:
        // Handle the GET request...

    case http.MethodPost:
        // Handle the POST request...

    case http.MethodOptions:
        w.Header().Set("Allow", "GET, POST, OPTIONS")
        w.WriteHeader(http.StatusNoContent)

    default:
        w.Header().Set("Allow", "GET, POST, OPTIONS")
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
    }
}

在这几行中,你已经有效地得到了基于方法的路由,以及自定义404405 响应和对OPTIONS 请求的支持。这比你在许多第三方路由中得到的要多得多。

随着时间的推移,我意识到http.ServeMux ,它有很多优点,在很多情况下是完全足够的。事实上,我唯一建议使用它的时候是当你需要支持URL路径中的变量或自定义路由规则的时候。在这些情况下,试图使用http.ServeMux ,可能会变得有点棘手,我认为一般来说,选择第三方路由器更好。

julienschmidt/httprouter

我认为 julienschmidt/httprouter就其行为和对HTTP规范的符合性而言,是任何第三方路由器中最接近 "完美 "的。它支持基于方法的路由和动态URL。它还自动处理OPTIONS 请求,并正确发送405 响应,并允许你为404405 响应设置自定义处理程序。

它不支持基于主机的路由、自定义路由规则或regexp路由模式。还需要注意的是,httprouter 不允许有冲突的路由,像/post/create/post/:id 。客观地说,这是一件好事,因为它有助于避免bug,但如果你需要使用冲突的路由(例如,与现有系统使用的路由保持一致),这可能是一个问题。

httprouter 的一个缺点是,API和文档有点混乱。这个包是在Go 1.7中引入请求上下文之前首次发布的,目前的很多API仍然存在,以支持这些旧版本的Go。现在,你可以使用常规的http.Handlerhttp.HandlerFunc 签名来编写你的处理程序,你所需要的只是router.Handler()router.HandlerFunc() 方法来注册它们。比如说:

func main() {
    router := httprouter.New()

    router.HandlerFunc("GET", "/", indexGet)
    router.HandlerFunc("POST", "/", indexPost)

    err := http.ListenAndServe(":3000", mux)
    log.Fatal(err)
}

func indexGet(w http.ResponseWriter, r *http.Request) {
    // Handle the GET request...
}

func indexPost(w http.ResponseWriter, r *http.Request) {
    // Handle the POST request...
}

go-chi/chi

go-chi/chi包支持基于方法的路由,URL路径中的变量,以及regexp路由模式。像httprouter ,它还允许你为404405 响应设置自定义处理程序。

但我最喜欢的是chi ,你可以创建使用特定中间件的路由 "组",就像下面的代码片段。这在你有很多中间件和路由需要管理的大型应用中非常有用:

r := chi.NewRouter()

// Middleware used on all routes
r.Use(exampleMiddlewareOne)
r.Use(exampleMiddlewareTwo)

r.Get("/one", exampleHandlerOne)

r.Group(func(r chi.Router) {
    // Middleware only used in this route group
    r.Use(exampleMiddlewareThree)

    r.Get("/two", exampleHandlerTwo)
})

值得注意的是,chi 允许冲突的路由,路由将按照它们被声明的顺序进行匹配。

chi 的两个缺点是,它不能自动处理OPTIONS 请求,也不能在405 响应中设置Allow 头。如果你正在构建一个网络应用程序或私人API,那么这些东西可能不是一个大问题--但如果你正在构建一个公共API,这就是需要注意的问题了。像httprouter ,它也不支持基于主机的路由。

需要注意的是,在过去的6年中,chi 已经有5个主要的版本更新,其中大部分都包括了破坏性的变化。历史不一定是未来的预测,但确实表明,对于chi 来说,向后兼容和不做破坏性的改变不如这个候选名单上的其他一些路由器那么重要。相比之下,httproutergorilla/mux 在这段时间内没有做任何破坏性的改变。

gorilla/mux

gorilla/mux包可能是最著名的 Go 路由器,这是有原因的。 它的功能非常丰富,包括支持基于方法的路由、动态 URL、regexp 路由模式和基于主机的路由。重要的是,它是这个短名单上唯一支持自定义路由规则和路由 "反转 "的路由器(就像你在Django、Rails或Laravel中得到的那样)。它还允许你为404405 响应设置自定义处理程序。

它的缺点基本上与chi 相同--它httprouter 那样自动处理OPTIONS 请求,也不在405 响应中包含Allow 头。同样,像chi ,它允许冲突的路由,路由按照它们被声明的顺序进行匹配。

鉴于chigorilla/mux 的缺点相似,在两者之间选择是相当直接的:如果你需要支持自定义路由规则、基于主机的路由或路由 "反转",请选择gorilla/mux 。如果你不需要这些 "高级 "功能,那么可能最好选择chi ,因为你可以获得管理中间件的漂亮功能,特别是如果你正在构建一个大型应用。

值得一提的是

另外两个我认为值得一提的路由是 bmizerany/patmatryer/way.我对这两个路由器情有独钟,因为它们都是刻意的简单。它们有小的API和非常清晰易懂的代码,这使得人们很容易准确地掌握路由器在幕后是如何工作的。尤其是matryer/way 背后的代码,非常值得一读。

虽然它们的功能不如入围名单中的其他路由器,但我认为它们的简单性使它们很适合用于教程(尤其是针对新地鼠的教程),或者作为一个起点/灵感,如果你想建立自己的自定义路由器。

流程图

考虑到各种利弊和支持的功能,这个流程图应该可以帮助你在四个入围的路由器中做出选择。

其他路由器

下面列出了我评估的其他路由器,并附有一个简短的说明,解释它们为什么没有进入短名单。

注意:我使用了*"版本库是否包含go.mod文件?"*这个问题作为衡量一个代码库目前是否被维护的代理措施。这似乎是合理的--如果维护者仍然参与围棋世界并关心代码,我的猜测是他们会在过去几年的某个时候更新版本库以使用模块:

存储库笔记
celrenheit/lion目前尚未维护。
claygod/Bxog目前未维护。
clevergo/clevergo使用自定义处理程序签名(不是http.Handler或http.HandlerFunc)。
dimfeld/httptreemux不完全支持http.Handler。需要中间件来设置自定义404/405处理程序。
Donutloop/mux目前尚未维护。
gernest/alien目前尚未维护。
go-ozzo/ozzo-routing使用自定义处理程序签名(不是http.Handler或http.HandlerFunc)。
go-playground/Lars目前尚未维护。
go-zoo/bone很好,但与chi的用例相似(后者提供更多)。不完整的测试。
go101/tinyrouter冗长的路由声明。不会自动发送405响应。
gocraft/web目前尚未维护。
goji/goji略有不同,但灵活的API,支持自定义匹配器。需要中间件来设置自定义404/405处理程序。不错,但我认为gorilla/mux提供了类似的功能,而且更容易使用。
goroute/route使用自定义处理程序签名(不是http.Handler或http.HandlerFunc)。
gowww/router很好,但与chi的使用情况类似(后者提供更多)。没有办法设置自定义405处理程序。
GuilhermeCaruso/bellt无法设置自定义404或405处理程序。
husobee/vestigo目前尚未维护。只支持http.HandlerFunc。
naoina/denco目前未维护。
nbari/violetear很好,但与chi的用例相似(后者提供更多)。用自己的自定义类型包装http.ResponseWriter,这在某些情况下可能会导致问题。
nbio/hitch缺少文档。
nissy/bon目前没有维护。
razonyang/fastrouter目前尚未维护。
rs/xmux目前尚未维护。使用自定义处理程序签名(不是http.Handler或http.HandlerFunc)。
takama/router目前尚未维护。
vardius/gorouter很好,但与chi的用例相似(后者提供更多)。5年内的四个主要版本表明API可能不可靠。
VividCortex/siesta很好,但与chi的使用情况类似(后者提供更多)。没有办法设置自定义的405处理器。
xujiajun/gorouter目前尚未维护。