进阶 www.iris-go.com 2026-10-20 02:39:14 · 18 阅读

第63章 Iris 框架第63章:Middleware 中间件详解

中间件 ​ Iris 中的中间件是在 HTTP 请求的主处理器之前或之后执行的代码。例如,一个名为 "logger" 的中间件可以在把控制权交给主处理器之前,将请求详情写入文件或控制台。中间件非常灵活,可在应用内部乃至多个应用之间复用! 中间件本质上就是一个签名为 func(ctx iris.Context) 的 iris.Handler。每个中间件在上一个中间件调用 ctx.Next() 方法时才会执行。这个机制可用于身份验证等场景——比如请求未获授权时,可以直接返回错误响应,而不调用 ctx.Next()。 Iris 提供了 7(+1)种注册中间件的方式,以满足不同的应用需求:Party.UseError、Party.Use、Party.UseOnce、Application.UseGlobal、Party.Done、Application.DoneGlobal、Party.UseRouter 和 Application.WrapRouter。借助这些工具,你可以完全掌控请求的处理流程。 注册中间件 ​ 下面通过在一个 Iris Application 和 Party(路由分组)中注册中间件,看看这些方法各自的实际效果。下面的示例会向客户端展示每个已注册处理器的执行顺序。 ```go package main import ( "net/http" "github.com/kataras/iris/v12" ) func main() { app := iris.New() app.WrapRouter(routerWrapper) app.UseRouter(routerMiddleware) app.UseGlobal(globalMiddleware) app.Use(useMiddleware) app.UseError(errorMiddleware) // app.Done(done) // app.DoneGlobal(doneGlobal) // 添加 OnErrorCode(iris.StatusNotFound) 后,`.UseGlobal` // 也会在 404 页面上触发;如果不加这一行, // 只有 `UseError` 会被调用,因此错误页面应使用 UseError。 app.OnErrorCode(iris.StatusNotFound, notFoundHandler) app.Get("/", mainHandler) app.Listen(":8080") } func mainHandler(ctx iris.Context) { ctx.WriteString("Main Handler") } func notFoundHandler(ctx iris.Context) { ctx.WriteString("404 Error Handler") } ``` Application.WrapRouter 方法 以这种方式注册的 WrapperFunc 针对整个 Application 运行,优先于其他所有操作。其执行顺序是后注册先执行,这与其他中间件方法不同。WrapperFunc 并不是 iris.Handler,它的签名如下:`func(http.ResponseWriter, *http.Request, router http.HandlerFunc)`。这是用于拦截请求并可选择性修改 router 行为(例如导航到与请求路径不同的路由、在 router 执行前进行安全校验等)的最底层功能。CORS 实现就是一个很好的用例示例。 Party.UseRouter 方法 以这种方式注册的中间件会在特定 Party 的路径前缀下对所有路由运行(无论是否匹配成功,包括会导致错误的路由)。除非调用了 Party.ResetRouteFilters() 或 Party.Reset(),否则所有子 Party 都会继承这些中间件。它们按照注册顺序执行,且在匹配路由上使用 UseGlobal 和 Use 之前,或在错误发生时使用 UseError 之前运行。 Application.UseGlobal 方法 以这种方式注册的中间件会在整个 Application(包括所有 Party 和 Subdomain)的所有现有及未来路由,以及注册的错误处理器(通过 OnErrorCode 匹配页面)上运行。它们也按照注册顺序执行,且在 Use 或 UseError 之前运行。 Party.Use 方法 以这种方式注册的中间件会在特定 Party 及其子级下,位于 UseGlobal 之后运行。它们按照注册顺序执行,并紧跟在 Handle(如 Get、Post、Patch 等)执行之前。该中间件在发生错误时不会执行。 Party.UseError 方法 以这种方式注册的中间件仅在遇到 HTTP 错误(如资源不可用的 404 或受保护的 401 资源)时才会运行,这一点与 Use 不同。它们会在所有子 Party 中触发,并按照注册顺序被调用。 Party.Done 方法 在特定 Party 及其子级下,注册在路由中间件和处理程序之后执行的处理器。除非修改了 Execution Rules,否则最后一个路由的处理器需要调用 ctx.Next。 Application.DoneGlobal 方法 与 UseGlobal 类似,但专门针对 Done 处理器。它在 Application 实例级别运行,位于其他所有操作之后。 本示例中使用的中间件代码如下: ```go func routerWrapper(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) { if r.URL.Path == "/" { w.Write([]byte("#1 .WrapRouter\n")) /* 给新 Gopher 的提示: 如果像这样的原始 `net/http` 包装器在错误资源上写入任何内容, 响应写入器将自动发送 `200` OK 状态码(在首次写入时)。 此之后执行的任何错误处理器都不会按预期触发。 此外,一旦调用了 `w.WriteHeader`,就不能再更改状态码。 在 Iris 处理器中,如果在设置状态码之前写入内容, 同样会自动发送 200 OK 状态码,且之后无法更改。 然而,如果在 Iris 处理器中调用 `ctx.StatusCode` 且未写入任何 内容,则之后可以更改状态码。如果需要改变这种行为, 必须在处理器开头调用 `ctx.Record`。 */ } // 继续执行 Iris Router,让它完成工作。 router(w, r) } func routerMiddleware(ctx iris.Context) { if ctx.Path() == "/" { ctx.WriteString("#2 .UseRouter\n") // routerWrapper 中描述的相同注意事项在这里也适用。 } ctx.Next() } func globalMiddleware(ctx iris.Context) { ctx.WriteString("#3 .UseGlobal\n") ctx.Next() } func useMiddleware(ctx iris.Context) { ctx.WriteString("#4 .Use\n") ctx.Next() } func errorMiddleware(ctx iris.Context) { ctx.WriteString("#3 .UseError\n") ctx.Next() } ```
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
运行我们的简单应用:bash$ go run main.go Now listening on: http://localhost:8080 Application started. Press CTRL+C to shut down.1
2
3
将浏览器指向 http://localhost:8080,输出结果应完全如下:text#1 .WrapRouter #2 .UseRouter #3 .UseGlobal #4 .Use Main Handler1
2
3
4
5
对于应当返回 404 的页面,例如 http://localhost:8080/a_not_found_resource:text#3 .UseGlobal #3 .UseError 404 Error Handler1
2
3
(注意 WrapRouter 和 UseRouter 仍按顺序在第 1、2 位触发,只是未显示在输出中。请阅读块状注释了解原因。)但如果没有 OnErrorCode: 重点强调,若未注册 OnErrorCode,你的 UseGlobal 不会执行。text#3 .UseError Not Found1
2
修改 Middleware 你可以通过包装现有 Middleware 来修改其行为。 假设你想在 UseRouter 注册一个 Middleware,使其在除 static.example.com 子域外的所有地方运行。有两种实现方式。第一种:gopackage main import ( "github.com/kataras/iris/v12" "github.com/kataras/iris/v12/middleware/basicauth" ) func main() { users := map[string]string{"username":"password"} auth := basicauth.Default(users) app.UseRouter(skipStaticSubdomain(auth)) // <-- // [...] app.Listen(":80") } func skipStaticSubdomain(handler iris.Handler) iris.Handler { return func(ctx iris.Context) { if ctx.Subdomain() == "static." { // 继续到下一个或主处理函数并退出。 ctx.Next() return } handler(ctx) } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
第二种方式是使用 iris.NewConditionalHandler:gotype Filter func(Context) bool func NewConditionalHandler(filter Filter, handlers ...Handler) Handler1
2
3
使用示例,检查子域是否存在且不是 static.:goapp.UseRouter(iris.NewConditionalHandler(isNotStaticSubdomain, auth))1
gofunc isNotStaticSubdomain(ctx iris.Context) bool { return ctx.Subdomain() != "static." }1
2
3
就这么简单,理解了思路之后实现起来很轻松。在 Handler 之间传递数据 ​每次请求-响应的生命周期中都包含一个 iris.Context 实例,该实例在整个 handler 链中共享。Context.Values() 返回一块临时内存存储,可用于在中间件和 handler 之间传递数据。这块存储提供了很多辅助方法,最常用的两个是:goSet(key string, value interface{}) (Entry, bool) Get(key string) interface{}1
2
用法 ​设置一个值:gofunc myMiddleware(ctx iris.Context) { ctx.Values().Set("key", value) }1
2
3
读取一个值:gofunc myHandler(ctx iris.Context) { value := ctx.Values().Get("key") }1
2
3
完整的 memstore 方法列表见这里。编写中间件 ​gopackage main import "github.com/kataras/iris/v12" func main() { app := iris.New() // 或者使用 app.Use(before) 和 app.Done(after)。 app.Get("/", before, mainHandler, after) app.Listen(":8080") } func before(ctx iris.Context) { shareInformation := "this is a sharable information between handlers" requestPath := ctx.Path() println("Before the mainHandler: " + requestPath) ctx.Values().Set("info", shareInformation) ctx.Next() // 执行下一个 handler,这里是 mainHandler。 } func after(ctx iris.Context) { println("After the mainHandler") } func mainHandler(ctx iris.Context) { println("Inside mainHandler") // 从 "before" handler 中取出信息。 info := ctx.Values().GetString("info") // 向客户端写回一些响应内容。 ctx.HTML("") ctx.HTML("
Info: " + info) ctx.Next() // 执行 "after"。 }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
bash$ go run main.go # 然后访问 http://localhost:8080 Now listening on: http://localhost:8080 Application started. Press CTRL+C to shut down. Before the mainHandler: / Inside mainHandler After the mainHandler1
2
3
4
5
6
## 从路由中移除 Handler 若要移除某条路由上的特定 handler,使用由 `Handle/Get/Post/Put` 等方法注册的 `*Route` 的 `RemoveHandler` 方法。该方法接受 handler 的名称(即 PC func,详见 `iris` context 子包中的 `HandlerName`)或 handler 本身。 示例代码: ```go func middleware(ctx iris.Context) { // [...] } func main() { app := iris.New() // 将中间件注册到所有匹配的路由上。 app.Use(middleware) // 当前路由的 handler 为:middleware、index app.Get("/", index) // 移除 middleware,当前路由的 handler 为:other app.Get("/other", other).RemoveHandler(middleware) } ``` ## 执行规则 你还可以通过 `ExecutionRules` 强制 `Begin`(`UseXXX`)和 `Finish`(`DoneXXX`)类型的 handler 执行,使其不再依赖 `ctx.Next()` 调用,强制将它们向前传递: ```go app.SetExecutionRules(iris.ExecutionRules{ // Begin: ... // Main: ... Done: iris.ExecutionOptions{Force: true}, }) ``` 转换 http.Handler/HandlerFunc 其实你的选择并不局限于这些——你可以使用任何基于 net/http 包工作的第三方中间件。Iris 与其他框架不同,它百分之百兼容标准规范,正因如此,许多在开发流程中使用 Go 的大型企业(包括一家知名的美国电视网络)都信任 Iris;它始终与时俱进,并会紧跟由 Go 作者在每个新语言版本中更新的 std net/http 包。 你可以通过调用 iris.FromStd(aThirdPartyMiddleware) 在 Iris 中使用任何为 net/http 编写的第三方中间件。请记住,ctx.ResponseWriter() 和 ctx.Request() 返回的正是 http.Handler 中相同的 net/http 输入参数。 From func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) From http.Handler 或 http.HandlerFunc From func(http.HandlerFunc) http.HandlerFunc 社区中间件 | 中间件 | 描述 | 示例 | | :--- | :--- | :--- | | pg | 提供对 PostgreSQL 数据库进行便捷且类型安全访问的中间件 | iris-contrib/middleware/pg/_examples | | jwt | 检查传入请求 Authorization 头部中的 JWT 并对其进行解码 | iris-contrib/middleware/jwt/_example | | cors | HTTP 访问控制 | iris-contrib/middleware/cors/_example | | secure | 实现几项快速安全加固的中间件 | iris-contrib/middleware/secure/_example | | tollbooth | 通用的 HTTP 请求速率限制中间件 | iris-contrib/middleware/tollboothic/_examples/limit-handler | | cloudwatch | AWS CloudWatch 指标中间件 | iris-contrib/middleware/cloudwatch/_example | | new relic | 官方的 New Relic Go Agent | iris-contrib/middleware/newrelic/_example | | prometheus | 轻松创建用于 prometheus 监控工具的指标端点 | iris-contrib/middleware/prometheus/_example | | casbin | 支持 ACL、RBAC、ABAC 等访问控制模型的授权库 | iris-contrib/middleware/casbin/_examples | | sentry-go (例: raven) | Go 语言的 Sentry 客户端 | sentry-go/example/iris | | csrf | 跨站点请求伪造 (CSRF) 防护 | iris-contrib/middleware/csrf/_example | | throttler | 限制对 HTTP 端点的访问速率 | iris-contrib/middleware/throttler/_example | 第三方中间件 Iris 拥有自己 func(ctx iris.Context) 形式的中间件,但它同时也兼容所有 net/http 的中间件形式。详情参见此处。 这里列出了一些有用的第三方处理器: 中间件 | 中间件 | 描述 | | :--- | :--- | | goth | OAuth 和 OAuth2 认证 | | permissions2 | 基于 Cookie 的用户和权限管理 | | csp | 内容安全策略 (CSP) 支持 | | delay | 为端点添加延迟。在测试高延迟影响时很有用 | | onthefly | 即时生成 TinySVG、HTML 和 CSS | | RestGate | 为 REST API 端点提供安全的认证 | | stats | 存储有关你的 Web 应用程序的信息(如响应时间等) | | VanGoH | 可配置的 AWS 风格 HMAC 认证中间件 | | digits | 处理 Twitter Digits 认证的中间件 |

评论 (0)