中间件学习

中间件

参考文档:Go语言高级编程

中间件就是非业务的技术类组件,常常使用中间件剥离非业务逻辑。

如下是一个简单的web服务,挂载一个简单的路由。

package main

import (
	"fmt"
	"net/http"
)

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

func main() {
	http.Handle("/", http.HandlerFunc(hello))
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("err:", err)
	}
}

若有一个需求需要统计hello方法执行耗时,最简单的方法即为修改hello方法,在其中加入耗时统计。

func hello(wr http.ResponseWriter, r *http.Request) {
    timeStart := time.Now()
    wr.Write([]byte("hello"))
    timeElapsed := time.Since(timeStart)
    fmt.Println("use time:", timeElapsed)
}

但假如有更多的方法需要加入耗时统计功能,则需要每个方法都需适配。这样导致每个方法都包含一段与业务无关的代码,且后续的维护成本也更高。例如,我们需要将每个接口运行耗时上报的监控系统,那么我们就需要把每个方法的代码都修改一遍。

上述,我们犯了一个最大错误,就是把业务代码和非业务代码揉在了一起,且有很多重复代码。为此,我们可以使用中间件剥离非业务逻辑。

package main

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

func hello(wr http.ResponseWriter, r *http.Request) {
	fmt.Println("\nwrite response hello...\n")
	wr.Write([]byte("hello"))
}

func timeMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
		fmt.Println("time middleware start...")
		timeStart := time.Now()

		// next handler
		next.ServeHTTP(wr, r)

		timeElapsed := time.Since(timeStart)
		fmt.Println("time middleware end, use time:", timeElapsed)
	})
}

func main() {
	http.Handle("/", timeMiddleware(http.HandlerFunc(hello)))
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("err:", err)
	}
}

启动server后请求,输出结果:

time middleware start...

write response hello...

time middleware end, use time: 0s

这样就非常轻松地实现了业务与非业务之间的剥离,魔法就在于这个timeMiddleware.

优雅实现中间件

上一节中解决了业务功能代码和非业务功能代码的解耦,但也提到了,看起来并不美观,如果需要修改这些函数的顺序,或者增删中间件还是有点费劲,本节我们来进行一些“写法”上的优化。

看一个例子:

r := NewRouter()
r.Use(timeMiddleware)
r.Use(helloMiddleware)
r.Add("/", http.HandlerFunc(hello))

通过多步设置,我们拥有了和上一节差不多的执行函数链。胜在直观易懂,如果我们要增加或者删除中间件,只要简单地增加删除对应的Use()调用就可以了。非常方便。

从框架的角度来讲,怎么实现这样的功能呢?也不复杂:

package main

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

func hello(wr http.ResponseWriter, r *http.Request) {
	fmt.Println("\nwrite response hello...\n")
	wr.Write([]byte("hello"))
}

func timeMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
		fmt.Println("time middleware start...")
		timeStart := time.Now()

		// next handler
		next.ServeHTTP(wr, r)

		timeElapsed := time.Since(timeStart)
		fmt.Println("time middleware end, use time:", timeElapsed)
	})
}

func helloMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
		fmt.Println("hello middelware begin!")

		// next handler
		next.ServeHTTP(wr, r)

		fmt.Println("hello middleware end !")
	})
}

type middleware func(http.Handler) http.Handler

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

func NewRouter() *Router {
	m := make(map[string]http.Handler, 10)
	newR := &Router{
		mux: m,
	}
	return newR
}

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

func (r *Router) Add(route string, h http.Handler) {
	var mergedHandler = h

	for i := len(r.middlewareChain) - 1; i >= 0; i-- {
		mergedHandler = r.middlewareChain[i](mergedHandler)
	}
	r.mux[route] = mergedHandler
}

func main() {
	// 增加中间件
	r := NewRouter()
	r.Use(timeMiddleware)
	r.Use(helloMiddleware)
	r.Add("/", http.HandlerFunc(hello))

	http.Handle("/", r.mux["/"])
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("err:", err)
	}
}

r.mux["/"]可以理解为:

timeMiddleWare(helloMiddelWare(hello))

遂执行顺序为:

timeMiddleWare(helloMiddelWare)               END!
            |                                  |
            | echo:time middleware start      < echo:time middleware end...
            V                                  |
   helloMiddelWare(hello)                      |
            |                                  |
            | echo:hello middelware begin!    < echo:hello middleware end ! 
            V                                  |
         hello()                               |
            |                                  |       
            |echo:write response hello...      |        
            |                                  |
            ------------------»----------------

理清楚执行顺序后,可以看看实际程序输出是否与我们预想相同:

time middleware start...
hello middelware begin!

write response hello...

hello middleware end !
time middleware end, use time: 976.5µs
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值