## 請求參數驗證
對Web進行驗證是一個難題。本節將探討使用閉包來支持驗證函數,并允許在初始化控制器結構時執行驗證類型以增強靈活性。
我們將在結構上執行驗證,但不會討論如何填充結構。我們可以假設通過解析JSON,從表單輸入或其他方法顯式填充數據。
### 實踐
1. 建立 controller.go:
```
package validation
// Controller 保存了驗證方法
type Controller struct {
ValidatePayload func(p *Payload) error
}
// New 使用我們的本地驗證初始化controller 它可以被覆蓋
func New() *Controller {
return &Controller{
ValidatePayload: ValidatePayload,
}
}
```
2. 建立 validate.go:
```
package validation
import "errors"
// Verror是在驗證期間發生的錯誤,我們可以將其返回給用戶
type Verror struct {
error
}
// Payload 是我們處理的內容
type Payload struct {
Name string `json:"name"`
Age int `json:"age"`
}
// ValidatePayload是我們控制器中閉包的1個實現
func ValidatePayload(p *Payload) error {
if p.Name == "" {
return Verror{errors.New("name is required")}
}
if p.Age <= 0 || p.Age >= 120 {
return Verror{errors.New("age is required and must be a value greater than 0 and less than 120")}
}
return nil
}
```
3. 建立 process.go:
```
package validation
import (
"encoding/json"
"fmt"
"net/http"
)
// Process是一個驗證post傳入的數據
func (c *Controller) Process(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
decoder := json.NewDecoder(r.Body)
defer r.Body.Close()
var p Payload
if err := decoder.Decode(&p); err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
if err := c.ValidatePayload(&p); err != nil {
switch err.(type) {
case Verror:
w.WriteHeader(http.StatusBadRequest)
// pass the Verror along
w.Write([]byte(err.Error()))
return
default:
w.WriteHeader(http.StatusInternalServerError)
return
}
}
}
```
4. 建立 main.go:
```
package main
import (
"fmt"
"net/http"
"github.com/agtorre/go-cookbook/chapter7/validation"
)
func main() {
c := validation.New()
http.HandleFunc("/", c.Process)
fmt.Println("Listening on port :3333")
err := http.ListenAndServe(":3333", nil)
panic(err)
}
```
5. 運行:
```
go run main.go
```
這會輸出
```
Listening on port :3333
```
進行請求測試:
```
$curl "http://localhost:3333/-X POST -d '{}'
name is required
$curl "http://localhost:3333/-X POST -d '{"name":"test"}'
age is required and must be a value greater than 0 and
less than 120
$curl "http://localhost:3333/-X POST -d '{"name":"test",
"age": 5}' -v
<lots of output, should contain a 200 OK status code>
```
### 說明
我們通過將閉包傳遞給控制器結構來處理驗證。這種方法的優點是我們可以在運行時模擬和替換驗證函數,因此測試變得更加簡單。 另外,我們沒有綁定到單個函數簽名,因此可以將諸如數據庫連接之類的東西傳遞給驗證函數。
另外需要關注的是返回了一個名為Verror的類型錯誤。此類型包含用于向用戶顯示的驗證錯誤消息。這種方法的一個缺點是它不能同時處理多個驗證消息。這可以通過修改Verror類型以允許更多狀態(例如,通過包含映射)來實現,以便在從ValidatePayload函數返回之前容納更多驗證錯誤。
* * * *
學識淺薄,錯誤在所難免。歡迎在群中就本書提出修改意見,以饗后來者,長風拜謝。
Golang中國(211938256)
beego實戰(258969317)
Go實踐(386056972)
- 前言
- 第一章 I/O和文件系統
- 常見 I/O 接口
- 使用bytes和strings包
- 操作文件夾和文件
- 使用CSV格式化數據
- 操作臨時文件
- 使用 text/template和HTML/templates包
- 第二章 命令行工具
- 解析命令行flag標識
- 解析命令行參數
- 讀取和設置環境變量
- 操作TOML,YAML和JSON配置文件
- 操做Unix系統下的pipe管道
- 處理信號量
- ANSI命令行著色
- 第三章 數據類型轉換和解析
- 數據類型和接口轉換
- 使用math包和math/big包處理數字類型
- 貨幣轉換和float64注意事項
- 使用指針和SQL Null類型進行編碼和解碼
- 對Go數據編碼和解碼
- Go中的結構體標簽和反射
- 通過閉包實現集合操作
- 第四章 錯誤處理
- 錯誤接口
- 使用第三方errors包
- 使用log包記錄錯誤
- 結構化日志記錄
- 使用context包進行日志記錄
- 使用包級全局變量
- 處理恐慌
- 第五章 數據存儲
- 使用database/sql包操作MySQL
- 執行數據庫事務接口
- SQL的連接池速率限制和超時
- 操作Redis
- 操作MongoDB
- 創建存儲接口以實現數據可移植性
- 第六章 Web客戶端和APIs
- 使用http.Client
- 調用REST API
- 并發操作客戶端請求
- 使用OAuth2
- 實現OAuth2令牌存儲接口
- 封裝http請求客戶端
- 理解GRPC的使用
- 第七章 網絡服務
- 處理Web請求
- 使用閉包進行狀態處理
- 請求參數驗證
- 內容渲染
- 使用中間件
- 構建反向代理
- 將GRPC導出為JSON API
- 第八章 測試
- 使用標準庫進行模擬
- 使用Mockgen包
- 使用表驅動測試
- 使用第三方測試工具
- 模糊測試
- 行為驅動測試
- 第九章 并發和并行
- 第十章 分布式系統
- 第十一章 響應式編程和數據流
- 第十二章 無服務器編程
- 第十三章 性能改進