跳到主要内容

表单操作

一 字段校验

通过内置函数len()可以获取字符串的长度,以此可以校验参数的合法性:

if len(r.Form["username"][0])==0{
//为空的处理
}

r.Form对不同类型的表单元素的留空有不同的处理:

  • 空文本框、空文本区域以及文件上传,元素的值为空值
  • 未选中的复选框和单选按钮,则不会在r.Form中产生相应条目,如果我们用上面例子中的方式去获取数据时程序就会报错。所以我们需要通过r.Form.Get()来获取值,因为如果字段不存在,通过该方式获取的是空值。但是通过r.Form.Get()只能获取单个的值,

二 文件上传

2.1 前后端模拟上传

前端代码:

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

form的enctype属性有如下三种情况:

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

golang的后端处理代码:


// 上传文件处理路由:http.HandleFunc("/upload", upload)
func upload(w http.ResponseWriter, r *http.Request) {

// 设置上传文件能使用的内存大小,超过了,则存储在系统临时文件中
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("./upload/" + handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
io.Copy(f, file)
}

2.2 go客户端模拟上传

Go支持模拟客户端表单功能支持文件上传:

package main

import (
"bytes"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
)

func postFile(filename string, targetUrl string) error {
bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)

//关键的一步操作
fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
if err != nil {
fmt.Println("error writing to buffer")
return err
}

//打开文件句柄操作
fh, err := os.Open(filename)
if err != nil {
fmt.Println("error opening file")
return err
}
defer fh.Close()

//iocopy
_, err = io.Copy(fileWriter, fh)
if err != nil {
return err
}

contentType := bodyWriter.FormDataContentType()
bodyWriter.Close()

resp, err := http.Post(targetUrl, contentType, bodyBuf)
if err != nil {
return err
}
defer resp.Body.Close()
resp_body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Println(resp.Status)
fmt.Println(string(resp_body))
return nil
}

// sample usage
func main() {
target_url := "http://localhost:8080/upload"
filename := "./test.pdf"
postFile(filename, target_url)
}

三 防止重复提交

防止表单重复提交的方案有很多,其中之一是在表单中添加一个带有唯一值的隐藏字段:

  • 1.在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。
  • 2.将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token
  • 3.表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。

在下列情况下,服务器程序将拒绝处理用户提交的表单请求:

  • 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
  • 当前用户的Session中不存在Token(令牌)。
  • 用户提交的表单数据中没有Token(令牌)。
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="hidden" name="token" value="{{.}}">
<input type="submit" value="登陆">

在模版里面增加了一个隐藏字段token,该值通过MD5(时间戳)来确定唯一值,然后我们把这个值存储到服务器端,以方便表单提交时比对判定。

func login(w http.ResponseWriter, r *http.Request) {

r.ParseForm()
token := r.Form.Get("token")

if token != "" {
//验证token的合法性
} else {
//不存在token报错
}

// 执行具体登录业务
}