ginvalidator 是一组 Gin 中间件,它包装了我的另一个开源包 validatorgo 提供的广泛的验证器和消毒器集合。它还使用流行的开源包 gjson 进行 JSON 字段语法,提供从 JSON 对象中高效查询和提取数据的功能。
它允许您以多种方式组合它们,以便您可以验证和清理您的 Gin 请求,并提供工具来确定请求是否有效,以及根据您的验证器匹配哪些数据。
它基于流行的js/express库express-validator
此版本的 ginvalidator 要求您的应用程序在 Go 1.16 上运行。
它还经验证可与 Gin 1.x.x.
为什么不使用?
确保您的计算机上安装了 Go。
go mod init example.com/learning
使用 go get 安装必要的软件包。
go get -u github.com/gin-gonic/gin
go get -u github.com/bube054/ginvalidator
学习东西的最好方法之一就是通过实例!因此,让我们卷起袖子,开始编写代码吧。
首先需要的是运行一个 Gin 服务器。让我们实现一个向某人打招呼的功能;为此,创建一个 main.go 然后添加以下代码:
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/hello", func(ctx *gin.Context) { person := ctx.Query("person") ctx.String(http.StatusOK, "Hello, %s!", person) }) r.Run() // listen and serve on 0.0.0.0:8080 }
现在通过在终端上执行 go run main.go 来运行此文件。
go mod init example.com/learning
HTTP 服务器应该正在运行,您可以打开 http://localhost:8080/hello?person=John 向 John 致敬!
? 提示:
您可以将 Air 与 Go 和 Gin 结合使用来实现实时重新加载。每当文件更改时,这些都会自动重新启动服务器,因此您不必自己执行此操作!
所以服务器正在工作,但存在问题。最值得注意的是,当某人的名字未设置时,您不想向某人打招呼。
例如,访问 http://localhost:8080/hello 将打印“Hello,”。
这就是 ginvalidator 派上用场的地方。它提供了用于验证您的请求的验证器、消毒器和修饰符。
让我们添加一个验证器和一个修饰符来检查人员查询字符串不能为空,验证器名为 Empty ,修饰符名为 Not:
go get -u github.com/gin-gonic/gin
? 注意:
为简洁起见,代码示例中使用 gv 作为 ginvalidator 的别名。
现在,重新启动服务器,然后再次访问 http://localhost:8080/hello。嗯,它仍然打印“Hello,!”...为什么?
ginvalidator验证链不会自动向用户报告验证错误。
原因很简单:当您添加更多验证器或更多字段时,您希望如何收集错误?您想要一份所有错误的列表,每个字段只有一个,整体只有一个......?
所以下一个明显的步骤是再次更改上面的代码,这次使用 ValidationResult 函数验证验证结果:
go get -u github.com/bube054/ginvalidator
现在,如果您再次访问 http://localhost:8080/hello,您将看到以下 JSON 内容,为了清晰起见,我们进行了格式化:
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/hello", func(ctx *gin.Context) { person := ctx.Query("person") ctx.String(http.StatusOK, "Hello, %s!", person) }) r.Run() // listen and serve on 0.0.0.0:8080 }
现在,这告诉我们的是
这是一个更好的场景,但仍然可以改进。我们继续吧。
所有请求位置验证器都接受可选的第二个参数,它是用于格式化错误消息的函数。如果提供 nil,将使用默认的通用错误消息,如上面的示例所示。
go run main.go
现在,如果您再次访问 http://localhost:8080/hello,您将看到以下 JSON 内容,并带有新的错误消息:
package main import ( "net/http" gv "github.com/bube054/ginvalidator" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/hello", gv.NewQuery("person", nil). Chain(). Not(). Empty(nil). Validate(), func(ctx *gin.Context) { person := ctx.Query("person") ctx.String(http.StatusOK, "Hello, %s!", person) }) r.Run() }
您可以使用 GetMatchedData,它会自动收集 ginvalidator 已验证和/或清理的所有数据。然后可以使用 MatchedData 的 Get 方法访问此数据:
go mod init example.com/learning
打开 http://localhost:8080/hello?person=John 向 John 致敬!
可用位置有 BodyLocation、CookieLocation、QueryLocation、ParamLocation 和 HeaderLocation。
每个位置都包含一个 String 方法,该方法返回存储经过验证/清理的数据的位置。
虽然用户不能再发送空人名,但它仍然可以将 HTML 注入您的页面!这称为跨站脚本漏洞 (XSS)。
让我们看看它是如何工作的。转到 http://localhost:8080/hello?person=John,您应该看到“Hello, John!”。
虽然此示例很好,但攻击者可以将人员查询字符串更改为 <script> 。加载自己的 JavaScript 的标签可能有害。<br>
在这种情况下,缓解 ginvalidator 问题的一种方法是使用消毒剂,特别是 Escape,它将特殊的 HTML 字符与其他可以表示为文本的字符进行转换。<br>
</script>
go get -u github.com/gin-gonic/gin
现在,如果您重新启动服务器并刷新页面,您将看到“Hello, John!”。我们的示例页面不再容易受到 XSS 攻击!
⚠️ 注意:
ginvalidator 在清理期间不会修改 http.Request 值。要访问清理后的数据,请始终使用 GetMatchedData 函数。
验证链是 ginvalidator 中的主要概念之一,因此了解它很有用,以便您可以有效地使用它。
但是不用担心:如果您已经阅读了入门指南,那么您已经在不知不觉中使用了验证链!
验证链是使用以下函数创建的,每个函数都针对 HTTP 请求中的特定位置:
它们之所以有这个名字,是因为它们用验证(或清理)来包装字段的值,并且它的每个方法都返回自身。
这种模式通常称为方法链,因此称为验证链。
验证链不仅具有许多用于定义验证、清理和修改的有用方法,而且还具有返回 Gin 中间件处理函数的 Validate 方法。
这是验证链通常如何使用以及如何阅读的示例:
go mod init example.com/learning
验证链具有三种方法:验证器、消毒器和修饰器。
验证器确定请求字段的值是否有效。这意味着检查该字段是否采用您期望的格式。例如,如果您正在构建注册表单,您的要求可能是用户名必须是电子邮件地址,并且密码的长度必须至少为 8 个字符。
如果该值无效,则会使用一些错误消息记录该字段的错误。然后可以稍后在 Gin 路由处理程序中检索此验证错误并将其返回给用户。
他们是:
消毒剂会改变字段值。它们对于消除值中的噪音很有用,甚至可能提供一些针对威胁的基本防线。
Sanitizers 将更新后的字段值保留回 Gin 上下文中,以便其他 ginvalidator 函数、您自己的路由处理程序代码,甚至其他中间件都可以使用它。
他们是:
修饰符定义验证链运行时的行为方式。
他们是:
? 注意:
这些方法在 pkg.go.dev ginvalidator 文档中使用 GoDoc 进行了完整记录。如果有任何细节不清楚,您可能还需要参考 validatorgo 包中的相关函数以获取更多上下文,我将在下面进行解释。
验证链公开的所有功能实际上都来自 validatorgo,这是我的其他开源 go 包之一,专门从事字符串验证/清理。请检查一下,加星标并分享???,谢谢。
这包括所有 validatorgo 验证器和清理程序,从常用的 IsEmail、IsLength 和 Trim 到更小众的 IsISBN、IsMultibyte 和 StripLow!
这些在 ginvalidator 中被称为标准验证器和标准消毒器。但没有来自 validatorgo 的 Is 前缀。
在验证链上调用方法的顺序通常很重要。
它们几乎总是按照指定的顺序运行,因此您只需阅读验证链的定义(从第一个链接方法到最后一个方法)就可以知道验证链将做什么。
以下面的代码片段为例:
go mod init example.com/learning
在这种情况下,如果用户传递一个仅由空格组成的“search_query”值,它不会为空,因此验证通过。但由于 .Trim() 消毒剂存在,空格将被删除,并且该字段将变为空,因此您实际上最终会得到误报。
现在,将其与以下代码片段进行比较:
go get -u github.com/gin-gonic/gin
这条链将更明智地删除空格,然后验证值是否不为空。
此规则的一个例外是 .Optional():它可以放置在链中的任何一点,并将该链标记为可选。
如果您希望重用同一个链,最好从函数中返回它们:
go get -u github.com/bube054/ginvalidator
在 ginvalidator 中,字段是任何经过验证或清理的值,并且它是字符串。
几乎每个函数或值都以某种方式由 ginvalidator 引用字段返回。因此,了解字段路径语法对于选择验证字段以及访问验证错误或验证数据时非常重要。
正文字段仅对以下内容类型有效:
go mod init example.com/learning
使用路径 user.name,提取的值为“John”。
go get -u github.com/gin-gonic/gin
身体:
go get -u github.com/bube054/ginvalidator
字段“name”的值为“John”,“email”的值为“john.doe@example.com”。
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/hello", func(ctx *gin.Context) { person := ctx.Query("person") ctx.String(http.StatusOK, "Hello, %s!", person) }) r.Run() // listen and serve on 0.0.0.0:8080 }
身体:
go run main.go
字段“name”的值为“John”,“file”为上传的文件。
查询字段对应URL搜索参数,其值自动为Gin未转义的url。
示例:
package main import ( "net/http" gv "github.com/bube054/ginvalidator" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/hello", gv.NewQuery("person", nil). Chain(). Not(). Empty(nil). Validate(), func(ctx *gin.Context) { person := ctx.Query("person") ctx.String(http.StatusOK, "Hello, %s!", person) }) r.Run() }
package main import ( "net/http" gv "github.com/bube054/ginvalidator" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/hello", gv.NewQuery("person", nil). Chain(). Not(). Empty(nil). Validate(), func(ctx *gin.Context) { result, err := gv.ValidationResult(ctx) if err != nil { ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ "message": "The server encountered an unexpected error.", }) return } if len(result) != 0 { ctx.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{ "errors": result, }) return } person := ctx.Query("person") ctx.String(http.StatusOK, "Hello, %s!", person) }) r.Run() }
Param 字段 表示 URL 路径参数,其值会被 ginvalidator 自动转义。
示例:
{ "errors": [ { "location": "queries", "message": "Invalid value", "field": "person", "value": "" } ] }
标头字段是HTTP请求标头,它们的值不是未转义的。如果您提供非规范标头密钥,将会出现日志警告。
示例:
package main import ( "net/http" gv "github.com/bube054/ginvalidator" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/hello", gv.NewQuery("person", func(initialValue, sanitizedValue, validatorName string) string { return "Please enter your name." }, ).Chain(). Not(). Empty(nil). Validate(), func(ctx *gin.Context) { result, err := gv.ValidationResult(ctx) if err != nil { ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ "message": "The server encountered an unexpected error.", }) return } if len(result) != 0 { ctx.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{ "errors": result, }) return } person := ctx.Query("person") ctx.String(http.StatusOK, "Hello, %s!", person) }) r.Run() }
Cookie 字段 是 HTTP cookie,其值自动为 Gin 未转义的 url。
示例:
{ "errors": [ { "location": "queries", "message": "Please enter your name.", "field": "person", "value": "" } ] }
如果您正在构建的服务器不是一个非常简单的服务器,那么迟早您将需要验证器、清理器和错误消息,而不仅仅是 ginvalidator 中内置的内容。
ginvalidator 无法满足您并且您可能会遇到的一个经典需求是在用户注册时验证电子邮件地址是否正在使用。
可以通过实现自定义验证器在 ginvalidator 中执行此操作。
CustomValidator 是验证链上可用的方法,它接收特殊函数 CustomValidatorFunc,并且必须返回一个布尔值来确定字段是否有效。
CustomSanitizer 也是验证链上可用的方法,它接收特殊函数 CustomSanitizerFunc,并且必须返回新的清理值。
CustomValidator 可以通过使用 goroutine 和sync.WaitGroup 来异步处理并发操作。在验证器中,您可以为每个异步任务启动 goroutine,并将每个任务添加到 WaitGroup。所有任务完成后,验证器应返回一个布尔值。
例如,为了检查电子邮件是否未被使用:
go mod init example.com/learning
或者您也可以验证密码是否与重复匹配:
go get -u github.com/gin-gonic/gin
⚠️ 注意:
如果请求正文将被多次访问(无论是在同一验证链中、在同一请求上下文的另一个验证链中还是在后续处理程序中),请确保在每次读取后重置请求正文。如果不这样做,再次读取正文时可能会导致错误或数据丢失。
CustomSanitizer 没有太多规则。无论它们返回什么值,都是该字段将获取的新值。
自定义清理程序还可以通过使用 goroutine 和sync.WaitGroup 来异步处理并发操作。
go get -u github.com/bube054/ginvalidator
每当字段值无效时,都会为其记录一条错误消息。
默认错误消息是“无效值”,它根本无法描述错误的内容,因此您可能需要对其进行自定义。您可以通过
进行定制
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/hello", func(ctx *gin.Context) { person := ctx.Query("person") ctx.String(http.StatusOK, "Hello, %s!", person) }) r.Run() // listen and serve on 0.0.0.0:8080 }
有关验证器名称的完整列表,请参阅 ginvalidator 常量。
以上是使用 ginvalidator 简化 Go 中的 Gin 输入验证的详细内容。更多信息请关注PHP中文网其他相关文章!