Go 学习笔记(三十三)HttpRouter 路由

本文原创地址:博客园骏马金龙Go Web:HttpRouter 路由

HttpRouter 是一个轻量级但却非常高效的 multiplexer。

用法示例

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))
}

首先执行:

go get github.com/julienschmidt/httprouter

然后再启动 web 服务:

go run xxx.go

和 http 包的 ServeMux 用法其实很类似。上面定义了两个 httprouter 中的 handle,类似于 http 包中的 http.HandlerFunc 类型,具体的对应关系后文会解释,只要认为它是 handler,是处理对应请求的就行了。然后使用 New()方法创建了实例,并使用 GET() 方法为两个模式注册了对应的 handler。

需要注意的是,第二个模式 "/hello/:name",它可以用来做命名匹配,类似于正则表达式的命名捕获分组。后面会详细解释用法。

httprouter 用法说明

Variables
func CleanPath(p string) string
type Handle
type Param
type Params
func ParamsFromContext(ctx context.Context) Params
func (ps Params) ByName(name string) string
type Router
func New() *Router
func (r *Router) DELETE(path string, handle Handle)
func (r *Router) GET(path string, handle Handle)
func (r *Router) HEAD(path string, handle Handle)
func (r *Router) Handle(method, path string, handle Handle)
func (r *Router) Handler(method, path string, handler http.Handler)
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc)
func (r *Router) Lookup(method, path string) (Handle, Params, bool)
func (r *Router) OPTIONS(path string, handle Handle)
func (r *Router) PATCH(path string, handle Handle)
func (r *Router) POST(path string, handle Handle)
func (r *Router) PUT(path string, handle Handle)
func (r *Router) ServeFiles(path string, root http.FileSystem)
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

type Handle

httprouter 中的 Handle 类似于 http.HandlerFunc,只不过它支持第三个参数 Params。

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).

例如前面示例中的 Index()和 Hello() 都是 Handle 类型的实例。

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"))
}

注册 handler

httprouter.Router 类型类似于 http 包中的 ServeMux,它实现了 http.Handler 接口,所以它是一个 http.Handler。它可以将请求分配给注册好的 handler。

type Router struct {}
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

除此之外,Router 提供了不少方法,用来指示如何为路径注册 handler。

func (r *Router) Handle(method, path string, handle Handle)
func (r *Router) Handler(method, path string, handler http.Handler)
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc)

httprouter.Handle() 用于为路径注册指定的 Handle,而 httprouter.Handle 对应于 http.HandlerFunc,所以是直接将 Handle 类型的函数绑定到指定路径上。同时,它还可以指定 http 方法:GET, POST, HEAD, PUT, PATCH, DELETE, OPTIONS。

这些方法还有对应的各自缩写:

func (r *Router) DELETE(path string, handle Handle)
func (r *Router) GET(path string, handle Handle)
func (r *Router) HEAD(path string, handle Handle)
func (r *Router) OPTIONS(path string, handle Handle)
func (r *Router) PATCH(path string, handle Handle)
func (r *Router) POST(path string, handle Handle)
func (r *Router) PUT(path string, handle Handle)

例如,Get()等价于 route.Handle("GET", path, handle)。

例如上面的示例中,为两个路径注册了各自的 httprouter.Handle 函数。

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

Handler()方法是直接为指定 http 方法和路径注册 http.Handler;HandlerFunc() 方法则是直接为指定 http 方法和路径注册 http.HandlerFunc。

Param 相关

type Param struct {
	Key   string
	Value string
}
Param is a single URL parameter, consisting of a key and a value.

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 (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.

Param 类型是 key/value 型的结构,每个分组捕获到的值都会保存为此类型。正如前面的示例中:

router.GET("/hello/:name", Hello)

这里的:name就是 key,当请求的 URL 路径为/hello/abc,则 key 对应的 value 为 abc。也就是说保存了一个 Param 实例:

Param{
	Key: "name",
	Value: "abc",
}

更多的匹配用法稍后解释。

Params 是 Param 的 slice。也就是说,每个分组捕获到的 key/value 都存放在这个 slice 中。

ByName(str) 方法可以根据 Param 的 Key 检索已经保存在 slice 中的 Param 的 Value。正如示例中:

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

router.GET("/hello/:name", Hello)

这里ByName("name")将检索保存在 slice 中,Key="name" 的 Param,且返回这个 Param 中的 Value。

由于 Params 是 slice 结构,除了 ByName() 方法可以检索 key/value,通过 slice 的方法也可以直接检索:

ps[0].Key
ps[0].Value

路径匹配规则

httprouter 要为路径注册 handler 的适合,路径可以进行命名捕获。有两种命名捕获的方式:

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

其中:name的捕获方式是匹配内容直到下一个斜线或者路径的结尾。例如要为如下路径注册 handler:

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

*name的捕获方式是从指定位置开始 (包含前缀 "/") 匹配到结尾:

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

再解释下什么时候会进行重定向。在 Router 类型中,第一个字段控制尾随斜线的重定向操作:

type Router struct {
	RedirectTrailingSlash bool
	...
}

如果请求的 URL 路径包含或者不包含尾随斜线时,但在注册的路径上包含了或没有包含 "/" 的目标上定义了 handler,但是会进行 301 重定向。简单地说,不管 URL 是否带尾随斜线,只要注册路径不存在,但在去掉尾随斜线或加上尾随斜线的路径上定义了 handler,就会自动重定向。

例如注册路径为/foo,请求路径为/foo/,会重定向。

下面还有几种会重定向的情况:

注册路径:/blog/:category/:post
请求URL路径:/blog/go/request-routers/

注册路径:/blog/:category
请求URL路径:/blog/go

注册路径:/files/*filepath
请求URL路径:/files

Lookup()

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

Lookup 根据 method+path 检索对应的 Handle,以及 Params,并可以通过第三个返回值判断是否会进行重定向。

上一篇 Go 学习笔记(三十二)自带的 ServeMux multiplexer
Go 学习笔记(目录)
下一篇 Go 学习笔记(三十四)处理请求