# 4.1 處理表單的輸入
先來看一個表單遞交的例子,我們有如下的表單內容,命名成文件login.gtpl(放入當前新建項目的目錄里面)
<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呢?
http包里面有一個很簡單的方式就可以獲取,我們在前面web的例子的基礎上來看看怎么處理login頁面的form數據
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"strings"
)
func sayhelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() //解析url傳遞的參數,對于POST則解析響應包的主體(request body)
//注意:如果沒有調用ParseForm方法,下面無法獲取表單的數據
fmt.Println(r.Form) //這些信息是輸出到服務器端的打印信息
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Println(r.Form["url_long"])
for k, v := range r.Form {
fmt.Println("key:", k)
fmt.Println("val:", strings.Join(v, ""))
}
fmt.Fprintf(w, "Hello astaxie!") //這個寫入到w的是輸出到客戶端的
}
func login(w http.ResponseWriter, r *http.Request) {
fmt.Println("method:", r.Method) //獲取請求的方法
if r.Method == "GET" {
t, _ := template.ParseFiles("login.gtpl")
t.Execute(w, nil)
} else {
//請求的是登陸數據,那么執行登陸的邏輯判斷
fmt.Println("username:", r.Form["username"])
fmt.Println("password:", r.Form["password"])
}
}
func main() {
http.HandleFunc("/", sayhelloName) //設置訪問的路由
http.HandleFunc("/login", login) //設置訪問的路由
err := http.ListenAndServe(":9090", nil) //設置監聽的端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
通過上面的代碼我們可以看出獲取請求方法是通過`r.Method`來完成的,這是個字符串類型的變量,返回GET, POST, PUT等method信息。
login函數中我們根據`r.Method`來判斷是顯示登錄界面還是處理登錄邏輯。當GET方式請求時顯示登錄界面,其他方式請求時則處理登錄邏輯,如查詢數據庫、驗證登錄信息等。
當我們在瀏覽器里面打開`http://127.0.0.1:9090/login`的時候,出現如下界面

圖4.1 用戶登錄界面
我們輸入用戶名和密碼之后發現在服務器端是不會打印出來任何輸出的,為什么呢?默認情況下,Handler里面是不會自動解析form的,必須顯式的調用`r.ParseForm()`后,你才能對這個表單數據進行操作。我們修改一下代碼,在`fmt.Println("username:", r.Form["username"])`之前加一行`r.ParseForm()`,重新編譯,再次測試輸入遞交,現在是不是在服務器端有輸出你的輸入的用戶名和密碼了。
`r.Form`里面包含了所有請求的參數,比如URL中query-string、POST的數據、PUT的數據,所有當你在URL的query-string字段和POST沖突時,會保存成一個slice,里面存儲了多個值,Go官方文檔中說在接下來的版本里面將會把POST、GET這些數據分離開來。
現在我們修改一下login.gtpl里面form的action值`http://127.0.0.1:9090/login`修改為`http://127.0.0.1:9090/login?username=astaxie`,再次測試,服務器的輸出username是不是一個slice。服務器端的輸出如下:

圖4.2 服務器端打印接受到的信息
`request.Form`是一個url.Values類型,里面存儲的是對應的類似`key=value`的信息,下面展示了可以對form數據進行的一些操作:
v := url.Values{}
v.Set("name", "Ava")
v.Add("friend", "Jess")
v.Add("friend", "Sarah")
v.Add("friend", "Zoe")
// v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe"
fmt.Println(v.Get("name"))
fmt.Println(v.Get("friend"))
fmt.Println(v["friend"])
>**Tips**:
Request本身也提供了FormValue()函數來獲取用戶提交的參數。如r.Form["username"]也可寫成r.FormValue("username")。調用r.FormValue時會自動調用r.ParseForm,所以不必提前調用。r.FormValue只會返回同名參數中的第一個,若參數不存在則返回空字符串。
## links
* [目錄](<preface.md>)
* 上一節: [表單](<04.0.md>)
* 下一節: [驗證表單的輸入](<04.2.md>)
- 目錄
- Go環境配置
- Go安裝
- GOPATH 與工作空間
- Go 命令
- Go開發工具
- 小結
- Go語言基礎
- 你好,Go
- Go基礎
- 流程和函數
- struct
- 面向對象
- interface
- 并發
- 小結
- Web基礎
- web工作方式
- Go搭建一個簡單的web服務
- Go如何使得web工作
- Go的http包詳解
- 小結
- 表單
- 處理表單的輸入
- 驗證表單的輸入
- 預防跨站腳本
- 防止多次遞交表單
- 處理文件上傳
- 小結
- 訪問數據庫
- database/sql接口
- 使用MySQL數據庫
- 使用SQLite數據庫
- 使用PostgreSQL數據庫
- 使用beedb庫進行ORM開發
- NOSQL數據庫操作
- 小結
- session和數據存儲
- session和cookie
- Go如何使用session
- session存儲
- 預防session劫持
- 小結
- 文本文件處理
- XML處理
- JSON處理
- 正則處理
- 模板處理
- 文件操作
- 字符串處理
- 小結
- Web服務
- Socket編程
- WebSocket
- REST
- RPC
- 小結
- 安全與加密
- 預防CSRF攻擊
- 確保輸入過濾
- 避免XSS攻擊
- 避免SQL注入
- 存儲密碼
- 加密和解密數據
- 小結
- 國際化和本地化
- 設置默認地區
- 本地化資源
- 國際化站點
- 小結
- 錯誤處理,調試和測試
- 錯誤處理
- 使用GDB調試
- Go怎么寫測試用例
- 小結
- 部署與維護
- 應用日志
- 網站錯誤處理
- 應用部署
- 備份和恢復
- 小結
- 如何設計一個Web框架
- 項目規劃
- 自定義路由器設計
- controller設計
- 日志和配置設計
- 實現博客的增刪改
- 小結
- 擴展Web框架
- 靜態文件支持
- Session支持
- 表單支持
- 用戶認證
- 多語言支持
- pprof支持
- 小結
- 參考資料