翻译自pkg.go.dev,对某些模糊的地方添加了自己的解释

链接

  • Github源码
    • 关于看源码,还是应该从pkg.go.dev网站上去看,github上的源码可能包含尚未发布的内容。从pkg.go.dev网站上底部的source code可以直接查看相应的发布版本的源码
  • 博客

  • HttpRouter是轻量级高性能的HTTP请求路由,也称多路复用器(Mux),主要实现了net/http中的Handler部分的内容

  • net/http中实现的Mux不同,本路由器支持在路由规则(pattern)中使用变量,并与request方法匹配,且具有更好的伸缩性
  • 使用了基数树进行路由的有效匹配,性能更高占用内存更小(类似redis的stream的底层数据结构)
  • GIN框架的路由是基于这个包实现的

    2. 特性

  • 唯一的明确匹配

    • 其他的路由,比如http.ServeMux,一个URL请求可以匹配多个路由,所以需要添加各种优先级规则,比如最长匹配先注册先匹配等。本路由器的设计是,一个请求只能匹配0或1个路由。所以,不会发生意外的匹配,这样有利于 SEO 以及用户体验
  • 不关心最后的斜杠
    • URL的末尾是否有/不会造成影响,路由器将会自动地进行重定向
    • 这个功能也可以关掉
  • 路径的自动修正
    • 除了自动对末尾/进行检测以外,路由器还可以修复错误的情况并移除多余的路径元素(如../或者//
    • 可以启用大小写不敏感等功能
  • 路由中的参数
    • 不再解析请求中的URL路径,仅仅只给路径部分进行命名,路由器会提供动态值
  • 无垃圾
    • 匹配和分派的过程中不会产生垃圾。仅有的堆分配是:为路径参数构建的KEY-VALUE切片,以及构建新的context以及请求对象(后者仅在标准的Handler/HandlerFuncAPI中)
  • 最好的性能
  • 不再发生服务器崩溃
    • 可以使用PanicHandler来处理恐慌并从中恢复,并可以记录日志并发送一个错误页面
  • API的最佳选择

    • 本路由器的设计鼓励构建合理的、分层的RESTful的API。此外,它对OPTIONS请求和405 Method Not Allowed的答复提供了内建的支持
    • 也可以自定义NotFoundMethodNotAllowed的handler

      3. 关于http.Handler

      1. // package net/http
      2. type Handler interface {
      3. ServeHTTP(ResponseWriter, *Request)
      4. }
      我认为,Handler这个接口,某种程度上可以直接理解为路由器,ServeHTTP这个方法,就是用于实现转发、处理请求等功能的,可以实现成hello这样的函数,可以实现成继续转发的功能等等。

      使用HttpRouter包

      1. 导入

      1. import (
      2. "github.com/julienschmidt/httprouter"
      3. )

      2. 关于路由中的两种参数

  • named parameters

    • :name,匹配所有在下一个/的内容
    • Path: /blog/:category/:post
      • /blog/go/request-routers match: category=”go”, post=”request-routers”
      • /blog/go/request-routers/ no match, but the router would redirect
      • /blog/go/ no match
      • /blog/go/request-routers/comments no match
  • 全匹配catch-all parameters

    • *name,匹配所有内容;只能用于一整个pattern的最后一个参数
    • Path: /files/*filepath
      • /files/ match: filepath=”/“
      • /files/LICENSE match: filepath=”/LICENSE”
      • /files/templates/article.html match: filepath=”/templates/article.html”
      • /files no match, but the router would redirect

        工作原理

        路由器依赖于基数树(radix tree),这是一个GET请求的路由树例子
        1. Priority Path Handle
        2. 9 \ *<1>
        3. 3 s nil
        4. 2 |├earch\ *<2>
        5. 1 |└upport\ *<3>
        6. 2 blog\ *<4>
        7. 1 | └:post nil //:post只是一个占位符
        8. 1 | \ *<5>
        9. 2 about-us\ *<6>
        10. 1 | team\ *<7>
        11. 1 contact\ *<8>
  • *<num>表示的是其handler方法的内存地址。

  • 与哈希表不同,使用树形结构允许我们使用像:post这样的动态部分。
  • 根据Benchmarks speak for themselves,这么做效率特别高。
  • 由于URL是分层的结构,并且使用的字符集有限,因此存在很多公共前缀的概率是很高的。
  • 路由器为每种请求方法单独管理一棵树(GET、POST等)。这个方法比[在每个节点都保存method->handle 映射更加节省空间],还可以使得我们在查找之前就减少路由问题

  • 为了更好的扩展性,每一层的子节点都按照优先级排序。这里的优先级的含义是,其子孙节点中的handle的数量之和。这样做有如下的好处

    • 按照优先级的顺序进行匹配,每次匹配的线路数量更多,可以更快完成匹配
    • 最长的可达路径始终可以首先评估,从上到下,从左到右评估
      1. ├------------
      2. ├---------
      3. ├-----
      4. ├----
      5. ├--
      6. ├--
      7. └-

      可以与http.Handler一起使用

      路由器实现了http.Handler接口。此外,还为[http.Handler]()[http.HandlerFunc](https://godoc.org/github.com/julienschmidt/httprouter#Router.HandlerFunc)提供了方便的适配器,使得他们可以在注册路由时用作httprouter.Handler

关于Named parameters,在将http.Handler注册成httprouter.Handler之后,就可以通过request.Context获得:

  1. func Hello(w http.ResponseWriter, r *http.Request) {
  2. params := httprouter.ParamsFromContext(r.Context())
  3. fmt.Fprintf(w, "hello, %s!\n", params.ByName("name"))
  4. }

上文说的方式是httprouter提供的辅助函数,也可以通过下面这种方式直接获得

  1. params := r.Context().Value(httprouter.ParamsKey)

自动OPTIONS响应以及CORS

还不了解

关于使用中间件

httprouter包仅仅只是提供了一个高效的路由器,其实这个路由器与http.ServeMux相同,都是实现了http.Handler接口,所以你仍然可以在路由器之前链接任何与http.Handler兼容的中间件。比如,你可以just write your own,或者直接使用基于httprouter的框架

多域/子域

你的服务器是否服务于多个domian/host?是否需要使用sub-domain?为每个主机定义一个路由器即可

  1. // We need an object that implements the http.Handler interface.
  2. // Therefore we need a type for which we implement the ServeHTTP method.
  3. // We just use a map here, in which we map host names (with port) to http.Handlers
  4. type HostSwitch map[string]http.Handler
  5. // Implement the ServeHTTP method on our new type
  6. func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  7. // Check if a http.Handler is registered for the given host.
  8. // If yes, use it to handle the request.
  9. if handler := hs[r.Host]; handler != nil {
  10. handler.ServeHTTP(w, r)
  11. } else {
  12. // Handle host names for which no handler is registered
  13. http.Error(w, "Forbidden", 403) // Or Redirect?
  14. }
  15. }
  16. func main() {
  17. // Initialize a router as usual
  18. router := httprouter.New()
  19. router.GET("/", Index)
  20. router.GET("/hello/:name", Hello)
  21. // Make a new HostSwitch and insert the router (our http handler)
  22. // for example.com and port 12345
  23. hs := make(HostSwitch)
  24. hs["example.com:12345"] = router
  25. // Use the HostSwitch to listen and serve on port 12345
  26. log.Fatal(http.ListenAndServe(":12345", hs))
  27. }
  28. // 新定义了一个路由器,用于转发

基本的认证

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. "github.com/julienschmidt/httprouter"
  7. )
  8. func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {
  9. return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
  10. // Get the Basic Authentication credentials
  11. user, password, hasAuth := r.BasicAuth()
  12. if hasAuth && user == requiredUser && password == requiredPassword {
  13. // Delegate request to the given handle
  14. h(w, r, ps)
  15. } else {
  16. // Request Basic Authentication otherwise
  17. w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
  18. http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
  19. }
  20. }
  21. }
  22. func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  23. fmt.Fprint(w, "Not protected!\n")
  24. }
  25. func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  26. fmt.Fprint(w, "Protected!\n")
  27. }
  28. func main() {
  29. user := "gordon"
  30. pass := "secret!"
  31. router := httprouter.New()
  32. router.GET("/", Index)
  33. router.GET("/protected/", BasicAuth(Protected, user, pass))
  34. log.Fatal(http.ListenAndServe(":8080", router))
  35. }

与NotFound handler链接

可能需要将Router.HandleMethodNotAllowed设为false,以避免发生问题

你可以使用另一个http.Handler(如另一个router),通过其Router.NotFound来处理当前路由器无法匹配的请求。这是可以链接的(我是这么理解的,这段话是在说明Router.NotFound字段的用法)

静态文件

比如,可以用NotFound处理器给/目录提供静态文件

  1. // Serve static files from the ./public directory
  2. router.NotFound = http.FileServer(http.Dir("public"))

但是以上的方法回避了路由器的严格核心规则以避免路由问题。较为干净的方式是使用具体的子路径来提供文件,比如/static/*filepath

基于HttpRouter的框架

  • Ace: Blazing fast Go Web Framework
  • api2go: A JSON API Implementation for Go
  • Gin: Features a martini-like API with much better performance
  • Goat: A minimalistic REST API server in Go
  • goMiddlewareChain: An express.js-like-middleware-chain
  • Hikaru: Supports standalone and Google AppEngine
  • Hitch: Hitch ties httprouter,httpcontext, and middleware up in a bow
  • httpway: Simple middleware extension with context for httprouter and a server with gracefully shutdown support
  • kami: A tiny web framework using x/net/context
  • Medeina: Inspired by Ruby’s Roda and Cuba
  • Neko: A lightweight web application framework for Golang
  • pbgo: pbgo is a mini RPC/REST framework based on Protobuf
  • River: River is a simple and lightweight REST server
  • siesta: Composable HTTP handlers with contexts
  • xmux: xmux is a httprouter fork on top of xhandler (net/context aware)