go 的路由中间件实现原理
Posted muamaker
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 等方法的路由,形成一个强大的路由工具。
以上是关于go 的路由中间件实现原理的主要内容,如果未能解决你的问题,请参考以下文章
Express实战 - 应用案例- realworld-API - 路由设计 - mongoose - 数据验证 - 密码加密 - 登录接口 - 身份认证 - token - 增删改查API(代码片段