Go Web 編程入門:驗證器
theme: devui-blue highlight: atom-one-dark
持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第 27 天,點擊查看活動詳情
前言
網絡驗證可能是一個難題。 有句話在 Web 開發中流傳很廣的原則:
我們不能相信來自客户端用户表單的任何內容。
所以我們必須在使用這些數據之前驗證所有傳入數據。實現 REST API 是 Go 應用程序的典型用例。 API 接受的格式錯誤的數據可能會導致系統其他部分出現嚴重錯誤。
最好的情況是您的數據庫有一些機制來防止存儲格式錯誤的數據。如果不這樣做,這些數據可能會導致您面向客户的應用程序出現錯誤和意外行為(比如 SQL 注入)。
在這篇文章中,我們將介紹如何在 Go 中驗證發送到 REST API 的數據。
手動驗證輸入
簡易的 REST API
這是一個簡單的 REST API 示例,使用 gorilla/mux
包構建。它是一個很棒的 HTTP 路由器,特別是對於 REST API。 API 為一個端點提供路徑 /user
。為簡單起見,它只接受所有用户的 HTTP GET 和創建用户的 HTTP Post。此外,它沒有持久性數據庫,而是用切片將用户存儲在內存中。
```go 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 } } ```
現在讓我們看看如何手動驗證在請求正文中提供給此 API 的 POST 處理程序的輸入。
手動驗證輸入
有時我們要求用户輸入一些字段,但他們未能完成該字段。例如在上一節中,當我們需要用户名時。您可以使用 len 函數來獲取字段的長度,以確保用户輸入了某些內容。
go
if len(r.Form["username"][0])==0{
// code for empty field
}
假設我們想在使用 Post 處理程序創建用户時根據需要設置 FirstName、LastName 和 Email。此外,我們希望電子郵件字段是有效的電子郵件地址。一種簡單的方法是手動驗證字段,如下所示:
```go 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()) } ```
完整示例:
```go 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 } } ```
可以看到這個驗證方法非常宂長。我們必須定義一個自定義函數來驗證常見的東西,比如電子郵件地址。讓我們看看如何改進這一點。
上面只是簡單通過 validateEmail
函數驗證郵箱中是否含有 @
字符:
go
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, "@")
}
其實更好的方式是通過正則表達式來驗證 E-mail 的有效性:
go
if m, _ := regexp.MatchString(`^([\w._]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
fmt.Println("no")
}else{
fmt.Println("yes")
}
使用結構標籤驗證輸入
在 Go 中驗證結構的一種更慣用的方法是使用結構標籤。有許多通過結構標籤進行結構驗證的包。我們將在這裏使用 https://github.com/go-playground/validator:
該驗證器 基於標籤實現結構和單個字段的值驗證。
使用 go get github.com/go-playground/validator/v10
進行安裝。
這不僅使我們能夠使用結構標籤進行驗證,而且還提供了許多預定義的驗證方法,例如電子郵件地址。
我們將對結構執行此驗證,但不探討如何填充結構。 我們可以假設數據將通過解析 JSON 有效負載、從表單輸入顯式填充或其他方法來填充。
如果您的數據需要其他驗證器,請查看驗證器包的文檔。您需要的驗證器很有可能在軟件包提供的 80 多個驗證器之下。
```go 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 } } ```
上面的 validationError.Error()
返回一個字符串,該字符串總結了結構中每個失敗的驗證。所以 BadRequest 響應仍然有非常詳細的信息説明出了什麼問題。
我們將驗證更改為使用驗證器包,現在根據以下規則進行驗證:
- ID 字段不應該由用户設置,所以我們驗證它有 int 的默認值,即 0
- FullName 和 LastName 是必需的
- 電子郵件字段是必需的,並使用預定義的電子郵件驗證器進行驗證
使用自定義驗證器驗證輸入
所以現在我們使用驗證器包並使用結構標籤驗證結構。但是我們如何驗證一個無法通過庫提供的標籤驗證的結構字段呢?
假設我們想將某些視頻遊戲列入黑名單。因為我們不希望我們的系統中有喜歡 PUBG 或 Fortnite 等遊戲的用户。在這種情況下,我們可以定義一個自定義的 validate 標記值並讓 validate 包像這樣使用它:
首先我們定義一個驗證函數:
go
func GameBlacklistValidator(f1 validator.FieldLevel) bool {
gameBlacklist := []string{"PUBG", "Fortnite"}
game := f1.Field().String()
for _, g := range gameBlacklist {
if game == g {
return false
}
}
return true
}
然後我們用驗證器實例註冊函數和相應的標籤。
go
...
validate := validator.New()
validate.RegisterValidation("game-blacklist", GameBlacklistValidator)
...
現在我們在 User 結構的定義中添加標籤。
go
type User struct {
ID int `validate:"isdefault"`
FirstName string `validate:"required"`
LastName string `validate:"required"`
FavouriteVideoGame string `validate:"game-blacklist"`
Email string `validate:"required,email"`
}
推薦驗證庫
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
- 一文帶你瞭解 Python 中的繼承知識點
- 如何使用 HTML 和 CSS 寫一個登錄界面
- 代碼之外:寫作是倒逼成長的最佳方式
- Redis 的快速介紹及其基本數據類型和操作
- 經久不衰的設計定律就是——不要讓我思考的設計
- 一文了解 Python 中的裝飾器
- 聊聊 Go 語言與雲原生技術
- Go Web 編程入門:驗證器
- Golang 的藝術、哲學和科學
- Django API 開發:視圖設置和路由
- Web 編程入門:什麼是Web API?
- Python 實現設計模式之工廠模式
- 好開心我進入了面試環節,那麼我該如何自我介紹?
- 鴻蒙學習筆記:利用鴻蒙JavaUI 框架的 WebView 加載在線網頁
- Go 語言入門很簡單:讀寫鎖