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 語言入門很簡單:讀寫鎖