在 Go 中, ​​http.Request​​​ 上的函数可以从 ​​URL​​​ 或 ​​Body​​​ 中提取数据,通过 ​​Form​​​ 字段、 ​​PostForm​​​ 字段、 ​​MultipartForm​​ 字段可以提取表单数据。这一期我们就讲一讲这几个字段。


Go Web 中的 Form_html

Form 字段

Go Web 中的 Form_html_02


Form 里面的数据是键值对(key-value对)。

一般我们先调用 ​​ParseForm​​​ 或 ​​ParseMultipartForm​​​ 方法来解析 ​​Request​​​ 。然后相应地访问 ​​Form​​​ 字段、 ​​PostForm​​​ 字段或 ​​MultipartForm​​ 字段来获取表单数据。

下面我们举个例子,首先创建一个 index.html 文件,主要用于创建表单,例子中创建了两个输入框,一个是用于输入 ​​id​​​ ,另一个用于输入 ​​name​​ :

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Go Web Form</title>
</head>
<body>
<form action="http://localhost:8080/" method="post" enctype="application/x-www-form-urlencoded">
<input type="text" name="id" />
<input type="text" name="name" />
<input type="submit" />
</form>
</body>
</html>

然后我们编写 main.go 文件,对提交的表单数据进行解析并输出:

package main

import (
"fmt"
"net/http"
)

func main() {
server := http.Server{
Addr: "localhost:8080",
Handler: nil,
}

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 解析
r.ParseForm()
// 输出
fmt.Fprintln(w, r.Form)
})

server.ListenAndServe()
}

运行程序,打开刚刚写好的 HTML 网页,在表单中输入数据,这里的 ​​id​​​ 填入的是 ​​123​​​ 而 ​​name​​​ 填入的是 ​​caizi​​ :

Go Web 中的 Form_表单_03

点击提交,转到 ​​http://localhost:8080/​​ 显示了刚刚我们表单输入的数据:

Go Web 中的 Form_字段_04

map[id:[123] name:[caizi]]

通过数据可以看出, ​​r.Form​​​ 字段其实就是一个 map ,这里面有两个 key 分别是 ​​id​​​ 和 ​​name​​​ ,对应的值是一个字符串切片,分别是 ​​[123]​​​ 和 ​​[caizi]​​ 。这里注意,每个 key 可以对应多个值,而且 map 里面数据的顺序是不一定的。

通过源码我们也可以知道, Form 是 ​​url.Values​​ 类型的:

Form url.Values

而 ​​url.Values​​ 实际上是一个 map ,其键是 string 类型的,而值是 []string 类型的:

type Values map[string][]string

Go Web 中的 Form_html

PostForm 字段

Go Web 中的 Form_html_02


如果我们只想得到某个 key 的 value ,可以使用 ​​r.Form["key"]​​​ ,其返回含有一个元素的 ​​slice​​​ ,例如刚刚上面的例子,调用 ​​r.Form["name"]​​​ 会返回 ​​["caizi"]​​ 。

上面我们也提到过,每个 key 可以对应多个 value ,如果表单和 URL 里有同样的 key ,那么它们都会放在一个 slice 里,但表单里的值会靠前,而 URL 的值会靠后。

还是上面那个例子,我们对 index.html 进行修改:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Go Web Form</title>
</head>
<body>
<form action="http://localhost:8080/?id=456" method="post" enctype="application/x-www-form-urlencoded">
<input type="text" name="id" />
<input type="text" name="name" />
<input type="submit" />
</form>
</body>
</html>

这里 URL 添加了参数, ​​http://localhost:8080/?id=456​​​ 即 ​​id=456​​ ,同样打开网页填入刚刚的数据,会有下面的结果:

Go Web 中的 Form_html_07

map[id:[123 456] name:[caizi]]

而如果只想要表单的键值对,而不想要 URL 的,可以使用 ​​PostForm​​ 字段。还是刚刚修改后的 index.html 文件,但把 main.go 中的 ​​r.Form​​​ 修改成 ​​r.PostForm​​ ,运行后还是在表单中输入之前的数据,提交后如下:

Go Web 中的 Form_表单_08

map[id:[123] name:[caizi]]

可以看到只获取了表单的键值对。

但注意 ​​PostForm​​​ 只支持 ​​enctype​​​ 为 ​​application/x-www-form-urlencoded​​​ 的情况。想要得到 ​​multipart​​​ 键值对,必须使用 ​​MultipartForm​​ 字段


Go Web 中的 Form_html

MultipartForm 字段

Go Web 中的 Form_html_02


想要使用 MultipartForm 这个字段的话,首先需要调用 ​​ParseMultipartForm​​​ 这个方法,这个方法需要一个参数,参数是需要读取数据的长度, ​​int64​​​ 类型,单位是字节数。这个方法会在必要时自动调用 ​​ParseForm​​​ 方法。​​MultipartForm​​ 只包含表单的键值对。

该方法返回类型是一个 struct 而不是 map 。这个 struct 里有两个 map :

  • 一个 map 的 key 是 string ,而 value 是 []string 。
  • 另一个 map 是空的(其 key 是 string ,而 value 是文件,用于传文件)。

接下来我们把 index.html 中的 ​​enctype​​​ 改成 ​​multipart/form-data​​ :

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Go Web Form</title>
</head>
<body>
<form action="http://localhost:8080/?id=456" method="post" enctype="multipart/form-data">
<input type="text" name="id" />
<input type="text" name="name" />
<input type="submit" />
</form>
</body>
</html>

main.go 文件中的 ​​r.ParseForm()​​​ 改成 ​​r.ParseMultipartForm(1024)​​​ ,把 ​​fmt.Fprintln(w, r.Form)​​​ 改成 ​​fmt.Fprintln(w, r.MultipartForm)​​ 。

package main

import (
"fmt"
"net/http"
)

func main() {
server := http.Server{
Addr: "localhost:8080",
Handler: nil,
}

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(1024)
fmt.Fprintln(w, r.MultipartForm)
})

server.ListenAndServe()
}

还是跟上面一样运行程序后打开 index.html 文件输入数据,提交后结果如下:

Go Web 中的 Form_表单_11

&{map[id:[123] name:[caizi]] map[]}

可以看到结果是一个结构体,里面有两个 map ,跟上面提到的一致。