前言
网络验证可能是一个难题。 有句话在 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 } }
现在让我们看看如何手动验证在请求正文中提供给此 API 的 POST 处理程序的输入。
手动验证输入
有时我们要求用户输入一些字段,但他们未能完成该字段。例如在上一节中,当我们需要用户名时。您可以使用 len 函数来获取字段的长度,以确保用户输入了某些内容。
if len(r.Form["username"][0])==0{ // code for empty field }
假设我们想在使用 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()) }
完整示例:
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
函数验证邮箱中是否含有 @
字符:
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 的有效性:
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") }