当前位置:网站首页>Go Web 编程入门:验证器
Go Web 编程入门:验证器
2022-06-22 12:49:00 【51CTO】
前言
网络验证可能是一个难题。 有句话在 Web 开发中流传很广的原则:
我们不能相信来自客户端用户表单的任何内容。
所以我们必须在使用这些数据之前验证所有传入数据。实现 REST API 是 Go 应用程序的典型用例。 API 接受的格式错误的数据可能会导致系统其他部分出现严重错误。
最好的情况是您的数据库有一些机制来防止存储格式错误的数据。如果不这样做,这些数据可能会导致您面向客户的应用程序出现错误和意外行为(比如 SQL 注入)。
在这篇文章中,我们将介绍如何在 Go 中验证发送到 REST API 的数据。
手动验证输入
简易的 REST API
这是一个简单的 REST API 示例,使用 gorilla/mux 包构建。它是一个很棒的 HTTP 路由器,特别是对于 REST API。 API 为一个端点提供路径 /user。为简单起见,它只接受所有用户的 HTTP GET 和创建用户的 HTTP Post。此外,它没有持久性数据库,而是用切片将用户存储在内存中。
package
main
import (
"encoding/json"
"log"
"net/http"
"strings"
"github.com/gorilla/mux"
)
type
User
struct {
ID
int
FirstName
string
LastName
string
FavouriteVideoGame
string
Email
string
}
func
main() {
router :
=
mux
.
NewRouter()
router
.
HandleFunc(
"/user",
PostUser)
.
Methods(
http
.
MethodPost)
router
.
HandleFunc(
"/user",
GetUsers)
.
Methods(
http
.
MethodGet)
log
.
Fatal(
http
.
ListenAndServe(
":8081",
router))
}
var
users
= []
User{}
var
id
=
0
func
validateEmail(
email
string)
bool {
// This is obviously not a good validation strategy for email addresses
// pretend a complex regex here
return
!
strings
.
Contains(
email,
"@")
}
func
PostUser(
w
http
.
ResponseWriter,
r
*
http
.
Request) {
user :
=
User{}
json
.
NewDecoder(
r
.
Body)
.
Decode(
&
user)
// We don't want an API user to set the ID manually
// in a production use case this could be an automatically
// ID in the database
user
.
ID
=
id
id
++
users
=
append(
users,
user)
w
.
WriteHeader(
http
.
StatusCreated)
}
func
GetUsers(
w
http
.
ResponseWriter,
r
*
http
.
Request) {
w
.
Header()
.
Add(
"Content-Type",
"application/json")
if
err :
=
json
.
NewEncoder(
w)
.
Encode(
users);
err
!=
nil {
log
.
Println(
err)
w
.
WriteHeader(
http
.
StatusInternalServerError)
return
}
}
- 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.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
现在让我们看看如何手动验证在请求正文中提供给此 API 的 POST 处理程序的输入。
手动验证输入
有时我们要求用户输入一些字段,但他们未能完成该字段。例如在上一节中,当我们需要用户名时。您可以使用 len 函数来获取字段的长度,以确保用户输入了某些内容。
假设我们想在使用 Post 处理程序创建用户时根据需要设置 FirstName、LastName 和 Email。此外,我们希望电子邮件字段是有效的电子邮件地址。一种简单的方法是手动验证字段,如下所示:
if
user
.
FirstName
==
"" {
errs
=
append(
errs,
fmt
.
Errorf(
"Firstname is required")
.
Error())
}
if
user
.
LastName
==
"" {
errs
=
append(
errs,
fmt
.
Errorf(
"LastName is required")
.
Error())
}
if
user
.
Email
==
""
||
validateEmail(
user
.
Email) {
errs
=
append(
errs,
fmt
.
Errorf(
"A valid Email is required")
.
Error())
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
完整示例:
package
main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"github.com/gorilla/mux"
)
type
User
struct {
ID
int
FirstName
string
LastName
string
FavouriteVideoGame
string
Email
string
}
func
main() {
router :
=
mux
.
NewRouter()
router
.
HandleFunc(
"/user",
PostUser)
.
Methods(
http
.
MethodPost)
router
.
HandleFunc(
"/user",
GetUsers)
.
Methods(
http
.
MethodGet)
log
.
Fatal(
http
.
ListenAndServe(
":8081",
router))
}
var
users
= []
User{}
var
id
=
0
func
validateEmail(
email
string)
bool {
// That's obviously not a good validation strategy for email addresses
// pretend a complex regex here
return
!
strings
.
Contains(
email,
"@")
}
func
PostUser(
w
http
.
ResponseWriter,
r
*
http
.
Request) {
user :
=
User{}
json
.
NewDecoder(
r
.
Body)
.
Decode(
&
user)
errs :
= []
string{}
if
user
.
FirstName
==
"" {
errs
=
append(
errs,
fmt
.
Errorf(
"Firstname is required")
.
Error())
}
if
user
.
LastName
==
"" {
errs
=
append(
errs,
fmt
.
Errorf(
"LastName is required")
.
Error())
}
if
user
.
Email
==
""
||
validateEmail(
user
.
Email) {
errs
=
append(
errs,
fmt
.
Errorf(
"A valid Email is required")
.
Error())
}
if
len(
errs)
>
0 {
w
.
Header()
.
Add(
"Content-Type",
"application/json")
w
.
WriteHeader(
http
.
StatusBadRequest)
if
err :
=
json
.
NewEncoder(
w)
.
Encode(
errs);
err
!=
nil {
}
return
}
// We don't want an API user to set the ID manually
// in a production use case this could be an automatically
// ID in the database
user
.
ID
=
id
id
++
users
=
append(
users,
user)
w
.
WriteHeader(
http
.
StatusCreated)
}
func
GetUsers(
w
http
.
ResponseWriter,
r
*
http
.
Request) {
w
.
Header()
.
Add(
"Content-Type",
"application/json")
if
err :
=
json
.
NewEncoder(
w)
.
Encode(
users);
err
!=
nil {
log
.
Println(
err)
w
.
WriteHeader(
http
.
StatusInternalServerError)
return
}
}
- 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.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
可以看到这个验证方法非常冗长。我们必须定义一个自定义函数来验证常见的东西,比如电子邮件地址。让我们看看如何改进这一点。
上面只是简单通过 validateEmail 函数验证邮箱中是否含有 @ 字符:
其实更好的方式是通过正则表达式来验证 E-mail 的有效性:
使用结构标签验证输入
在 Go 中验证结构的一种更惯用的方法是使用结构标签。有许多通过结构标签进行结构验证的包。我们将在这里使用 https://github.com/go-playground/validator:该 验证器基于标签实现结构和单个字段的值验证。

使用 go get github.com/go-playground/validator/v10 进行安装。
这不仅使我们能够使用结构标签进行验证,而且还提供了许多预定义的验证方法,例如电子邮件地址。
我们将对结构执行此验证,但不探讨如何填充结构。 我们可以假设数据将通过解析 JSON 有效负载、从表单输入显式填充或其他方法来填充。
如果您的数据需要其他验证器,请查看验证器包的文档。您需要的验证器很有可能在软件包提供的 80 多个验证器之下。
package
main
import (
"encoding/json"
"log"
"net/http"
"github.com/go-playground/validator/v10"
"github.com/gorilla/mux"
)
type
User
struct {
ID
int
`validate:"isdefault"`
FirstName
string
`validate:"required"`
LastName
string
`validate:"required"`
FavouriteVideoGame
string
Email
string
`validate:"required,email"`
}
func
main() {
router :
=
mux
.
NewRouter()
router
.
HandleFunc(
"/user",
PostUser)
.
Methods(
http
.
MethodPost)
router
.
HandleFunc(
"/user",
GetUsers)
.
Methods(
http
.
MethodGet)
log
.
Fatal(
http
.
ListenAndServe(
":8081",
router))
}
var
users
= []
User{}
var
id
=
0
func
PostUser(
w
http
.
ResponseWriter,
r
*
http
.
Request) {
user :
=
User{}
json
.
NewDecoder(
r
.
Body)
.
Decode(
&
user)
validate :
=
validator
.
New()
err :
=
validate
.
Struct(
user)
if
err
!=
nil {
validationErrors :
=
err
.(
validator
.
ValidationErrors)
w
.
Header()
.
Add(
"Content-Type",
"application/json")
w
.
WriteHeader(
http
.
StatusBadRequest)
responseBody :
=
map[
string]
string{
"error":
validationErrors
.
Error()}
if
err :
=
json
.
NewEncoder(
w)
.
Encode(
responseBody);
err
!=
nil {
}
return
}
// We don't want an API user to set the ID manually
// in a production use case this could be an automatically
// ID in the database
user
.
ID
=
id
id
++
users
=
append(
users,
user)
w
.
WriteHeader(
http
.
StatusCreated)
}
func
GetUsers(
w
http
.
ResponseWriter,
r
*
http
.
Request) {
w
.
Header()
.
Add(
"Content-Type",
"application/json")
if
err :
=
json
.
NewEncoder(
w)
.
Encode(
users);
err
!=
nil {
log
.
Println(
err)
w
.
WriteHeader(
http
.
StatusInternalServerError)
return
}
}
- 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.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
上面的 validationError.Error() 返回一个字符串,该字符串总结了结构中每个失败的验证。所以 BadRequest 响应仍然有非常详细的信息说明出了什么问题。
我们将验证更改为使用验证器包,现在根据以下规则进行验证:
- ID 字段不应该由用户设置,所以我们验证它有 int 的默认值,即 0
- FullName 和 LastName 是必需的
- 电子邮件字段是必需的,并使用预定义的电子邮件验证器进行验证
使用自定义验证器验证输入
所以现在我们使用验证器包并使用结构标签验证结构。但是我们如何验证一个无法通过库提供的标签验证的结构字段呢?
假设我们想将某些视频游戏列入黑名单。因为我们不希望我们的系统中有喜欢 PUBG 或 Fortnite 等游戏的用户。在这种情况下,我们可以定义一个自定义的 validate 标记值并让 validate 包像这样使用它:
首先我们定义一个验证函数:
然后我们用验证器实例注册函数和相应的标签。
现在我们在 User 结构的定义中添加标签。
推荐验证库
Awesome Go 项目下有用于验证的库。这里推荐如下:

- checkdigit - Provide check digit algorithms (Luhn, Verhoeff, Damm) and calculators (ISBN, EAN, JAN, UPC, etc.).
- gody - :balloon: A lightweight struct validator for Go.
- govalid - Fast, tag-based validation for structs.
- govalidator - Validators and sanitizers for strings, numerics, slices and structs.
- govalidator - Validate Golang request data with simple rules. Highly inspired by Laravel’s request validation.
- jio - jio is a json schema validator similar to joi.
- ozzo-validation - Supports validation of various data types (structs, strings, maps, slices, etc.) with configurable and extensible validation rules specified in usual code constructs instead of struct tags.
- terraform-validator - A norms and conventions validator for Terraform.
- validate - Go package for data validation and filtering. support validate Map, Struct, Request(Form, JSON, url.Values, Uploaded Files) data and more features.
- validate - This package provides a framework for writing validations for Go applications.
- validator - Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving.
- Validator - A lightweight model validator written in Go.Contains VFs:Min, Max, MinLength, MaxLength, Length, Enum, Regex.
根据介绍赶紧为自己的项目挑一个吧~
总结
验证 REST API 输入对于防止应用程序出现格式错误的数据至关重要。您可以编写自己的验证逻辑,但在大多数情况下,最好使用维护良好的验证包,例如上面的推荐。
这使您可以在结构中使用标签来配置验证,并使运行验证的逻辑保持简单。如果您有一个需要不常见验证功能的特殊用例,您仍然可以为验证器包定义自己的扩展。
- Package validatorValidation
- Awesome Go:https://awesome-go.com/validation/
- Client-side form validation
- Validate REST Input in Go
- Verification of inputs
边栏推荐
- Nine good programming habits for 10 years
- Leetcode dichotomy
- Some common SQL (version 05 and above) database maintenance scripts
- leetcode-区间dp
- 【云原生】Nacos中的事件发布与订阅--观察者模式
- leetcode 11. Container with the most water
- Double hands of daily practice of Li Kou 2day9
- "Dare not doubt the code, but have to doubt the code" a network request timeout analysis
- 高薪程序员&面试题精讲系列114之Redis缓存你熟悉吗?Redis的key如何设计?内存淘汰机制你熟悉吗?
- leetcode-子序列/子串問題
猜你喜欢

Eureka的InstanceInfoReplicator类(服务注册辅助类)

transformers VIT图像模型向量获取

Opengauss database source code analysis series articles -- detailed explanation of dense equivalent query technology

如何保护WordPress网站免受网络攻击?采取安全措施至关重要

Tables converting to latex format

“不敢去怀疑代码,又不得不怀疑代码”记一次网络请求超时分析

【云原生】Nacos中的事件发布与订阅--观察者模式

Nine good programming habits for 10 years

Stop using system Currenttimemillis() takes too long to count. It's too low. Stopwatch is easy to use!

天润云上市在即:VC大佬田溯宁大幅减持,预计将套现2.6亿港元
随机推荐
高薪程序员&面试题精讲系列114之Redis缓存你熟悉吗?Redis的key如何设计?内存淘汰机制你熟悉吗?
七牛云上传图片
Seven cattle cloud upload picture
3dMax建模笔记(一):介绍3dMax和创建第一个模型Hello world
Talk about row storage and column storage of database
Acwing 241 Loulan totem (detailed explanation of tree array)
Neuron+eKuiper 实现工业物联网数据采集、清理与反控
Leetcode subsequence / substring problem
Stephencovey's tips for efficient work for young people
A simple scientific research secret
Configuring cplex12.4 tutorial in VS2010
epoch_ Num and predict_ Conversion of num
程序员要不要选择软件人才外包公司?
12306 ticket grabbing tutorial
在CSDN写文几年,我出了“第一本书“,感恩!
Some common SQL (version 05 and above) database maintenance scripts
Acwing week 53
openGauss内核分析之查询重写
Leetcode union search set
谈谈人生风控
