go 的路由中間件實現原理


利用go原生的 http 模塊,寫一個簡單的服務,然后實現以下路由中間件

 

一、簡單的中間件實現

package main

import (
	"fmt"
	"net/http"
	"time"
)

func hello(wr http.ResponseWriter, r *http.Request) {
	wr.Write([]byte("hello"))
}

func timeMiddleware(next http.Handler) http.Handler {
	// http.HandlerFunc 將匿名函數強制轉換,為 http.Handler 的接口類型
	return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
		timeStart := time.Now()
		// next handler
		next.ServeHTTP(wr, r)
		timeElapsed := time.Since(timeStart)
		fmt.Println(timeElapsed)
	})
}


func main() {
	// http.HandlerFunc(hello) 類型轉換, 給 hello 函數加上 , ServeHTTP 方法
	helloHandler := http.HandlerFunc(hello);
   // 這里還可以加更多的中間鍵,例如 logger =>  http.Handle("/", logger(timeMiddleware(helloHandler)));
	http.Handle("/", timeMiddleware(helloHandler));
	http.ListenAndServe(":8085", nil)
}

  

以上就是一個簡易版的路由中間件,需要注意以下區別:

http.Handle 和 http.Handler
http.HandleFunc 和 http.HandlerFunc
 
以上寫法的缺點: 不便於維護和修改。嵌套過多,邏輯有點亂。
 
 
二、抽離一個 router 的類。
 
router.go 代碼如下
package router

import (
	"net/http"
)

type middleware func (http.Handler)http.Handler

type Router struct{
	middlewareChain [] middleware
	mux map[string] http.Handler
}

func NewRouter() *Router {
	// middlewareChain 會自動初始化,map 不會自動初始化
	//middlewareChain := new([]middleware); 
	mux := map[string]http.Handler{};
	return &Router{mux:mux};
}

func (r *Router) Use(m middleware)  {
	r.middlewareChain = append(r.middlewareChain,m);
}

func (r *Router) Add(route string, h http.Handler)  {
	var mergedHandle = h;
	for i := len(r.middlewareChain)- 1; i >= 0; i--{
		// 將后面的函數注入到前面的函數中
		mergedHandle = r.middlewareChain[i](mergedHandle);
	}
	r.mux[route] = mergedHandle;
}

func (r *Router) Load ()  {
	// 遍歷將路由加入
	for k,v := range r.mux{
		http.Handle(k,v);
	}
}

  

在 main.go 的使用如下:

package main

import (
	"net/http"
	"fmt"
	"time"
	"test_dev/play/day4/router"
)


func hello(wr http.ResponseWriter, r *http.Request) {
	wr.Write([]byte("hello"))
}

func timeMiddleware(next http.Handler) http.Handler {
	// http.HandlerFunc 將匿名函數強制轉換,為 http.Handler 的接口類型
	return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
		// 進入時間
		timeStart := time.Now()
		// next handler
		next.ServeHTTP(wr, r)
		// 處理結束的時間
		timeElapsed := time.Since(timeStart)
		fmt.Println(timeElapsed)
	})
}



func main()  {
	helloHandler := http.HandlerFunc(hello);
	r := router.NewRouter();

	r.Use(timeMiddleware);
	// r.Use(logger)

	r.Add("/",helloHandler);

	r.Load();
	err := http.ListenAndServe(":8085", nil)

	fmt.Println(err);
}

  

以上寫法,極大的增加中間件的靈活性。 注意:在router.go 的 Add方法里面,是倒過來遍歷的。。。這樣形成一個迭代,當路由觸發的時候,是一個 洋蔥模型  。熟悉node.js的會發現,其實這就是  koa2 的中間件核心思想。

 

以上寫法缺點:中間件不夠簡潔,需要自己包一層做類型轉換。

 

三、將類型轉換移植到 router.go 的類里面

router.go 如下

 

package router

import (
	"net/http"

)





 type addfunc func ( http.ResponseWriter,  *http.Request )

type middleware func (http.ResponseWriter,  *http.Request , http.Handler)



type Router struct{
	middlewareChain [] middleware
	mux map[string] http.Handler
}

func middlewareTrsfromMiddleware(m middleware,next http.Handler) http.Handler {
	return http.HandlerFunc(func ( res http.ResponseWriter, req *http.Request)  {
		m(res,req,next);
	});
}


func NewRouter() *Router {
	mux := map[string]http.Handler{};
	return &Router{mux:mux};
}

func (r *Router) Use(m middleware)  {
	r.middlewareChain = append(r.middlewareChain,m);
}

func (r *Router) Add(route string, h addfunc)  {
	// 必須要指定 mergedHandle 為 http.Handler 類型
	var mergedHandle http.Handler = http.HandlerFunc(h) 
	for i := len(r.middlewareChain)- 1; i >= 0; i--{
		// 將后面的函數注入到前面的函數中
		mergedHandle =  middlewareTrsfromMiddleware(r.middlewareChain[i],mergedHandle);
	}
	r.mux[route] = mergedHandle;
}

func (r *Router) Load ()  {
	// 遍歷將路由加入
	for k,v := range r.mux{
		http.Handle(k,v);
	}
}

 

  

 

main.go 的使用

package main

import (
	"net/http"
	"fmt"
	"time"
	"test_dev/play/day5/router"
)

func hello(res http.ResponseWriter, req *http.Request ) {
	res.Write([]byte("hello"))
}

func timeMiddleware(res http.ResponseWriter, req *http.Request , next http.Handler) {
	// 進入時間
	timeStart := time.Now()
	// next handler
	next.ServeHTTP(res, req)
	// 處理結束的時間
	timeElapsed := time.Since(timeStart)
	fmt.Println(timeElapsed)
} 
func main()  {

	// helloHandler := http.HandlerFunc(hello);

	r := router.NewRouter();

	r.Use(timeMiddleware);
	// r.Use(logger)

	r.Add("/",hello);

	r.Load();

	http.ListenAndServe(":8085", nil)
}

  

最后,臟活類活,都丟給 router.go 了,main里面已經非常簡潔和靈活。

 

擴展:依照現有的 洋蔥模型 可以繼續增加支持 post 、get 等方法的路由,形成一個強大的路由工具。

 

 

補充

 http.Handler : 是一個 interface 

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

  

http.Handle : 是一個方法

func Handle(pattern string, handler http.Handler)

  

 http.HandlerFunc: 是一個類型

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

可以看見,這個類型還有一個方法叫  ServeHTTP , 注意和  http.Handler 區分

 

http.HandleFunc : 是一個方法
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))

  

 

 

 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM