基于 gorilla/mux 包实现路由定义和请求分发:基本使用

功能简介

前面我们介绍了 Go 官方标准库 net/http 自带的 DefaultServeMux 底层实现,通过 DefaultServeMux 提供的路由处理器虽然简单易上手,但是存在很多不足,比如:

  • 不支持参数设定,例如 /user/:uid 这种泛类型匹配;
  • 对 REST 风格接口支持不友好,无法限制访问路由的方法;
  • 对于拥有很多路由规则的应用,编写大量路由规则非常繁琐。

为此,我们可以使用第三方库 gorilla/mux 提供的更加强大的路由处理器(mux 代表 HTTP request multiplexer,即 HTTP 请求多路复用器),和 http.ServeMux 实现原理一样,gorilla/mux 提供的路由器实现类 mux.Router 也会匹配用户请求与系统注册的路由规则,然后将用户请求转发过去。

mux.Router 主要具备以下特性:

  • 实现了 http.Handler 接口,所以和 http.ServeMux 完全兼容;
  • 可以基于 URL 主机、路径、前缀、scheme、请求头、请求参数、请求方法进行路由匹配;
  • URL 主机、路径、查询字符串支持可选的正则匹配;
  • 支持构建或反转已注册的 URL 主机,以便维护对资源的引用;
  • 支持路由嵌套(类似 Laravel 中的路由分组),以便不同路由可以共享通用条件,比如主机、路径前缀等。

使用入门

开始使用 mux.Router 之前,需要安装这个扩展包:

go get -u github.com/gorilla/mux

然后在我们可以这样实现创建第一个Web应用中演示的注册路由:

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

func sayHelloWorld(w http.ResponseWriter, r *http.Request)  {
    w.WriteHeader(http.StatusOK)  // 设置响应状态码为 200
    fmt.Fprintf(w, "Hello, World!")  // 发送响应到客户端
}

func main()  {
    r := mux.NewRouter()
    r.HandleFunc("/hello", sayHelloWorld)
    log.Fatal(http.ListenAndServe(":8080", r))
}

main 函数中的第一行显式初始化了 mux.Router 作为路由器,然后在这个路由器中注册路由规则,最后将这个路由器传入 http.ListenAndServe 方法,整个调用过程和之前并无二致,因为我们前面说了,mux.Router 也实现了 Handler 接口。

运行这段代码,在浏览器访问 http://localhost:8080/hello,即可渲染出如下结果:

Hello, World!

路由参数

现在,我们想要在路由定义中设置路由参数,例如 /hello/world/hello/学院君,这可以通过如下方式来实现:

r.HandleFunc("/hello/{name}", sayHelloWorld)

你甚至还可以通过正则表达式限制参数字符:

r.HandleFunc("/hello/{name:[a-z]+}", sayHelloWorld)

以上规则表示路由参数只能是小写字母,不支持其它字符。

相应地,在闭包处理函数中,我们需要这样解析路由参数:

func sayHelloWorld(w http.ResponseWriter, r *http.Request)  {
    params := mux.Vars(r)
    w.WriteHeader(http.StatusOK)  // 设置响应状态码为 200
    fmt.Fprintf(w, "Hello, %s!", params["name"])  // 发送响应到客户端
}

重启服务器,这次,我们可以通过 http://localhost:8080/hello/xueyuanjun 这种方式请求路由了:

Hello, xueyuanjun!

如果参数中包含中文,则返回 404 响应,表示路由匹配失败:

-w539

自定义处理器

http.ServeMux 一样,在 mux.Router 中,还可以将请求转发到自定义的处理器类,而不是闭包函数:

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

func sayHelloWorld(w http.ResponseWriter, r *http.Request)  {
    params := mux.Vars(r)
    w.WriteHeader(http.StatusOK)  // 设置响应状态码为 200
    fmt.Fprintf(w, "Hello, %s!", params["name"])  // 发送响应到客户端
}

type HelloWorldHandler struct {}

func (handler *HelloWorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)  {
    params := mux.Vars(r)
    w.WriteHeader(http.StatusOK)  // 设置响应状态码为 200
    fmt.Fprintf(w, "你好, %s!", params["name"])  // 发送响应到客户端
}

func main()  {
    r := mux.NewRouter()
    r.HandleFunc("/hello/{name:[a-z]+}", sayHelloWorld)
    r.Handle("/zh/hello/{name}", &HelloWorldHandler{})
    log.Fatal(http.ListenAndServe(":8080", r))
}

上篇教程介绍的自定义路由处理器一样,这里自定义的 HelloWorldHandler 也要实现 Handler 接口声明的 ServeHTTP 方法,调用方式和之前一样,只是需要通过 r.Handle 方法,因为第二个参数传入的是处理器实例,而不是闭包函数。

重启服务器,我们就可以访问如下 HTTP 路由了:

-w541

以上,就是 gorilla/mux 扩展包提供的路由器 mux.Router 的基本使用,下篇教程,我们继续介绍它的更多路由匹配功能,包括限定主机、请求方法、scheme、路径前缀、请求头、查询字符串等,通过 mux.Router,我们甚至可以构建出比 Laravel 路由还要强大的路由匹配规则。

上一篇: 路由映射和请求分发的底层实现及自定义路由器

下一篇: 基于 gorilla/mux 包实现路由定义和请求分发:进阶使用