这是我参与「第五届青训营 」伴学笔记创作活动的第 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 获取文件句柄,对文件进行存储等处理。