httprouter文档

HttpRouter

原文:https://pkg.go.dev/github.com/julienschmidt/httprouter

​ HttpRouter 是一个轻量级高性能的 Go 语言 HTTP 请求路由器(也称为多路复用器multiplexer或简称 mux)。

​ 与 Go 标准库中的 net/http 包中的默认 mux 不同,该路由器支持路由模式中的变量,并匹配请求方法。它也具有更好的可扩展性。

​ 该路由器针对高性能和小内存占用进行了优化。即使在非常长的路径和大量路由的情况下,它也能良好地扩展。使用压缩动态 trie(基数树 radix tree)结构进行高效匹配。

特性

​ 只有显式匹配:在其他路由器(如 http.ServeMux)中,请求的 URL 路径可以匹配多个模式。因此它们有一些尴尬的模式优先级规则,比如最长匹配或首先注册,首先匹配。通过这个路由器的设计,一个请求只能精确地匹配一个或零个路由。结果,也没有意外的匹配,这使得它非常适合 SEO 并改善了用户体验。

​ 不再关心尾部斜杠:选择您喜欢的URL样式,如果缺少尾部斜杠或多了一个,路由器会自动重定向客户端。当然,只有当新路径有处理程序时,它才会这样做。如果您不喜欢,可以关闭此行为

​ 路径自动纠正:除了在不增加额外成本的情况下检测缺少或附加的尾随斜杠外,路由器还可以修复错误的大小写和删除多余的路径元素(如 ..///)。你的用户中有 CAPTAIN CAPS LOCK 吗?HttpRouter 可以通过进行大小写不敏感的查找并将其重定向到正确的 URL 来帮助他。

​ 在路由模式中使用参数:停止解析请求的 URL 路径,只需给路径段一个名称,路由器就会将动态值传递给您。由于路由器的设计,路径参数非常便宜。

​ 零垃圾:匹配和调度过程不会产生任何垃圾。仅进行堆分配以构建路径参数的键值对切片和构建新的上下文和请求对象(仅在标准的 Handler/HandlerFunc API 中)。在三参数 API 中,如果请求路径不包含参数,则不需要进行任何堆分配。

​ 最佳性能:基准测试说明一切。有关实现的技术细节,请参见下面。

​ 不再有服务器崩溃的问题:您可以设置一个Panic处理程序来处理在处理HTTP请求时发生的panic。路由器会恢复并让PanicHandler记录发生的情况并提供一个漂亮的错误页面。

​ 完美适用于API:该路由器设计鼓励构建合理的分层RESTful API。此外,它还具有内置的OPTIONS请求405 Method Not Allowed响应的本机支持。

​ 当然,您也可以设置自定义的NotFoundMethodNotAllowed处理程序并提供静态文件。

用法

​ 这只是一个简单的介绍,请查看文档以获取详细信息。

​ 让我们从一个微不足道的例子开始:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
    "fmt"
    "net/http"
    "log"

    "github.com/julienschmidt/httprouter"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
    router := httprouter.New()
    router.GET("/", Index)
    router.GET("/hello/:name", Hello)

    log.Fatal(http.ListenAndServe(":8080", router))
}

命名参数

​ 正如您所看到的,:name是一个命名参数。值可以通过httprouter.Params访问,它只是httprouter.Params的切片。您可以通过在切片中使用其索引或使用ByName(name)方法来获取参数的值::name可以通过ByName("name")检索。

​ 当使用http.Handler(使用router.Handlerhttp.HandlerFunc)而不是使用第三个函数参数的HttpRouter的处理API时,命名参数存储在request.Context中。在下面的"为什么它不能与http.Handler一起工作?“下面查看更多信息。

​ 命名参数仅匹配单个路径段:

Pattern: /user/:user

 /user/gordon              match
 /user/you                 match
 /user/gordon/profile      no match
 /user/                    no match

注意:由于此路由器仅具有显式匹配,因此您无法在相同的路径段上同时为相同的请求方法注册静态路由和参数。例如,您不能同时为/user/new/user/:user注册模式,以进行相同的请求方法。不同请求方法的路由与彼此独立。

捕获所有参数

​ 第二种类型是捕获所有参数,格式为*name。正如名称所示,它们匹配所有内容。因此,它们必须始终位于模式的末尾:

Pattern: /src/*filepath

 /src/                     match
 /src/somefile.go          match
 /src/subdir/somefile.go   match

它是如何工作的?

​ 路由器依赖于树形结构,该结构大量使用公共前缀,基本上是一棵紧凑的前缀树(或者称作Radix树)。具有公共前缀的节点也共享一个共同的父节点。以下是GET请求方法的路由树的简短示例:

Priority   Path             Handle
9          \                *<1>
3          ├s               nil
2          |├earch\         *<2>
1          |└upport\        *<3>
2          ├blog\           *<4>
1          |    └:post      nil
1          |         └\     *<5>
2          ├about-us\       *<6>
1          |        └team\  *<7>
1          └contact\        *<8>

​ 每个 *<num> 表示一个处理程序函数(指针)的内存地址。如果您从根节点到叶子节点沿着树的路径走,您将获得完整的路由路径,例如 \blog:post\,其中 :post 只是一个占位符(参数),用于实际的帖子名称。与哈希映射不同,树形结构也允许我们使用 :post 参数等动态部分,因为我们实际上是针对路由模式进行匹配,而不仅仅是比较哈希值。正如基准测试所显示的那样,这非常有效和高效。

​ 由于 URL 路径具有层次结构并且只使用有限的字符集(字节值),很可能存在许多公共前缀。这使我们可以轻松地将路由缩小为越来越小的问题。此外,路由器为每个请求方法管理一个单独的树。一方面,它比在每个单独节点中保存方法->处理程序映射更节省空间,另一方面它还允许我们在甚至开始查找前缀树之前大大减少路由问题。

​ 为了更好的可扩展性,每个树级别上的子节点按优先级排序,其中优先级仅为在子节点(孩子、孙子等)中注册的处理程序数量。这种方法有两个帮助:

  1. 首先,先评估最多的路由路径节点,这有助于使尽可能多的路由尽快到达。
  2. 这是某种成本补偿。最长可达路径(最高成本)总是可以首先进行评估。下面的图表显示了树形结构。节点从上到下,从左到右进行评估。
├------------
├---------
├-----
├----
├--
├--
└-

为什么它不能与http.Handler一起工作?

​ 它可以!路由器本身实现了http.Handler接口。此外,路由器提供了方便的适配器,使得当注册路由时,可以将它们用作httprouter.Handle来使用http.Handlershttp.HandlerFuncs

​ 命名参数可以通过request.Context访问:

1
2
3
4
5
func Hello(w http.ResponseWriter, r *http.Request) {
    params := httprouter.ParamsFromContext(r.Context())

    fmt.Fprintf(w, "hello, %s!\n", params.ByName("name"))
}

​ 或者,也可以使用params := r.Context().Value(httprouter.ParamsKey)而不是使用helper函数。

​ 尝试自己使用它,使用HttpRouter非常简单。这个包是紧凑而简约的,但也可能是最容易设置的路由器之一。

自动OPTIONS响应和CORS

​ 有人可能希望修改自动响应OPTIONS请求的方式,例如支持CORS预检请求或设置其他标头。可以使用Router.GlobalOPTIONS处理程序来实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if r.Header.Get("Access-Control-Request-Method") != "" {
        // 设置CORS标头
        header := w.Header()
        header.Set("Access-Control-Allow-Methods", header.Get("Allow"))
        header.Set("Access-Control-Allow-Origin", "*")
    }

    // 调整状态码为204
    w.WriteHeader(http.StatusNoContent)
})

我在哪里可以找到中间件X?

​ 此包只提供一个非常高效的请求路由器和一些额外的功能。该路由器只是一个http.Handler,您可以在路由器之前链接任何http.Handler兼容的中间件,例如Gorilla处理程序。或者你可以自己编写,这非常容易!

​ 或者,您可以尝试基于HttpRouter的Web框架。

多域/子域

​ 以下是一个快速的例子:您的服务器是否提供多个域名/主机?您想使用子域吗?为每个主机定义一个路由器!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 我们需要一个实现http.Handler接口的对象。
// 因此,我们需要一种类型,在其中实现ServeHTTP方法。
// 我们只是在此处使用一个映射,其中我们将主机名(带端口)
// 映射到http.Handlers
type HostSwitch map[string]http.Handler

//在我们的新类型上实现ServeHTTP方法
func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	//检查是否为给定主机注册了http.Handler。
	//如果是,使用它来处理请求。
	if handler := hs[r.Host]; handler != nil {
		handler.ServeHTTP(w, r)
	} else {
		//处理未注册处理程序的主机名
		http.Error(w, "Forbidden", 403) //还是重定向?
	}
}

func main() {
	//像往常一样初始化路由器
	router := httprouter.New()
	router.GET("/", Index)
	router.GET("/hello/:name", Hello)

	//创建一个HostSwitch并插入路由器(我们的http处理程序)
	//例如.com和端口12345
	hs := make(HostSwitch)
	hs["example.com:12345"] = router

	//使用HostSwitch在端口12345上进行监听和服务
	log.Fatal(http.ListenAndServe(":12345", hs))
}

基本身份验证

​ 另一个快速的例子:处理基本身份验证(RFC 2617):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		// Get the Basic Authentication credentials
        // 获取基本身份验证凭据
		user, password, hasAuth := r.BasicAuth()

		if hasAuth && user == requiredUser && password == requiredPassword {
			// 将请求委托给给定的处理程序
			h(w, r, ps)
		} else {
			// 否则请求基本身份验证
			w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		}
	}
}

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "Not protected!\n")
}

func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "Protected!\n")
}

func main() {
	user := "gordon"
	pass := "secret!"

	router := httprouter.New()
	router.GET("/", Index)
	router.GET("/protected/", BasicAuth(Protected, user, pass))

	log.Fatal(http.ListenAndServe(":8080", router))
}

使用NotFound处理程序进行链接

注意:可能需要将Router.HandleMethodNotAllowed设置为false以避免出现问题。

​ 您可以使用另一个http.Handler(例如另一个路由器)来处理无法由此路由器匹配的请求,方法是使用Router.NotFound处理程序进行链接。这样可以进行链接。

静态文件

NotFound处理程序可以例如用于从根路径/(如index.html文件以及其他资源)提供静态文件:

// Serve static files from the ./public directory
// 从./public目录提供静态文件
router.NotFound = http.FileServer(http.Dir("public"))

​ 但是,此方法规避了此路由器的严格核心规则以避免路由问题。更干净的方法是使用一个不同的子路径来提供文件,例如/static/*filepath/files/*filepath

基于HttpRouter的Web框架

​ 如果HttpRouter对您来说有点过于简单,您可以尝试以下更高级的第三方Web框架,这些框架是基于HttpRouter包构建的:

  • Ace:极速的 Go Web 框架
  • api2go:用于 Go 的 JSON API 实现
  • Gin:具有类似 Martini 的 API 和更好的性能
  • Goat:Go 中极简的 REST API 服务器
  • goMiddlewareChain:类似于 Express.js 的中间件链
  • Hikaru:支持独立和 Google AppEngine
  • Hitch:将 httprouter、httpcontext和中间件捆绑在一起
  • httpway:支持具有上下文的中间件扩展的 httprouter 和具有优雅关闭支持的服务器
  • kami:使用 x/net/context 的微型 Web 框架
  • Medeina:受 Ruby 的 Roda 和 Cuba 启发
  • Neko:用于 Golang 的轻量级 Web 应用框架
  • pbgo:基于 Protobuf 的 mini RPC/REST 框架
  • River:River 是一个简单轻量级的 REST 服务器
  • siesta:具有上下文的可组合 HTTP 处理程序
  • xmux:xmux 是 httprouter 在 xhandler(net/context 意识)之上的 fork

Documentation

Overview

Package httprouter is a trie based high performance HTTP request router.

A trivial example is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
    "fmt"
    "github.com/julienschmidt/httprouter"
    "net/http"
    "log"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
    router := httprouter.New()
    router.GET("/", Index)
    router.GET("/hello/:name", Hello)

    log.Fatal(http.ListenAndServe(":8080", router))
}

The router matches incoming requests by the request method and the path. If a handle is registered for this path and method, the router delegates the request to that function. For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to register handles, for all other methods router.Handle can be used.

The registered path, against which the router matches incoming requests, can contain two types of parameters:

Syntax    Type
:name     named parameter
*name     catch-all parameter

Named parameters are dynamic path segments. They match anything until the next ‘/’ or the path end:

Path: /blog/:category/:post

Requests:
 /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 match anything until the path end, including the directory index (the ‘/’ before the catch-all). Since they match anything until the end, catch-all parameters must always be the final path element.

Path: /files/*filepath

Requests:
 /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

The value of parameters is saved as a slice of the Param struct, consisting each of a key and a value. The slice is passed to the Handle func as a third parameter. There are two ways to retrieve the value of a parameter:

// by the name of the parameter
user := ps.ByName("user") // defined by :user or *user

// by the index of the parameter. This way you can also get the name (key)
thirdKey   := ps[2].Key   // the name of the 3rd parameter
thirdValue := ps[2].Value // the value of the 3rd parameter

Index

Constants

This section is empty.

Variables

View Source

var ParamsKey = paramsKey{}

ParamsKey is the request context key under which URL params are stored.

Functions

func CleanPath

1
func CleanPath(p string) string

CleanPath is the URL version of path.Clean, it returns a canonical URL path for p, eliminating . and .. elements.

The following rules are applied iteratively until no further processing can be done:

  1. Replace multiple slashes with a single slash.
  2. Eliminate each . path name element (the current directory).
  3. Eliminate each inner .. path name element (the parent directory) along with the non-.. element that precedes it.
  4. Eliminate .. elements that begin a rooted path: that is, replace “/..” by “/” at the beginning of a path.

If the result of this process is an empty string, “/” is returned

Types

type Handle

1
type Handle func(http.ResponseWriter, *http.Request, Params)

Handle is a function that can be registered to a route to handle HTTP requests. Like http.HandlerFunc, but has a third parameter for the values of wildcards (variables).

type Param <- 1.1.0

1
2
3
4
type Param struct {
	Key   string
	Value string
}

Param is a single URL parameter, consisting of a key and a value.

type Params <- 1.1.0

1
type Params []Param

Params is a Param-slice, as returned by the router. The slice is ordered, the first URL parameter is also the first slice value. It is therefore safe to read values by the index.

func ParamsFromContext <- 1.2.0

1
func ParamsFromContext(ctx context.Context) Params

ParamsFromContext pulls the URL parameters from a request context, or returns nil if none are present.

func (Params) ByName <- 1.1.0

1
func (ps Params) ByName(name string) string

ByName returns the value of the first Param which key matches the given name. If no matching Param is found, an empty string is returned.

type Router

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
type Router struct {

	// Enables automatic redirection if the current route can't be matched but a
	// handler for the path with (without) the trailing slash exists.
	// For example if /foo/ is requested but a route only exists for /foo, the
	// client is redirected to /foo with http status code 301 for GET requests
	// and 307 for all other request methods.
	RedirectTrailingSlash bool

	// If enabled, the router tries to fix the current request path, if no
	// handle is registered for it.
	// First superfluous path elements like ../ or // are removed.
	// Afterwards the router does a case-insensitive lookup of the cleaned path.
	// If a handle can be found for this route, the router makes a redirection
	// to the corrected path with status code 301 for GET requests and 307 for
	// all other request methods.
	// For example /FOO and /..//Foo could be redirected to /foo.
	// RedirectTrailingSlash is independent of this option.
	RedirectFixedPath bool

	// If enabled, the router checks if another method is allowed for the
	// current route, if the current request can not be routed.
	// If this is the case, the request is answered with 'Method Not Allowed'
	// and HTTP status code 405.
	// If no other Method is allowed, the request is delegated to the NotFound
	// handler.
	HandleMethodNotAllowed bool

	// If enabled, the router automatically replies to OPTIONS requests.
	// Custom OPTIONS handlers take priority over automatic replies.
	HandleOPTIONS bool

	// An optional http.Handler that is called on automatic OPTIONS requests.
	// The handler is only called if HandleOPTIONS is true and no OPTIONS
	// handler for the specific path was set.
	// The "Allowed" header is set before calling the handler.
	GlobalOPTIONS http.Handler

	// Configurable http.Handler which is called when no matching route is
	// found. If it is not set, http.NotFound is used.
	NotFound http.Handler

	// Configurable http.Handler which is called when a request
	// cannot be routed and HandleMethodNotAllowed is true.
	// If it is not set, http.Error with http.StatusMethodNotAllowed is used.
	// The "Allow" header with allowed request methods is set before the handler
	// is called.
	MethodNotAllowed http.Handler

	// Function to handle panics recovered from http handlers.
	// It should be used to generate a error page and return the http error code
	// 500 (Internal Server Error).
	// The handler can be used to keep your server from crashing because of
	// unrecovered panics.
	PanicHandler func(http.ResponseWriter, *http.Request, interface{})
	// contains filtered or unexported fields
}

Router is a http.Handler which can be used to dispatch requests to different handler functions via configurable routes

func New

1
func New() *Router

New returns a new initialized Router. Path auto-correction, including trailing slashes, is enabled by default.

(*Router) DELETE

1
func (r *Router) DELETE(path string, handle Handle)

DELETE is a shortcut for router.Handle(http.MethodDelete, path, handle)

(*Router) GET

1
func (r *Router) GET(path string, handle Handle)

GET is a shortcut for router.Handle(http.MethodGet, path, handle)

(*Router) HEAD <- 1.1.0

1
func (r *Router) HEAD(path string, handle Handle)

HEAD is a shortcut for router.Handle(http.MethodHead, path, handle)

(*Router) Handle

1
func (r *Router) Handle(method, path string, handle Handle)

Handle registers a new request handle with the given path and method.

For GET, POST, PUT, PATCH and DELETE requests the respective shortcut functions can be used.

This function is intended for bulk loading and to allow the usage of less frequently used, non-standardized or custom methods (e.g. for internal communication with a proxy).

(*Router) Handler <- 1.1.0

1
func (r *Router) Handler(method, path string, handler http.Handler)

Handler is an adapter which allows the usage of an http.Handler as a request handle. The Params are available in the request context under ParamsKey.

(*Router) HandlerFunc

1
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc)

HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a request handle.

(*Router) Lookup <- 1.1.0

1
func (r *Router) Lookup(method, path string) (Handle, Params, bool)

Lookup allows the manual lookup of a method + path combo. This is e.g. useful to build a framework around this router. If the path was found, it returns the handle function and the path parameter values. Otherwise the third return value indicates whether a redirection to the same path with an extra / without the trailing slash should be performed.

(*Router) OPTIONS <- 1.1.0

1
func (r *Router) OPTIONS(path string, handle Handle)

OPTIONS is a shortcut for router.Handle(http.MethodOptions, path, handle)

(*Router) PATCH

1
func (r *Router) PATCH(path string, handle Handle)

PATCH is a shortcut for router.Handle(http.MethodPatch, path, handle)

(*Router) POST

1
func (r *Router) POST(path string, handle Handle)

POST is a shortcut for router.Handle(http.MethodPost, path, handle)

(*Router) PUT

1
func (r *Router) PUT(path string, handle Handle)

PUT is a shortcut for router.Handle(http.MethodPut, path, handle)

(*Router) ServeFiles

1
func (r *Router) ServeFiles(path string, root http.FileSystem)

ServeFiles serves files from the given file system root. The path must end with “/*filepath”, files are then served from the local path /defined/root/dir/*filepath. For example if root is “/etc” and *filepath is “passwd”, the local file “/etc/passwd” would be served. Internally a http.FileServer is used, therefore http.NotFound is used instead of the Router’s NotFound handler. To use the operating system’s file system implementation, use http.Dir:

router.ServeFiles("/src/*filepath", http.Dir("/var/www"))

(*Router) ServeHTTP

1
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP makes the router implement the http.Handler interface.

最后修改 June 5, 2023: 更新标准库 (33f199b)