AI 中文社区(简称 AI 中文社),是国内学习交流AI人工智能技术的中文社区网站,这里可获取及贡献任何AI人工智能技术,我们追求自由、简洁、纯粹、分享的多元化人工智能社区。

Negroni 入门指南

发布于 2024-05-07 · 浏览数 34 · 最后编辑 2024-05-20 12:35

Negroni

注意: 本函数库原本位于 github.com/codegangsta/negroni -- Github 会自动将请求重定向到当前地址, 但我们建议你更新一下引用路径。


在 Go 语言里,Negroni 是一个很地道的 Web 中间件,它轻量且非侵入,鼓励使用原生 net/http 处理器(Handlers)。


如果你喜欢用 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 - 请求 / 响应的日志中间件

  • negroni.Static - 静态文件处理中间件,默认目录为 "public"

negroni.Classic() 使你可以轻松上手 Negroni 那些有用的特性。

Handlers (处理器)

Negroni 提供双向的中间件机制,通过 negroni.Handler 这个接口来实现:

type Handler interface {
  ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
}

在中间件尚未写入 ResponseWriter 时,它会调用链上的下一个 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 函数把它映射到处理器链上:

n := negroni.New()
n.Use(negroni.HandlerFunc(MyMiddleware))

你也可以映射 http.Handler

n := negroni.New()

mux := http.NewServeMux()
// map your routes

n.UseHandler(mux)

http.ListenAndServe(":3000", n)

With()

Negroni 有一个便利的函数叫 WithWith 函数可以把一个或多个 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 有一个便利的函数叫 RunRun 接收 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),
))

内置中间件

静态文件处理

该中间件通过文件系统来代理(处理)静态文件。 一旦文件不存在,请求代理会转到下个中间件。如果你想要处理不存在的文件返回 404 File Not Found HTTP 错误,请参考使用 http.FileServer 作为处理器吧。


例子:

<!-- { "interrupt": true } -->
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 响应代码。另外,可以附上 PanicHandlerFunc 来报 500 错误给错误报告系统,如 Sentry 或 Airbrake。


例子:

<!-- { "interrupt": 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()
  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) {
    // 在这写些程式回报错误给 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

该中间件负责打印各个请求和响应日志。


例子:

<!-- { "interrupt": true } -->
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}}")

会输出:[200 18.263µs] - Go-User-Agent/1.1

第三方兼容中间件

以下是兼容 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 中间件处理器的骨架。


Prasanga Siripala 创建的 Go-Skeleton 是一个高效编写基于 web 的 Go/Negroni 项目的骨架。

即时编译

ginfresh 这两个应用是即时编译的 Negroni 工具。

Go & Negroni 初学者必读推荐

关于 Negroni

Negroni 原由 Code Gangsta 主导设计开发。

Negroni 入门指南 - 开发类 - Wiki - AI 中文社区