网站首页 > 技术教程 正文
简介
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
猜你喜欢
- 2024-09-25 工具推荐:dismap 快速资产发现和识别工具
- 2024-09-25 谷歌优化Chrome全局媒体控件 调整专辑封面尺寸
- 2024-09-25 分享几个优质的油猴脚本 油猴脚本推荐排行2020
- 2024-09-25 电商平台被入侵,黑客通过图片盗取电商平台用户隐私信息
- 2024-09-25 网站地址前的小图标怎么添加 网站后面加地址后缀
- 2024-09-25 秒开WebView?Android性能优化全攻略
- 2024-09-25 【Python程序开发系列】介绍一款轻量级高自由度web框架-NiceGUI
- 2024-09-25 Zotero文献管理 | 添加文献检索引擎(附下载)
- 2024-09-25 这5个好玩又实用的在线工具,你还不收藏吗?
- 2024-09-25 饿了么更新品牌色,从深蓝变浅蓝 饿了么色彩搭配分析
你 发表评论:
欢迎- 05-1613步震撼淘宝大促闪光裂纹破墙立体字PS制作教程
- 05-16AI教程 | 绘制扁平的萌萌哒图标
- 05-160基础学平面设计所需了解的基础常识汇总
- 05-16自学平面设计需要多长时间?十六年职业设计总监告诉你
- 05-16平面设计都要学习哪些内容?
- 05-16李涛PS教程 高手之路PS教程 合成教程 —制作一个小星球
- 05-16Illustrator实例教程:制作炫酷的漩涡效果
- 05-16Illustrator实例教程:利用混合工具制作一朵炫酷的花
- 最近发表
- 标签列表
-
- sd分区 (65)
- raid5数据恢复 (81)
- 地址转换 (73)
- 手机存储卡根目录 (55)
- tcp端口 (74)
- project server (59)
- 双击ctrl (55)
- 鼠标 单击变双击 (67)
- debugview (59)
- 字符动画 (65)
- flushdns (57)
- ps复制快捷键 (57)
- 清除系统垃圾代码 (58)
- web服务器的架设 (67)
- 16进制转换 (69)
- xclient (55)
- ps源文件 (67)
- filezilla server (59)
- 句柄无效 (56)
- word页眉页脚设置 (59)
- ansys实例 (56)
- 6 1 3固件 (59)
- sqlserver2000挂起 (59)
- vm虚拟主机 (55)
- config (61)
本文暂时没有评论,来添加一个吧(●'◡'●)