Go 每日一库之 negroni
2020/6/22 14:26:52
本文主要是介绍Go 每日一库之 negroni,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
简介
negroni
是一个专注于 HTTP 中间件的库。它小巧,无侵入,鼓励使用标准库net/http
的处理器(Handler
)。本文就来介绍一下这个库。
为什么要使用中间件?有一些逻辑代码,如统计、日志、调试等,每一个处理器中都需要,如果一个个去添加太繁琐了、容易出错、容易遗漏。如果我们要统计处理器耗时,可以在每个处理器中添加代码统计耗时:
package main import ( "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.Path start := 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 get github.com/urfave/negroni
后使用:
package main import ( "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.ico
negroni.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
接口:
// src/github.com/urfave/negroni/negroni.go type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) func (h HandlerFunc) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { h(rw, r, next) }
net/http
中也有类似的代码,通过http.HandlerFunc
封装func(http.ResponseWriter,*http.Request)
从而实现接口http.Handler
。
negroni.With
如果有多个中间件,每个都需要n.Use()
有些繁琐。negroni
提供了一个With()
方法,它接受一个或多个negroni.Handler
参数,返回一个新的对象:
func Middleware1(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { fmt.Println("Middleware1 begin") next(w, r) fmt.Println("Middleware1 end") } func Middleware2(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { fmt.Println("Middleware2 begin") next(w, r) fmt.Println("Middleware2 end") } func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.New() n = n.With( negroni.HandlerFunc(Middleware1), negroni.HandlerFunc(Middleware2), ) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
Run
Negroni
对象提供了一个方便的Run()
方法来运行服务器程序。它接受与http.ListenAndServe()
一样的地址(Addr
)参数:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.New() n.UseHandler(mux) n.Run(":3000") }
如果未指定端口,那么尝试使用PORT
环境变量。如果PORT
环境变量也未设置,那么使用默认的端口:8080
。
作为http.Handler
使用
negroni
很容易在net/http
程序中使用,negroni.Negroni
对象可直接作为http.Handler
传给相应的方法:
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) s := &http.Server{ Addr: ":8080", Handler: n, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } s.ListenAndServe() }
内置中间件
negroni
内置了一些常用的中间件,可直接使用。
Static
negroni.Static
可在指定目录中提供文件服务:
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.NewStatic(http.Dir("./public"))) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
在程序运行目录下创建public
目录,然后放入一些文件1.txt
,2.jpg
。程序运行之后,就能通过浏览器localhost:3000/1.txt
和localhost:3000/2.jpg
请求这些文件了。
另外需要特别注意一点,如果找不到对应的文件,Static
会将请求传给下一个中间件或处理器函数。在上面的例子中就是hello world
。在浏览器中输入localhost:3000/none-exist.txt
看看效果。
Logger
在快速开始中,我们通过negroni.Classic()
已经使用过这个中间件了。我们也可以单独使用,它可以记录请求的信息。我们还可以调用SetFormat()
方法设置日志的格式:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world") }) n := negroni.New() logger := negroni.NewLogger() logger.SetFormat("[{{.Status}} {{.Duration}}] - {{.Request.UserAgent}}") n.Use(logger) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
上面代码中将日志格式设置为[{{.Status}} {{.Duration}}] - {{.Request.UserAgent}}
,即响应状态、耗时和UserAgent
。
使用 Chrome 浏览器请求:
[negroni] [200 26.0029ms] - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36
Recovery
negroni.Recovery
可以捕获后续的中间件或处理器函数中出现的panic
,返回一个500
的响应码:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() n.Use(negroni.NewRecovery()) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
请求时panic
的堆栈会显示在浏览器中:
这在开发环境比较有用,但是生成环境中不能泄露这个信息。这时可以设置PrintStack
字段为false
:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() r := negroni.NewRecovery() r.PrintStack = false n.Use(r) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
除了在控制台和浏览器中输出panic
信息,Recovery
还提供了钩子函数,可以向其他服务上报panic
,如Sentry/Airbrake
。当然上报的代码要自己写😄。
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() r := negroni.NewRecovery() r.PanicHandlerFunc = reportToSentry n.Use(r) n.UseHandler(mux) http.ListenAndServe(":3000", n) } func reportToSentry(info *negroni.PanicInformation) { fmt.Println("sent to sentry") }
设置PanicHandlerFunc
之后,发生panic
就会调用此函数。
我们还可以对输出的格式进行设置,设置Formatter
字段为negroni.HTMLPanicFormatter
能让输出更好地在浏览器中呈现:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() r := negroni.NewRecovery() r.Formatter = &negroni.HTMLPanicFormatter{} n.Use(r) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
效果:
第三方中间件
除了内置中间件外,negroni
还有很多第三方的中间件。完整列表看这里:https://github.com/urfave/negroni#third-party-middleware。
我们只介绍一个xrequestid
,它在每个请求中增加一个随机的Header
:X-Request-Id
。
安装xrequestid
:
$ go get github.com/pilu/xrequestid
使用:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "X-Request-Id is `%s`", r.Header.Get("X-Request-Id")) }) n := negroni.New() n.Use(xrequestid.New(16)) n.UseHandler(mux) n.Run(":3000") }
给每个请求增加一个 16 字节的X-Request-Id
,处理器函数中将这个X-Request-Id
写入响应中,最后呈现在浏览器中。运行程序,在浏览器中输入localhost:3000
查看效果。
总结
negroni
专注于中间件,没有很多花哨的功能。无侵入性使得它很容易与标准库net/http
和其他的 Web 库(如gorilla/mux
)一起使用。
大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue😄
参考
- negroni GitHub:https://github.com/urfave/negroni
- Go 每日一库 GitHub:https://github.com/darjun/go-daily-lib
我
我的博客:https://darjun.github.io
欢迎关注我的微信公众号【GoUpUp】,共同学习,一起进步~
这篇关于Go 每日一库之 negroni的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-20MongoDB教程:从入门到实践详解
- 2024-11-17执行 Google Ads API 查询后返回的是空数组什么原因?-icode9专业技术文章分享
- 2024-11-17google广告数据不同经理账户下的凭证可以获取对方的api数据吗?-icode9专业技术文章分享
- 2024-11-15SendGrid 的 Go 客户端库怎么实现同时向多个邮箱发送邮件?-icode9专业技术文章分享
- 2024-11-15SendGrid 的 Go 客户端库怎么设置header 和 标签tag 呢?-icode9专业技术文章分享
- 2024-11-12Cargo deny安装指路
- 2024-11-02MongoDB项目实战:从入门到初级应用
- 2024-11-01随时随地一键转录,Google Cloud 新模型 Chirp 2 让语音识别更上一层楼
- 2024-10-25Google Cloud动手实验详解:如何在Cloud Run上开发无服务器应用
- 2024-10-24AI ?先驱齐聚 BAAI 2024,发布大规模语言、多模态、具身、生物计算以及 FlagOpen 2.0 等 AI 模型创新成果。