net/http 包中路由
Go 语言标准包 net/http
中路由的实现是基于映射表实现的,在 net/http
包中实现路由的结构体是 ServeMux ,其结构定义如下代码所示:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
该结构体字段 m
变量是一个 map 类型(即 key-value 结构,就是路由表),key 就是路由的路径,value 是一个 muxEntry 对象 ,muxEntry 对象 的结构如下代码所示:
type muxEntry struct {
h Handler
pattern string
该结构体字段 pattern
变量是对应的路径,h
变量就是对应的处理函数,当调用 http.Handle("/", &HomeHandler{})
方法进行路由注册时,实质上是将路径和 HomeHandler 对象 构建成一个 muxEntry 对象 ,然后加入到 ServeMux 的 m
中,具体过程参考如下图所示:
因为路由表是由 map 实现的,所以路由的查找过程就是通过路径从 map 中查找对应的 muxEntry 对象 ,然后获取对应的 handler 。
以上就是 net/http
包中路由的实现,但功能有限(如不能对路由进行分组、不能限定路由的请求方法、不能对路由加中间件等)。
路由实现的示例
实现方式分类
(1) 原生方式
-
调用 http.HandleFunc() 函数;
-
调用 http.ListenAndServe() 方法。
(2) 路由封装重写 ServeHTTP() 方法
-
map[string]http.HandlerFunc
路由存储格式,其中的string
由 method 和传入参数拼接字符串组成; -
map[string]map[string]http.HandlerFunc
路由存储格式,其中一维的键string
表示请求 method ;二维的键string
表示要匹配的 URL 地址,http.HandlerFunc
是处理 URL 请求的具体方法。
原生方式的实现
编写一个 HTTP Server 程序,该程序的具体代码如下:
package main
import (
"fmt"
"net/http"
)
func Process(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "原生 HTTP 路由测试页面")
}
func main() {
http.HandleFunc("/", Process)
log.Fatal(http.ListenAndServe(":8080", nil))
}
原生方式主要是两个步骤:第一步调用 http.HandleFunc() 函数导入对应的方法;第二步调用 http.ListenAndServe() 方法监听对应的端口,启动 HTTP 服务。
路由封装重写 ServeHTTP() 方法的实现
(1)不存储对应路由规则、直接在 ServeHTTP() 方法内判断对应的路由规则方式,直接使用 &结构体 new 一个空对象 的方式来实现,例如以下的程序代码:
// http.go
package main
import (
"fmt"
"net/http"
)
type MyMux struct {
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
Process(w, r)
return
}
http.NotFound(w, r)
return
}
func Process(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello MyMuxRoute!")
}
func main() {
mux := &MyMux{}
http.ListenAndServe(":8080", mux)
}
(2)编写一个 NewMyMux() 方法来创建对应的路由结构体,例如以下的程序代码:
package main
import (
"fmt"
"net/http"
)
type MyMux struct {
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
Process(w, r)
return
}
http.NotFound(w, r)
return
}
func Process(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello MyMuxRoute!")
}
func NewMyMux() *MyMux {
return &MyMux{} // 等同于 return new(MyMux)
}
func main() {
mux := NewMyMux()
http.ListenAndServe(":8080", mux)
}
封装以上的程序代码,将 NewMyMux() 方法和 type MyMux struct
以及 ServeHTTP() 方法封装到一个 router
包里,封装后程序的具体代码如下:
//http.go
package main
import (
"GoHTTP/route"
"net/http"
)
func main() {
mux := route.NewMyMux()
http.ListenAndServe(":8080", mux)
}
在同级目录下创建一个封装路由 route.go
程序,该程序的具体代码如下:
//route 包函数封装
//route.go
package route
import (
"fmt"
"net/http"
)
type MyMux struct {
}
func NewMyMux() *MyMux {
return &MyMux{} // 等同于 return new(MyMux)
}
func Process(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello MyMuxRoute!")
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
Process(w, r)
return
}
http.NotFound(w, r)
return
}
增加一个 MyMux 结构体来存储 http.HandleFunc("/", Process)
路由规则和重写 NewMyMux() 方法,增加路由的多样性和灵活性,修改后的程序代码如下:
// http.go
// 增加 NewMyMux 结构体属性,方便存储 http.HandleFunc("/", Process) 路由规则
package main
import (
"GoHTTP/route"
"net/http"
)
func main() {
mux := route.NewMyMux()
http.ListenAndServe(":8080", mux)
}
修改封装路由 route.go
程序,该程序的具体代码如下所示:
// route 包函数封装
// route.go
package route
import (
"fmt"
"net/http"
)
/*【对比前代码】
type MyMux struct {
}
*/
type MyMux struct {
handlers map[string][]*Handler // 用于存储 http.HandleFunc("/", Process) 格式的路由规则
}
type Handler struct {
path string
http.HandlerFunc
}
/*【对比前代码】
func NewMyMux() *MyMux {
return &MyMux{} // 等同于 return new(MyMux)
}
*/
func NewMyMux() *MyMux {
return &MyMux{make(map[string][]*Handler)}
}
func Process(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello MyMuxRoute!")
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
Process(w, r)
return
}
http.NotFound(w, r)
return
}
继续封装 http.ListenAndServe(":8080", mux)
方法,提升代码的简洁性,修改后的程序代码如下所示:
// 封装 http.ListenAndServe(":8080", mux) 功能
// http.go
package main
import (
"GoHTTP/route"
)
func main() {
mux := route.NewMyMux()
mux.Listen(":8080")
}
修改封装路由 route.go
程序,修改后的程序代码如下所示:
// route.go
package route
import (
"fmt"
"log"
"net/http"
)
type MyMux struct {
handlers map[string][]*Handler // 用于存储http.HandleFunc("/", Process) 格式的路由规则
}
type Handler struct {
path string
http.HandlerFunc
}
// 开启 HTTP 服务
func (m *MyMux) Listen(port string) {
err := http.ListenAndServe(port, m)
if err != nil {
log.Fatal("开启 HTTP 服务失败!")
}
}
func NewMyMux() *MyMux {
return &MyMux{make(map[string][]*Handler)}
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
Process(w, r)
return
}
http.NotFound(w, r)
return
}
func Process(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello MyMuxRoute!")
}
继续封装存储路由功能和重写 ServeHTTP() 方法,增加路由多样性和灵活性,便于存储 Rest 格式接口,修改后的程序代码如下所示:
// http.go
package main
import (
"GoHTTP/route"
"fmt"
"net/http"
)
func main() {
r := route.NewMyMux() /
r.AddRoute("GET", "/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprinln(w, "Hello GET!")
})
// mux.Listen(":8080")
http.ListenAndServe(":8080", r)
}
继续修改封装路由 route.go
程序,修改后的程序代码如下所示:
//route.go
package route
import (
"fmt"
"log"
"net/http"
"strings"
)
type MyMux struct {
handlers map[string][]*Handler // 用于存储 http.HandleFunc("/", Process) 格式的路由规则
}
type Handler struct {
path string
http.HandlerFunc
}
// 开启 HTTP 服务
func (m *MyMux) Listen(port string) {
err := http.ListenAndServe(port, m)
if err != nil {
log.Fatal("开启 HTTP 服务失败!")
}
}
func NewMyMux() *MyMux {
return &MyMux{make(map[string][]*Handler)}
}
// 添加路由
func (m *MyMux) AddRoute(mode string, path string, fun http.HandlerFunc) {
m.add(mode, path, fun)
}
/*mode Post|Get|Put|Delete
*path 前缀
*fun 方法
*/
func (m *MyMux) add(mode, path string, fun http.HandlerFunc) {
h := &Handler{strings.ToLower(path),fun}
fmt.Println("h ::", &h)
fmt.Println("strings.ToLower(path) ::", strings.ToLower(path))
fmt.Println("strings.ToLower(mode) ::", strings.ToLower(mode))
// 路由 m.handlers 存储的格式是 Get|Post|Put|Delete:String:http.HandlerFunc
m.handlers[strings.ToLower(mode)] = append(
m.handlers[strings.ToLower(mode)],
h,
)
fmt.Println("m.handlers", m.handlers[strings.ToLower(mode)])
}
//优化前代码
/*
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
Process(w, r)
return
}
http.NotFound(w, r)
return
}
*/
// 进行路由分配
func (m *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 处理静态文件
url := strings.Split(strings.TrimLeft(r.URL.Path, "/"), "/")
// 调试代码
fmt.Println("r", fmt.Sprintf("%+v", r))
fmt.Println("w", fmt.Sprintf("%+v", w))
for _, handler := range m.handlers[strings.ToLower(r.Method)] {
if handler.path == "/"+strings.ToLower(url[0]) {
handler.ServeHTTP(w, r) // 调用的是 func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
return
}
}
http.NotFound(w, r)
return
}