Go Web编程(二)—— 表单处理 | 青训营笔记

83 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天

表单

表单是一个包含表单元素的区域。表单元素是允许用户在表单中(比如:文本域、下拉列表、单选框、复选框等等)输入信息的元素。表单使用表单标签(<form>)定义(需要了解一定的HTML)

处理表单的输入

r.ParseForm(),其中r类型是http.Request的指针,如下面的代码例子所示。这个函数用来解析URL传递的参数

Handler里面不会自动解析表单,因此必须显式调用这个函数

r.Method: 包含请求的方法

r.Form: 包含了所有请求的参数,是一个url.Values类型,存储类似于key=value的信息(可能需要了解一下JSON)

通过r.Form["username"]或者r.FormValue("username")可以获取用户提交的参数

调用 r.FormValue 时会自动调用 r.ParseForm,所以不必提前调用。r.FormValue 只会返回同名参数中的第一个,若参数不存在则返回空字符串

还可以对url.Values类型的数据进行Set("key",value)或者Add("key",value)操作

package main

import (
    "fmt"
    "html/template"
    "log"
    "net/http"
    "strings"
)

func login(w http.ResponseWriter, r *http.Request) {
    fmt.Println("method:", r.Method) // 获取请求的方法
    if r.Method == "GET" {
        t, _ := template.ParseFiles("login.gtpl")
        log.Println(t.Execute(w, nil))
    } else {
        err := r.ParseForm()   // 解析 url 传递的参数,对于 POST 则解析响应包的主体(request body)
        if err != nil {
           // handle error http.Error() for example
          log.Fatal("ParseForm: ", err)
        }
        // 请求的是登录数据,那么执行登录的逻辑判断
        r.ParseForm()
        fmt.Println("username:", r.Form["username"])
        fmt.Println("password:", r.Form["password"])
    }
}

func main() {
    http.HandleFunc("/login", login)         // 设置访问的路由
    err := http.ListenAndServe(":9090", nil) // 设置监听的端口
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

对于上述代码,打开http://127.0.0.1:9090/login, 输入用户名和密码测试, 看服务器端是否会打印

也可以直接在浏览器输入http://127.0.0.1:9090/login?username=newuser 进行测试

服务端验证表单输入

开发 Web 的一个原则就是,不能信任用户输入的任何信息,所以验证和过滤用户的输入信息就变得非常重要

Web应用主要有页面端的js验证和服务端的验证

确保从一个表单元素获得一个值

r.Form.Get(字段名)

若是map的值,则先确认是否为空值

if len(r.Form["字段名"][i] == 0 {
    //空值处理
}
获取r.Form["字段名"][i]

确保获取的是数字

可以先转化为int类型

getint,err:=strconv.Atoi(r.Form.Get("age"))
if err!=nil{
    // 数字转化出错了,那么可能就不是数字
}
//然后是对数字的处理

或者是通过正则匹配的方式(RE2)

if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {
    return false
}

确保是中文

对于中文我们目前有两种方式来验证,可以使用 unicode 包提供的 func Is(rangeTab *RangeTable, r rune) bool 来验证,也可以使用正则方式来验证

确保是Email地址

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")
}

确保是手机号

也可以通过正则表达式

确保下拉菜单是否有被选中的项目

slice:=[]string{"apple","pear","banana"}
v := r.Form.Get("fruit")
for _, item := range slice {
    if item == v {
        return true
    }
}

return false

确保输入的时间或者日期有效

使用time包

我们可以把用户的输入年月日转化成相应的时间,然后进行逻辑判断

防止多次递交表单

在表单中添加一个带有唯一值的隐藏字段,服务端验证表单时,先检查看该表单是否以及递交了,如果是,则拒绝再次递交

这个唯一值字段通常使用时间戳(MD5)来获取

Go处理文件上传

要使表单能够上传文件,首先第一步就是要添加 form 的 enctype 属性,enctype 属性有如下三种情况:

application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值
text/plain 空格转换为 "+" 加号,但不对特殊字符编码

例如 html文件如下

<html>
<head>
    <title>上传文件</title>
</head>
<body>
<form enctype="multipart/form-data" action="/upload" method="post">
  <input type="file" name="uploadfile" />
  <input type="hidden" name="token" value="{{.}}"/>
  <input type="submit" value="upload" />
</form>
</body>
</html>

服务器端


http.HandleFunc("/upload", upload)

// 处理 /upload  逻辑
func upload(w http.ResponseWriter, r *http.Request) {
    fmt.Println("method:", r.Method) // 获取请求的方法
    if r.Method == "GET" {
        crutime := time.Now().Unix()
        h := md5.New()
        io.WriteString(h, strconv.FormatInt(crutime, 10))
        token := fmt.Sprintf("%x", h.Sum(nil))

        t, _ := template.ParseFiles("upload.gtpl")
        t.Execute(w, token)
    } else {
        r.ParseMultipartForm(32 << 20)
        file, handler, err := r.FormFile("uploadfile")
        //文件handler是multipart.FileHeader类型
        if err != nil {
            fmt.Println(err)
            return
        }
        defer file.Close()
        fmt.Fprintf(w, "%v", handler.Header)
        f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)  // 此处假设当前目录下已存在test目录
        if err != nil {
            fmt.Println(err)
            return
        }
        defer f.Close()
        io.Copy(f, file)
    }
}

服务端调用r.ParseMultiparttForm把上传的文件存储在内存和临时文件中

然后使用 r.FormFile 获取文件句柄,对文件进行存储等处理。


参考资料

04.0 表单 | 第四章. 表单 |《Go Web 编程》| Go 技术论坛 (learnku.com)

io package - io - Go Packages