教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 使用 Go 处理中间件

使用 Go 处理中间件

发布时间:2022-01-16   编辑:jiaochengji.com
教程集为您提供使用 Go 处理中间件等资源,欢迎您收藏本站,我们将为您提供最新的使用 Go 处理中间件资源
<ul><li>简介</li> <li>gin 的中间件</li> <li>创建中间件</li> <li>总结</li> <li>当前部分的代码</li> </ul><h2>简介</h2>

开发 web 应用的时候, 很多地方都需要使用中间件来统一处理一些任务,
比如记录日志, 登录校验等.

gin 也提供了中间件功能.

<h2>gin 的中间件</h2>

在项目创建之初, 就已经导入了一些中间件, 当时没有仔细介绍.

<pre><code class="go">g.Use(gin.Logger()) g.Use(gin.Recovery()) g.Use(middleware.NoCache()) g.Use(middleware.Options()) g.Use(middleware.Secure())</code></pre>

前面两个是 gin 自带的中间件, 分别是日志记录和错误恢复.
后面三个是设置一些 header, 具体是阻止缓存响应, 响应 options 请求,
以及浏览器安全设置.

<pre><code class="go">// 阻止缓存响应 func NoCache() gin.HandlerFunc { return func(ctx *gin.Context) { ctx.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") ctx.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") ctx.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) ctx.Next() } } // 响应 options 请求, 并退出 func Options() gin.HandlerFunc { return func(ctx *gin.Context) { if ctx.Request.Method != "OPTIONS" { ctx.Next() } else { ctx.Header("Access-Control-Allow-Origin", "*") ctx.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") ctx.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") ctx.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") ctx.Header("Content-Type", "application/json") ctx.AbortWithStatus(200) } } } // 安全设置 func Secure() gin.HandlerFunc { return func(ctx *gin.Context) { ctx.Header("Access-Control-Allow-Origin", "*") ctx.Header("X-Frame-Options", "DENY") ctx.Header("X-Content-Type-Options", "nosniff") ctx.Header("X-XSS-Protection", "1; mode=block") if ctx.Request.TLS != nil { ctx.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // ctx.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } }</code></pre>

gin 的中间件结构就是一个返回 <code>func(ctx *gin.Context)</code> 的函数,
又叫做 <code>gin.HandlerFunc</code>. 本质上和普通的 handler 没什么不同,
<code>gin.HandlerFunc</code>是<code>func(*Context)</code> 的别名.

中间件可以被定义在三个地方

<ul><li>全局中间件</li> <li>Group 中间件</li> <li>单个路由中间件</li> </ul>

一点需要注意的是在 middleware 和 handler 中使用 goroutine 时,
应该使用 gin.Context 的只读副本, 例如 <code>cCp := context.Copy()</code>.

另一点则是注意中间件的顺序.

官方的示例如下:

<pre><code class="go">func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // Set example variable c.Set("example", "12345") // before request c.Next() // after request latency := time.Since(t) log.Print(latency) // access the status we are sending status := c.Writer.Status() log.Println(status) } }</code></pre> <h2>创建中间件</h2>

介绍了 gin 的中间件知识之后, 就可以根据需求使用中间件了.

实现一个中间件在每个请求中设置 <code>X-Request-Id</code> 头.

<pre><code class="go">// 在请求头中设置 X-Request-Id func RequestId() gin.HandlerFunc { return func(ctx *gin.Context) { requestId := ctx.Request.Header.Get("X-Request-Id") if requestId == "" { requestId = uuid.NewV4().String() } ctx.Set("X-Request-Id", requestId) ctx.Header("X-Request-Id", requestId) ctx.Next() } }</code></pre>

设置 header 的同时保存在 context 内部, 通过设置唯一的 ID 之后,
就可以追踪一系列的请求了.

再来实现一个日志记录的中间件, 虽然 gin 已经自带了日志记录的中间件,
但自己实现可以更加个性化.

<pre><code class="go">// 定义日志组件, 记录每一个请求 func Logging() gin.HandlerFunc { return func(ctx *gin.Context) { path := ctx.Request.URL.Path method := ctx.Request.Method ip := ctx.ClientIP() // 只记录特定的路由 reg := regexp.MustCompile("(/v1/user|/login)") if !reg.MatchString(path) { return } var bodyBytes []byte if ctx.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(ctx.Request.Body) } // 读取后写回 ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: ctx.Writer, } ctx.Writer = blw start := time.Now() ctx.Next() // 计算延迟, 和 gin.Logger 的差距有点大 // 这是因为 middleware 类似栈, 先进后出, ctx.Next() 是转折点 // 所以 gin.Logger 放在最前, 记录总时长 // Logging 放在最后, 记录实际运行的时间, 不包含其他中间件的耗时 end := time.Now() latency := end.Sub(start) code, message := -1, "" var response handler.Response if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { logrus.Errorf( "response body 不能被解析为 model.Response struct, body: `%s`, err: `%v`", blw.body.Bytes(), err, ) code = errno.InternalServerError.Code message = err.Error() } else { code = response.Code message = response.Message } logrus.WithFields(logrus.Fields{ "latency": fmt.Sprintf("%s", latency), "ip": ip, "method": method, "path": path, "code": code, "message": message, }).Info("记录请求") } }</code></pre>

在注册中间件的时候, 将 <code>Logging</code> 放在全局中间件的最后,
将 gin.Logger() 放在全局中间件的最开始.
通过对比延迟, 你可以发现, 在 handler 处理比较快时,
中间件在总请求耗时中占据了很大的比例.

所以, 中间件虽然非常实用, 但需要控制全局中间件的数量.

<h2>总结</h2>

中间件是非常实用的, 基本上 web 框架都会实现.

<h2>当前部分的代码</h2>

作为版本 v0.8.0

到此这篇关于“使用 Go 处理中间件”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
想系统学习GO语言(Golang
从零开始学习GO语言-搭建Go语言开发环境-快速开发入门第一个小程序
go语言和php的区别是什么?
Go语言爱好者周刊:第 78 期 — 这道关于 goroutine 的题
Go Base
1.14 Go语言工程结构详述
Go语言微服务开发框架实践-go chassis(中篇)
go run main.go 参数_Go语言入门:Hello world
Go1.13:使用go mod 管理依赖, 提示cannot find module providing package或cannot find main module
GO 依赖管理工具go Modules(官方推荐)

[关闭]
~ ~