Go 每日一库之 negroni

简介
negroni是一个专注于 HTTP 中间件的库 。 它小巧 , 无侵入 , 鼓励使用标准库net/http的处理器(Handler) 。 本文就来介绍一下这个库 。
为什么要使用中间件?有一些逻辑代码 , 如统计、日志、调试等 , 每一个处理器中都需要 , 如果一个个去添加太繁琐了、容易出错、容易遗漏 。 如果我们要统计处理器耗时 , 可以在每个处理器中添加代码统计耗时:
package mainimport ("fmt""net/http""time")func index(w http.ResponseWriter, r *http.Request) {start := time.Now()fmt.Fprintf(w, "home page")fmt.Printf("index elasped:%fs", time.Since(start).Seconds())}func greeting(w http.ResponseWriter, r *http.Request) {start := time.Now()name := r.FormValue("name")if name == "" {name = "world"}fmt.Fprintf(w, "hello %s", name)fmt.Printf("greeting elasped:%fs\n", time.Since(start).Seconds())}func main() {mux := http.NewServeMux()mux.HandleFunc("/", index)mux.HandleFunc("/greeting", greeting)http.ListenAndServe(":8000", mux)}但是这个做法非常不灵活:

  • 每增加一个处理器 , 都需要添加这部分代码 。 而这些代码与实际的处理器逻辑并没有什么关系 。 编写处理器时比较容易遗忘 , 特别是要考虑所有的返回路径 。 增加了编码负担;
  • 不利于修改:如果统计代码有错误或者需要调整 , 必须要改动所有的处理器;
  • 添加麻烦:要添加其他的统计逻辑也需要改动所有的处理器代码 。
利用 Go 语言的闭包 , 我们可以将实际的处理器代码封装到一个函数中 , 在这个函数中执行额外的逻辑:
func elasped(h func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {path := r.URL.Pathstart := time.Now()h(w, r)fmt.Printf("path:%s elasped:%fs\n", path, time.Since(start).Seconds())}}func index(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "home page")}func greeting(w http.ResponseWriter, r *http.Request) {name := r.FormValue("name")if name == "" {name = "world"}fmt.Fprintf(w, "hello %s", name)}func main() {mux := http.NewServeMux()mux.HandleFunc("/", elasped(index))mux.HandleFunc("/greeting", elasped(greeting))http.ListenAndServe(":8000", mux)}我们将额外的与处理器无关的代码放在另外的函数中 。 注册处理器函数时 , 我们不直接使用原始的处理器函数 , 而是用elasped函数封装一层 。 实际上elasped这样的函数就是中间件 。 它封装原始的处理器函数 , 返回一个新的处理器函数 。 从而能很方便在实际的处理逻辑前后插入代码 , 便于添加、修改和维护 。
快速使用【Go 每日一库之 negroni】先安装:
$ go get github.com/urfave/negroni后使用:
package mainimport ("fmt""net/http""github.com/urfave/negroni")func main() {mux := http.NewServeMux()mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello World!")})n := negroni.Classic()n.UseHandler(mux)http.ListenAndServe(":3000", n)}negroni的使用非常简单 , 它可以很方便的与http.Handler一起使用 。 negroni.Classic()提供了几个常用的中间件:
  • negroni.Recovery:恢复panic , 处理器代码中有panic会被这个中间件捕获 , 程序不会退出;
  • negroni.Logger:日志 , 记录请求和响应的基本信息;
  • negroni.Static:在public目录提供静态文件服务 。
调用n.UseHandler(mux) , 将这些中间件应用到多路复用器上 。 运行 , 在浏览器中输入localhost:3000 , 查看控制台输出:
$ go run main.go[negroni] 2020-06-22T06:48:53+08:00 | 200 |20.9966ms | localhost:3000 | GET /[negroni] 2020-06-22T06:48:54+08:00 | 200 |0s | localhost:3000 | GET /favicon.iconegroni.Handler接口negroni.Handler让我们对中间件的执行流程有更灵活的控制:
type Handler interface {ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)}我们编写的中间件签名必须是func(http.ResponseWriter,*http.Request,http.HandlerFunc) , 或者实现negroni.Handler接口:
func RandomMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {if rand.Int31n(100) <= 50 {fmt.Fprintf(w, "hello from RandomMiddleware")} else {next(w, r)}}func main() {mux := http.NewServeMux()mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello World!")})n := negroni.New()n.Use(negroni.HandlerFunc(RandomMiddleware))n.UseHandler(mux)http.ListenAndServe(":3000", n)}上面代码中实现了一个随机的中间件 , 有一半的概率直接从RandomMiddleware这个中间件返回 , 一半的概率执行实际的处理器函数 。 运行程序 , 在浏览器中不停地刷新页面localhost:3000看看效果 。
注意 , 实际上func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)只是一个方便的写法 。 在调用n.Use时使用了negroni.HandlerFunc做了一层封装 , 而negroni.HandlerFunc实现了negroni.Handler接口: