处理表单的输入
用户在表单中输入的数据会以键值的形式记录在请求的主体(body)中,然后以HTTP POST请求的形式发送至服务器。服务器在接收浏览器发送的表单请求后,还需要对这些数据进行语法分析(ParseForm()),从而提取出数据中记录的键值对。
先来看一个表单递交的例子,我们有如下的表单内容,命名成文件login.html(放入当前新建项目的目录里面)
<html> <head> <title></title> </head> <body> <form action="/login" method="post"> 用户名:<input type="text" name="username"> 密码:<input type="password" name="password"> <input type="submit" value="登录"> </form> </body> </html>
上面递交表单到服务器的/login
,当用户输入信息点击登录之后,会跳转到服务器的路由login
里面,我们首先要判断这个是什么方式传递过来,POST还是GET呢?是GET的话先返回界面,然后客户端提交(POST)。
package main import ( "fmt" "html/template" "log" "net/http" "strings" ) func main() { http.HandleFunc("/", sayHello) //设置访问的路由 http.HandleFunc("/login", login) //设置访问的路由 err := http.ListenAndServe(":8080", nil) //设置监听的端口 if err != nil { log.Fatal("ListenAndServe: ", err) } } func sayHello(w http.ResponseWriter, r *http.Request) { r.ParseForm() fmt.Println(r.Form) fmt.Println("Path", r.URL.Path) fmt.Println("scheme", r.URL.Scheme) for k, v := range r.Form { fmt.Println(k+":", strings.Join(v, " ")) } fmt.Fprintf(w, "Hello") } func login(w http.ResponseWriter, r *http.Request) { fmt.Println("method:", r.Method) //获取请求的方法 if r.Method == "GET" { t, _ := template.ParseFiles("login.html") log.Println(t.Execute(w, nil)) } else { //请求的是登录数据,那么执行登录的逻辑判断 fmt.Println("username:", r.Form["username"]) fmt.Println("password:", r.Form["password"]) } }
提交后:
发现空白,因为login中,没有r.ParseForm()分析表单数据,加入后:
Form
Form为url.Value类型
除了Form还有PostForm,两者的区别是Form除了有主体中的键值对数据,还有url中的(a=b&c=d)的键值对数据,而PostForm中只有主体body中的数据。
//还可以使用下面两个函数返回key对应的值,只不过两个函数只能返回一个value,即使一个key对应多个value。
func (r *Request) FormValue(key string) string
func (r *Request) PostFormValue(key string) string
验证表单的输入
必填字段
你想要确保从一个表单元素中得到一个值,例如前面小节里面的用户名,我们如何处理呢?Go有一个内置函数len
可以获取字符串的长度,这样我们就可以通过len来获取数据的长度,例如:
if len(r.Form["username"][0])==0{ //为空的处理 }
r.Form
对不同类型的表单元素的留空有不同的处理, 对于空文本框、空文本区域以及文件上传,元素的值为空值,而如果是未选中的复选框和单选按钮,则根本不会在r.Form中产生相应条目,如果我们用上面例子中的方式去获取数据时程序就会报错。所以我们需要通过r.Form.Get()
来获取值,因为如果字段不存在,通过该方式获取的是空值。但是通过r.Form.Get()
只能获取单个的值,如果是map的值,必须通过上面的方式来获取。
数字
你想要确保一个表单输入框中获取的只能是数字,例如,你想通过表单获取某个人的具体年龄是50岁还是10岁,而不是像“一把年纪了”或“年轻着呢”这种描述如果我们是判断正整数,那么我们先转化成int类型,然后进行处理:
getint,err:=strconv.Atoi(r.Form.Get("age")) if err!=nil{ //数字转化出错了,那么可能就不是数字 } //接下来就可以判断这个数字的大小范围了 if getint >100 { //太大了 }
还有一种方式就是正则匹配的方式:
if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m { return false }
对于性能要求很高的用户来说,这是一个老生常谈的问题了,他们认为应该尽量避免使用正则表达式,因为使用正则表达式的速度会比较慢。但是在目前机器性能那么强劲的情况下,对于这种简单的正则表达式效率和类型转换函数是没有什么差别的。如果你对正则表达式很熟悉,而且你在其它语言中也在使用它,那么在Go里面使用正则表达式将是一个便利的方式。
中文
有时候我们想通过表单元素获取一个用户的中文名字,但是又为了保证获取的是正确的中文,我们需要进行验证,而不是用户随便的一些输入。对于中文我们目前有两种方式来验证,可以使用 unicode
包提供的 func Is(rangeTab *RangeTable, r rune) bool
来验证,也可以使用正则方式来验证,这里使用最简单的正则方式,如下代码所示
if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m { return false }
英文
我们期望通过表单元素获取一个英文值,例如我们想知道一个用户的英文名,应该是astaxie,而不是asta谢。
我们可以很简单的通过正则验证数据:
if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m { return false }
手机号码
你想要判断用户输入的手机号码是否正确,通过正则也可以验证:
if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m { return false }
下拉菜单
如果我们想要判断表单里面<select>
元素生成的下拉菜单中是否有被选中的项目。有些时候黑客可能会伪造这个下拉菜单不存在的值发送给你,那么如何判断这个值是否是我们预设的值呢?
我们的select可能是这样的一些元素:
<select name="fruit"> <option value="apple">apple</option> <option value="pear">pear</option> <option value="banana">banana</option> </select>
那么我们可以这样来验证(遍历一个包含下拉菜单所有数据的切片):
slice:=[]string{"apple","pear","banana"} v := r.Form.Get("fruit") for _, item := range slice { if item == v { return true } } return false
单选按钮
如果我们想要判断radio按钮是否有一个被选中了,我们页面的输出可能就是一个男、女性别的选择,但是也可能一个15岁大的无聊小孩,一手拿着http协议的书,另一只手通过telnet客户端向你的程序在发送请求呢,你设定的性别男值是1,女是2,他给你发送一个3,你的程序会出现异常吗?因此我们也需要像下拉菜单的判断方式类似,判断我们获取的值是我们预设的值,而不是额外的值。
slice:=[]string{"1","2"} for _, v := range slice { if v == r.Form.Get("gender") { return true } } return false
复选框
有一项选择兴趣的复选框,你想确定用户选中的和你提供给用户选择的是同一个类型的数据。
<input type="checkbox" name="interest" value="football">足球 <input type="checkbox" name="interest" value="basketball">篮球 <input type="checkbox" name="interest" value="tennis">网球
对于复选框我们的验证和单选有点不一样,因为接收到的数据是一个slice:
slice:=[]string{"football","basketball","tennis"} a:=Slice_diff(r.Form["interest"],slice)
//r.Form["interest"]中的值如果slice中没有,则加入到a中。
if a == nil{ return true } return false
表单编码
application/x-www-form-urlencoded 表示在发送前编码所有字符(默认) multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。 text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
如果表单使用multipart/form-data编码时,表单数据将被存储在MultipartForm字段而不是以上两个字段,但url的数据存储在Form中。
MultipartForm字段
MultipartForm *multipart.Form type Form struct { Value map[string][]string File map[string][]*FileHeader } type FileHeader struct { Filename string Header textproto.MIMEHeader Size int64 content []byte tmpfile string }
type MIMEHeader map[string][]string A MIMEHeader represents a MIME-style header mapping keys to sets of values. 方法对象: (MIMEHeader): Add(key string, value string) Set(key string, value string) Get(key string) string Values(key string) []string Del(key string)
要使表单能够上传文件,首先第一步就是要添加form的enctype
属性(multipart/form-data):
<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>
在服务器端,我们增加一个handlerFunc:
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") 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.ParseMultipartForm
,里面的参数表示maxMemory
,调用ParseMultipartForm
之后,上传的文件存储在maxMemory
大小的内存里面,如果文件大小超过了maxMemory
,那么剩下的部分将存储在系统的临时文件中。我们可以通过r.FormFile
获取上面的文件句柄,然后实例中使用了io.Copy
来存储文件。
获取其他非文件字段信息的时候就不需要调用r.ParseForm
,因为在需要的时候Go自动会去调用ParseForm。而且ParseMultipartForm
调用一次之后,后面再次调用不会再有效果。
通过上面的实例我们可以看到我们上传文件主要三步处理:
- 表单中增加enctype="multipart/form-data"
- 服务端调用
r.ParseMultipartForm
,把上传的文件存储在内存和临时文件中 - 使用
r.FormFile
获取文件句柄,然后对文件进行存储等处理。
标签:http,string,Form,fmt,表单,go,return From: https://www.cnblogs.com/dadishi/p/16992263.html