net/http 包路由的实现


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 对象 ,然后加入到 ServeMuxm 中,具体过程参考如下图所示:

因为路由表是由 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
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

물の韜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值