利用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))
