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

      

     
     
  • 相关阅读:
    simpleDateFormat日期格式转换
    repo总结
    jrtplib使用注意事项
    iOS Crash获取闪回日志和上传server
    Android自己定义组件系列【5】——高级实践(1)
    TimesTen更改CacheGroup管理用户ORACLE结束和TT结束password【TimesTen操作和维修基地】
    Cordova探险系列(三)
    libpomelo 增加编译静态库cocos2d-x xcode 工程
    flex4 一些项目使用的技术
    2015华为德州扑克入境摘要——软体project
  • 原文地址:https://www.cnblogs.com/muamaker/p/12916333.html
Copyright © 2020-2023  润新知