Negroni
注意: 本函数库原本位于github.com/codegangsta/negroni
-- Github 会自动将请求重定向到当前地址, 但我们建议你更新一下引用路径。
在 Go 语言里,Negroni 是一个很地道的 Web 中间件,它是一个具备微型、非嵌入式、鼓励使用原生 net/http
库特征的中间件。
如果你喜欢用 Martini ,但又觉得它太魔幻,那么 Negroni 就是你很好的选择了。
各国语言翻译:
入门指导
当安装了 Go 语言并设置好了 GOPATH 后,新建第一个 .go
文件,命名为 server.go
。
package main import ( "fmt" "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to the home page!") }) n := negroni.Classic() // Includes some default middlewares n.UseHandler(mux) http.ListenAndServe(":3000", n) }
然后安装 Negroni 包(注意:要求 Go 1.1 或更高的版本的 Go 语言环境):
go get github.com/urfave/negroni
最后运行刚建好的 server.go 文件:
go run server.go
这时一个 Go net/http
Web 服务器会跑在 localhost:3000
上,使用浏览器打开 localhost:3000
可看到输出的结果。
第三方包
如果你使用 Debian 系统,你可以执行 apt install golang-github-urfave-negroni-dev
来安装 negroni
。 包地址 (写该文档时,它是在 sid
仓库中).
Negroni 是一个框架吗?
Negroni 不是一个框架,它是为了方便使用 net/http
而设计的一个库而已。
路由呢?
Negroni 没有带路由功能,使用 Negroni 时,需要找一个适合你的路由。不过好在 Go 社区里已经有相当多可用的路由,Negroni 更喜欢和那些完全支持 net/http
库的路由搭配使用,比如搭配 Gorilla Mux 路由器,这样使用:
router := mux.NewRouter() router.HandleFunc("/", HomeHandler) n := negroni.New(Middleware1, Middleware2) // Or use a middleware with the Use() function n.Use(Middleware3) // router goes last n.UseHandler(router) http.ListenAndServe(":3001", n)
negroni.Classic()
negroni.Classic()
提供一些默认的中间件,这些中间件在多数应用都很有用。
negroni.Recovery
-- 异常(恐慌性)恢复中间件negroni.Logging
-- 请求 /响应 log 日志中间件negroni.Static
-- 静态文件处理中间件,默认目录在 "public" 下.
这使得从 Negroni 开始使用一些有用的特性变得非常容易。
Handlers (处理器)
Negroni 提供双向的中间件机制,这个特征很棒,都是得益于 negroni.Handler
这个接口。
type Handler interface { ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) }
在中间件没有写入 ResponseWriter 响应前,它会在中间件链上调用 next http.HandlerFunc
先执行, 以下代码就是优雅的使用方式:
func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { // do some stuff before next(rw, r) // do some stuff after }
你也可以用 Use
函数把这些 http.Handler
处理器引入到处理器链上来:
n := negroni.New() n.Use(negroni.HandlerFunc(MyMiddleware))
你也可以使用 UseHandler
把 http.Handler
s 处理器引入。
n := negroni.New() mux := http.NewServeMux() // map your routes n.UseHandler(mux) http.ListenAndServe(":3000", n)
With()
Negroni 还有一个便利的函数叫 With
. With
函数可以把一个或多个 Handler
实例和接收者处理器集合组合成新的处理器集合,并返回新的 Negroni
实例对象。
// middleware we want to reuse common := negroni.New() common.Use(MyMiddleware1) common.Use(MyMiddleware2) // `specific` is a new negroni with the handlers from `common` combined with the // the handlers passed in specific := common.With( SpecificMiddleware1, SpecificMiddleware2 )
Run()
Negroni 有一个很好用的函数 Run
, Run
接收 addr 地址字符串 http.ListenAndServe.
package main import ( "github.com/urfave/negroni" ) func main() { n := negroni.Classic() n.Run(":8080") }
未提供地址的情况下,会使用 PORT
系统环境变量, 若未定义该系统环境变量则会用预设的地址, 请参考 Run 详情说明。
一般来说使用 net/http
方法, 并且将 Negroni 当作处理器传入, 这样可定制化更佳, 例如:
package main import ( "fmt" "log" "net/http" "time" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to the home page!") }) n := negroni.Classic() // Includes some default middlewares n.UseHandler(mux) s := &http.Server{ Addr: ":8080", Handler: n, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } log.Fatal(s.ListenAndServe()) }
特定路由(分组路由)
如果你需要一组路由功能,需要借助特定的路由中间件完成,做法很简单,只需建立一个新 Negroni 实例,传入路由处理器里即可。
router := mux.NewRouter() adminRoutes := mux.NewRouter() // add admin routes here // Create a new negroni for the admin middleware router.PathPrefix("/admin").Handler(negroni.New( Middleware1, Middleware2, negroni.Wrap(adminRoutes), ))
如果你使用 Gorilla Mux, 下面是一个使用 subrouter 的例子:
router := mux.NewRouter() subRouter := mux.NewRouter().PathPrefix("/subpath").Subrouter().StrictSlash(true) subRouter.HandleFunc("/", someSubpathHandler) // "/subpath/" subRouter.HandleFunc("/:id", someSubpathHandler) // "/subpath/:id" // "/subpath" is necessary to ensure the subRouter and main router linkup router.PathPrefix("/subpath").Handler(negroni.New( Middleware1, Middleware2, negroni.Wrap(subRouter), ))
With()
可被用来减少在跨路由分享时多余的中间件的冗余.
router := mux.NewRouter() apiRoutes := mux.NewRouter() // add api routes here webRoutes := mux.NewRouter() // add web routes here // create common middleware to be shared across routes common := negroni.New( Middleware1, Middleware2, ) // create a new negroni for the api middleware // using the common middleware as a base router.PathPrefix("/api").Handler(common.With( APIMiddleware1, negroni.Wrap(apiRoutes), )) // create a new negroni for the web middleware // using the common middleware as a base router.PathPrefix("/web").Handler(common.With( WebMiddleware1, negroni.Wrap(webRoutes), ))
内置中间件
静态文件处理
中间件通过文件系统 filesystem 来代理(处理)静态文件。 一旦文件不存在, 请求代理会转到下个中间件。如果你想要处理不存在的文件返回 404 File Not Found
HTTP 错误, 请参考 http.FileServer 处理器吧.
例子:
package main import ( "fmt" "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to the home page!") }) // Example of using a http.FileServer if you want "server-like" rather than "middleware" behavior // mux.Handle("/public", http.FileServer(http.Dir("/home/public"))) n := negroni.New() n.Use(negroni.NewStatic(http.Dir("/tmp"))) n.UseHandler(mux) http.ListenAndServe(":3002", n) }
中间件首先从 /tmp
找文件,一旦在文件系统中找不到匹配的文件,代理将调用下一个文件。
恢复
本中间件接收 panic
跟错误代码 500
的响应。如果其他中间件写了响应代码或 Body 内容的话, 中间件会无法顺利地传送 500 错误给客户端, 因为客户端已经收到 HTTP 响应。另外, 可以 像 Sentry 或 Airbrake 挂载 PanicHandlerFunc
来报 500 错误给系统。
例子:
package main import ( "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { panic("oh no") }) n := negroni.New() n.Use(negroni.NewRecovery()) n.UseHandler(mux) http.ListenAndServe(":3003", n) }
它将输出 500 Internal Server Error
给每个请求. 如果 PrintStack
设成 true
(默认值)的话,它也会把错误日志写入请求方追踪堆栈。
加错误处理器的例子:
package main import ( "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { panic("oh no") }) n := negroni.New() recovery := negroni.NewRecovery() recovery.PanicHandlerFunc = reportToSentry n.Use(recovery) n.UseHandler(mux) http.ListenAndServe(":3003", n) } func reportToSentry(info *negroni.PanicInformation) { // write code here to report error to Sentry }
默认情况下,这个中间件会简要输出日志信息到 STDOUT 上。当然你也可以通过 SetFormatter() 函数自定义输出的日志。
当发生崩溃时,同样你也可以通过 HTMLPanicFormatter 来显示美化的 HTML 输出结果。
package main import ( "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { panic("oh no") }) n := negroni.New() recovery := negroni.NewRecovery() recovery.Formatter = &negroni.HTMLPanicFormatter{} n.Use(recovery) n.UseHandler(mux) http.ListenAndServe(":3003", n) }
Logger
该中间件负责打印各个请求和响应日志.
例子:
package main import ( "fmt" "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to the home page!") }) n := negroni.New() n.Use(negroni.NewLogger()) n.UseHandler(mux) http.ListenAndServe(":3004", n) }
每个请求打印日志将如下:
[negroni] 2017-10-04T14:56:25+02:00 | 200 | 378µs | localhost:3004 | GET /
当然你可以调用 SetFormat 来自定义日志的格式。格式是一个预定义了字段的 LoggerEntry 结构体。正如以下代码: -
l.SetFormat("[{{.Status}} {{.Duration}}] - {{.Request.UserAgent}}")
第三方兼容中间件
以下是兼容 Negroni 的中间件列表,如果你也有兼容 Negroni 的中间件,如果想提交自己的中间件,建议你附上 PR 链接。
中间件 | 作者 | 描述 |
---|---|---|
authz | Yang Luo | 支持ACL, RBAC, ABAC的权限管理中间件,基于Casbin |
binding | Matt Holt | HTTP 请求数据注入到 structs 实体 |
cloudwatch | Colin Steele | AWS CloudWatch 矩阵的中间件 |
cors | Olivier Poitrey | Cross Origin Resource Sharing (CORS) support |
csp | Awake Networks | 基于Content Security Policy(CSP) |
delay | Jeff Martinez | 为endpoints增加延迟时间. 在测试严重网路延迟的效应时好用 |
New Relic Go Agent | Yadvendar Champawat | 官网 New Relic Go Agent (目前正在测试阶段) |
gorelic | Jingwen Owen Ou | New Relic agent for Go runtime |
Graceful | Tyler Bunnell | 优雅关闭 HTTP 的中间件 |
gzip | phyber | 响应流 GZIP 压缩 |
JWT Middleware | Auth0 | Middleware checks for a JWT on the Authorization header on incoming requests and decodes it
|
logrus | Dan Buch | 基于 Logrus-based logger 日志 |
oauth2 | David Bochenski | oAuth2 中间件 |
onthefly | Alexander Rødseth | 快速生成 TinySVG, HTML and CSS 中间件 |
permissions2 | Alexander Rødseth | Cookies, 用户和权限 |
prometheus | Rene Zbinden | 简易建立矩阵端点给prometheus建构工具 |
render | Cory Jacobsen | 渲染 JSON, XML and HTML 中间件 |
RestGate | Prasanga Siripala | REST API 接口的安全认证 |
secure | Cory Jacobsen | Middleware that implements a few quick security wins |
sessions | David Bochenski | Session 会话管理 |
stats | Florent Messa | 检测 web 应用当前运行状态信息 (响应时间等等。) |
VanGoH | Taylor Wrobel | Configurable AWS-Style 基于 HMAC 鉴权认证的中间件 |
xrequestid | Andrea Franz | 给每个请求指定一个随机 X-Request-Id 头的中间件 |
mgo session | Joel James | 处理在每个请求建立与关闭 mgo sessions |
digits | Bilal Amarni | 处理 Twitter Digits 的认证 |
stats | Chirag Gupta | endpoints用的管理QPS与延迟状态的中间件非同步地将状态刷入InfluxDB |
Chaos | Marc Falzon | 以编程方式在应用程式中插入无序行为的中间件 |
范例
Alexander Rødseth 创建的 mooseware 是一个编写兼容 Negroni 中间件的处理器骨架的范例。
即时编译
gin 和 fresh 这两个应用是即时编译的 Negroni 工具,推荐用户开发的时候使用。
Go & Negroni 初学者必读推荐
关于 Negroni
Negroni 原由 Code Gangsta 主导设计开发。