日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【Go Web学习笔记】第三章 Go与表单的操作

發布時間:2023/12/20 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Go Web学习笔记】第三章 Go与表单的操作 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言:大家好,以下所有內容都是我學習韓茹老師的教程時所整理的筆記。部分內容有過刪改, 推薦大家去看原作者的文檔進行學習, 本文章僅作為個人的學習筆記,后續還會在此基礎上不斷修改。學習Go Web時應該已經熟悉Go語言基本語法以及計算機網絡的相關內容。

學習鏈接:https://www.chaindesk.cn/witbook/17/253
參考書籍:《Go Web編程》謝孟軍

第三章、表單操作

1、 處理表單的輸入

先來看一個表單遞交的例子,我們有如下的表單內容,命名成文件login.html(放入當前新建項目的目錄里面)

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>登錄頁面</title></head><body><form action="http://127.0.0.1:8080/login" method="post">用戶名:<input type="text" name="username"><br>密&nbsp&nbsp&nbsp碼:<input type="password" name="password"><br><input type="submit" value="登陸"></form></body></html>

上面遞交表單到服務器的/login,當用戶輸入信息點擊登陸之后,會跳轉到服務器的路由login里面,我們首先要判斷這個是什么方式傳遞過來的,POST還是GET呢?

http包里面有一個很簡單的方式就可以獲取,我們在前面web的例子的基礎上來看看怎么處理login頁面的form數據,創建一個go文件:demo01_loginserver.go,代碼如下:

package mainimport ("fmt""html/template""log""net/http""strings" )func sayhello(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 my route!") //這個寫入到w的是輸出到客戶端的 } func login(w http.ResponseWriter, r *http.Request) {r.ParseForm() //解析url傳遞的參數,對于POST則解析響應包的主體(request body)//注意:如果沒有調用ParseForm方法,下面無法獲取表單的數據fmt.Println("method: ", r.Method) //獲取請求的方法if r.Method == "GET" {t, _ := template.ParseFiles("login.html")t.Execute(w, nil)} else {//請求的是登陸數據,那么執行登陸的邏輯判斷fmt.Println("username: ", r.Form["username"])fmt.Println("password: ", r.Form["password"])} } func main() {http.HandleFunc("/hello", sayhello) //設置訪問的路由http.HandleFunc("/login", login) //設置訪問的路由err := http.ListenAndServe(":8080", nil) //設置監聽的端口if err != nil {log.Fatal("ListenAndServe: ", err)} }

然后打開瀏覽器,在地址欄輸入:http://127.0.0.1:8080/login

輸入用戶名和密碼后,點擊按鈕,觀察服務器端的運行結果:

如果沒有行 r.ParseForm()。我們輸入用戶名和密碼之后發現在服務器端是不會打印出來任何輸出的,為什么呢?默認情況下,Handler里面是不會自動解析form的,必須顯式的調用 r.ParseForm() 后,你才能對這個表單數據進行操作。我們添加代碼 r.ParseForm(),重新運行,再次測試輸入遞交,現在是不是在服務器端有輸出我們的輸入的用戶名和密碼了。

r.Form 里面包含了所有請求的參數,比如URL中 query-string、POST的數據、PUT的數據,所有當你在URL的 querystring 字段和POST沖突時,會保存成一個slice,里面存儲了多個值,Go官方文檔中說在接下來的版本里面將會把POST、GET這些數據分離開來。

現在我們修改一下login.html里面form的action值http://127.0.0.1:8080/login修改為

http://127.0.0.1:8080/login?username=ruby,再次測試,服務器的輸出username就是一個slice。

服務器端的輸出如下:

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"])

注意: Request本身也提供了 FormValue() 函數來獲取用戶提交的參數。如 r.Form[“username”] 也可寫成 r.FormValue(“username”)。調用 r.FormValue 時會自動調用 r.ParseForm,所以不必提前調用。r.FormValue只會返回同名參數中的第一個,若參數不存在則返回空字符串。

運行結果:

2、 驗證表單的輸入

開發Web的一個原則就是,不能信任用戶輸入的任何信息,所以驗證和過濾用戶的輸入信息就變得非常重要,我們經常會在微博、新聞中聽到某某網站被入侵了,存在什么漏洞,這些大多是是因為網站對于用戶輸入的信息沒有做嚴格的驗證引起的,所以為了編寫出安全可靠的Web程序,驗證表單輸入的意義重大。

我們平常編寫Web應用主要有兩方面的數據驗證,一個是在頁面端的js驗證(目前在這方面有很多的插件庫,比如ValidationJS插件),一個是在服務器端的驗證,我們這小節講解的是如何在服務器端驗證。

2.1 必填字段

你想要確保從一個表單元素中得到一個值,例如前面小節里面的用戶名,我們如何處理呢?Go有一個內置函數len可以獲取字符串的長度,這樣我們就可以通過len來獲取數據的長度,例如:

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

r.Form 對不同類型的表單元素的留空有不同的處理, 對于空文本框、空文本區域以及文件上傳,元素的值為空值,而如果是未選中的復選框和單選按鈕,則根本不會在 r.Form 中產生相應條目,如果我們用上面例子中的方式去獲取數據時程序就會報錯。所以我們需要通過 r.Form.Get() 來獲取值,因為如果字段不存在,通過該方式獲取的是空值。

但是通過 r.Form.Get() 只能獲取單個的值,如果是map的值,必須通過上面的方式來獲取。

2.2 數字

你想要確保一個表單輸入框中獲取的只能是數字,例如,你想通過表單獲取某個人的具體年齡是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里面使用正則表達式將是一個便利的方式。

Go實現的正則是RE2,所有的字符都是UTF-8編碼的。

2.3 中文

有時候我們想通過表單元素獲取一個用戶的中文名字,但是又為了保證獲取的是正確的中文,我們需要進行驗證,而不是用戶隨便的一些輸入。對于中文我們目前有效的驗證只有正則方式來驗證,如下代碼所示

if m, _ := regexp.MatchString(`^[\x{4e00}-\x{9fa5}]+$`, r.Form.Get("zhname")); !m {return false }

2.4 英文

我們期望通過表單元素獲取一個英文值,例如我們想知道一個用戶的英文名,應該是rubyhan,而不是ruby韓。

我們可以很簡單的通過正則驗證數據:

if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("enname")); !m{return false }

2.5 電子郵件地址

你想知道用戶輸入的一個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") }

2.6 手機號碼

你想要判斷用戶輸入的手機號碼是否正確,通過正則也可以驗證:

if m, _ := regexp.MatchString(`^(1[3|5|6|7|8][0-9]\d{8})$`, r.Form.Get("mobile")); !m {return false }

2.7 下拉菜單

如果我們想要判斷表單里面<select>元素生成的下拉菜單中是否有被選中的項目。有些時候黑客可能會偽造這個下拉菜單不存在的值發送給你,那么如何判斷這個值是否是我們預設的值呢?

我們的select可能是這樣的一些元素:

學&nbsp&nbsp&nbsp歷: <!-- selected="selected" --> <select name="xueli"><option>--請選擇--</option><option value="xiaoxue">小學</option><option value="chuzhong">初中</option><option value="gaozhong">高中</option><option value="dazhuan" >大專</option><option value="benke">本科</option><option value="shuoshi">碩士</option><option value="boshi">博士</option><option value="lieshi">烈士</option> </select>

那么我們可以這樣來驗證:

/** 驗證下拉列表*/ func checkSelect(xueli string) bool {slice := []string{"xiaoxue", "chuzhong", "gaozhong", "dazhuan", "benke", "shuoshi", "boshi", "lieshi"}for _, v := range slice {if v == xueli {return true}}return false }

2.8 單選按鈕

如果我們想要判斷radio按鈕是否有一個被選中了,我們頁面的輸出可能就是一個男、女性別的選擇,但是也可能一個15歲大的無聊小孩,一手拿著http協議的書,另一只手通過telnet客戶端向你的程序在發送請求呢,你設定的性別男值是1,女是2,他給你發送一個3,你的程序會出現異常嗎?因此我們也需要像下拉菜單的判斷方式類似,判斷我們獲取的值是我們預設的值,而不是額外的值。

<input type="radio" name="sex" value="male" checked="checked"/><input type="radio" name="sex" value="female"/><input type="radio" name="sex" value="other"/>其他

那我們也可以類似下拉菜單的做法一樣:

/** 驗證單選按鈕*/ func checkSex(sex string) bool {slice := []string{"male", "female", "other"}for _, v := range slice {if v == sex {return true}}return false }

2.9 復選框

有一項選擇興趣的復選框,你想確定用戶選中的和你提供給用戶選擇的是同一個類型的數據。

愛&nbsp&nbsp&nbsp好: <input type="checkbox" name="hobby" value="game" checked="checked"/>游戲 <input type="checkbox" name="hobby" value="girl" />女人 <input type="checkbox" name="hobby" value="money" />金錢 <input type="checkbox" name="hobby" value="power" />權利 <br />

對于復選框我們的驗證和單選有點不一樣,因為接收到的數據是一個slice:

/** 驗證復選框*/ func checkHobby(hobby []string) bool {slice := []string{"game", "girl", "money", "power"}hobby2 := Slice_diff(hobby, slice)if hobby2 == nil {return true}return false }func Slice_diff(slice1, slice2 []string) (diffslice []string) {for _, v := range slice1 {if !InSlice(v, slice2) {diffslice = append(diffslice, v)}}return }/** 判斷是一個切片中是否包含指定的數值*/ func InSlice(val string, slice []string) bool {for _, v := range slice {if v == val {return true}}return false }

2.10 身份證號碼

如果我們想驗證表單輸入的是否是身份證,通過正則也可以方便的驗證,但是身份證現在都是18位,我們可以進行如下驗證:

//驗證18位身份證,18位前17位為數字,最后一位是校驗位,可能為數字或字符X。 if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m {return false }

上面列出了我們一些常用的服務器端的表單元素驗證,希望通過這個引導入門,能夠讓大家對Go的數據驗證有所了解,特別是Go里面的正則處理。

2.11 完整代碼

1.html頁面:register.html

<!DOCTYPE html> <html><head><meta charset="utf-8"><title>驗證表單</title></head><body><h1>注冊信息</h1><form action="http://127.0.0.1:8080/register" method="post">用戶名:<input type="text" name="username" id="username" /><br />密&nbsp&nbsp&nbsp碼:<input type="password" name="pwd" id="pwd" /><br />中文名:<input type="text" name="zhname" id="zhname" /><br />英文名:<input type="text" name="enname" id="enname" /><br />年&nbsp&nbsp&nbsp齡:<input type="text" name="age" id="age" /><br />性&nbsp&nbsp&nbsp&nbsp別:<input type="radio" name="sex" value="male" checked="checked"/><input type="radio" name="sex" value="female"/><input type="radio" name="sex" value="other"/>其他<br />郵&nbsp&nbsp&nbsp箱:<input type="text" name="email" id="email" /><br />手機號碼:<input type="text" name="mobile" id="mobile" /><br />身份證號:<input type="text" name="usercard" id="usercard"><br>愛&nbsp&nbsp&nbsp好:<input type="checkbox" name="hobby" value="game" checked="checked"/>游戲<input type="checkbox" name="hobby" value="girl" />女人<input type="checkbox" name="hobby" value="money" />金錢<input type="checkbox" name="hobby" value="power" />權利<br />學&nbsp&nbsp&nbsp歷:<!--selected="selected"--><select name="xueli"><option>--請選擇--</option><option value="xiaoxue">小學</option><option value="chuzhong">初中</option><option value="gaozhong">高中</option><option value="dazhuan" >大專</option><option value="benke">本科</option><option value="shuoshi">碩士</option><option value="boshi">博士</option><option value="lieshi">烈士</option></select><br />頭&nbsp&nbsp&nbsp像:<input type="file" name="myfile" /><br />個人簡介:<br /><textarea rows="8" cols="70"></textarea><br /><!--按鈕:帶事件,不帶事件事件:發生了某一件事帶事件:按鈕被點擊,會觸發某一件事不帶事件:按鈕被點擊,頁面沒有反應。配合JavaScriptbutton:無事件image:無事件reset:有事件,清空表單數據submit:有事件,提交表單當submit按鈕被點擊,觸發form表單中action屬性的路徑(服務器地址)提交方式:get:默認詞意:獲取,獲得url?username=zhangsan&pwd=123456&sex=female..url:請求的路徑地址?前是請求路徑后本次請求提交的數據傳遞的數據:采用名值對的形式參數名=參數值&參數名=參數值。。。不安全:數據暴露了傳遞少量的數據容易亂碼post:詞意:郵政郵局數據打包之后,傳遞給服務端數據安全可以傳遞大量的數據不容易亂碼--><input type="button" value="按鈕" /><input type="reset" value="重置"/><input type="image" src="img/qq.gif" /><input type="submit" value="提交" /></form></body> </html>

2.go文件:demo02_checkform.go

package mainimport ("fmt""log""net/http""strconv""regexp" )func register(w http.ResponseWriter, r *http.Request) {r.ParseForm()//1.驗證必填字段//username := r.Form["username"][0]username := r.Form.Get("username")if len(username) == 0 {fmt.Println("用戶名不能為空!")fmt.Fprintf(w, "用戶名不能為空!") //這個寫入到w的是輸出到客戶端的}//2.驗證數字age, err := strconv.Atoi(r.Form.Get("age"))if err != nil {//數字轉化出錯了,那么可能就是不是數字fmt.Println("您輸入的不是數字!")fmt.Fprintf(w, "您輸入的不是數字!") //這個寫入到w的是輸出到客戶端的}//接下來就可以判斷這個數字的大小范圍了if age > 100 || age < 0 {//太大了或太小了fmt.Println("您輸入的年齡太大了或太小了,請輸入0-100之間的整數!")fmt.Fprintf(w, "您輸入的年齡太大了或太小了,請輸入0-100之間的整數!") //這個寫入到w的是輸出到客戶端的}//或者正則表達式if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {fmt.Println("驗證有誤,您輸入的年齡太大了或太小了!")fmt.Fprintf(w, "驗證有誤,您輸入的年齡太大了或太小了!")}//3.驗證中文if m, _ := regexp.MatchString(`^[\x{4e00}-\x{9fa5}]+$`, r.Form.Get("zhname")); !m {fmt.Println("驗證有誤,請輸入中文!")fmt.Fprintf(w, "驗證有誤,請輸入中文!")}//4. 驗證英文if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("enname")); !m {fmt.Println("驗證有誤,請輸入英文!")fmt.Fprintf(w, "驗證有誤,請輸入英文!")}//5. 郵箱if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {fmt.Println("請輸入正確郵箱地址")fmt.Fprintf(w, "驗證有誤,請輸入正確郵箱地址!")}//6. 驗證手機號if m, _ := regexp.MatchString(`^(1[3|5|6|7|8][0-9]\d{8})$`, r.Form.Get("mobile")); !m {fmt.Println("請輸入正確手機號碼")fmt.Fprintf(w, "驗證有誤,請輸入正確手機號碼!")}//7. 下拉菜單xueli := r.Form.Get("xueli")res1 := checkSelect(xueli)if !res1 {fmt.Println("請選擇正確的下拉列表!")fmt.Fprintf(w, "請選擇正確的下拉列表!")}// 8. 單選按鈕sex := r.Form.Get("sex")res2 := checkSex(sex)if !res2 {fmt.Println("請選擇正確的性別!")fmt.Fprintf(w, "請選擇正確的性別!")}// 9. 復選框hobby := r.Form["hobby"]res3 := checkHobby(hobby)if !res3 {fmt.Println("請選擇正確的愛好!")fmt.Fprintf(w, "請選擇正確的愛好!")}// 10 身份證號//驗證18位身份證,18位前17位為數字,最后一位是校驗位,可能為數字或字符X。if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m {fmt.Println("請選擇正確的身份證號!")fmt.Fprintf(w, "請選擇正確的身份證號!")}//fmt.Println("驗證成功!")//fmt.Fprintf(w, "驗證成功!")}/** 驗證下拉列表*/ func checkSelect(xueli string) bool {slice := []string{"xiaoxue", "chuzhong", "gaozhong", "dazhuan", "benke", "shuoshi", "boshi", "lieshi"}for _, v := range slice {if v == xueli {return true}}return false }/** 驗證單選按鈕*/ func checkSex(sex string) bool {slice := []string{"male", "female", "other"}for _, v := range slice {if v == sex {return true}}return false }/** 驗證復選框*/ func checkHobby(hobby []string) bool {slice := []string{"game", "girl", "money", "power"}hobby2 := Slice_diff(hobby, slice)if hobby2 == nil {return true}return false }func Slice_diff(slice1, slice2 []string) (diffslice []string) {for _, v := range slice1 {if !InSlice(v, slice2) {diffslice = append(diffslice, v)}}return }/** 判斷一個切片中是否包含指定的數值*/ func InSlice(val string, slice []string) bool {for _, v := range slice {if v == val {return true}}return false }func main() {http.HandleFunc("/register", register) //設置訪問的路由err := http.ListenAndServe(":8080", nil) //設置監聽的端口if err != nil {log.Fatal("ListenAndServe: ", err)} }

(補充:下面是自己實現的一表單:

package mainimport ("fmt""log""net/http""regexp""strconv""text/template" )func register(w http.ResponseWriter, r *http.Request) {r.ParseForm()fmt.Println("method: ", r.Method) // 獲取請求的方法if r.Method == "GET" {t, _ := template.ParseFiles("register.html")t.Execute(w, nil)} else {// 1. 驗證必填字段username := r.Form.Get("username")if len(username) == 0 {fmt.Println("用戶名不能為空!")fmt.Fprintf(w, "用戶名不能為空!")}// 2. 驗證數字age, err := strconv.Atoi(r.Form.Get("age"))if err != nil {// 數字轉換出錯,那么可能就不是數字了fmt.Println("您輸入的不是數字!")fmt.Fprintf(w, "您輸入的不是數字!")}// 接下來判斷數字的大小范圍if age > 100 || age < 0 {fmt.Println("您輸入的年齡太大或太小,請輸入0-100之間的整數")fmt.Fprintf(w, "您輸入的年齡太大或太小,請輸入0-100之間的整數")}// 或者正則表達式if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {fmt.Println("您輸入的年齡太大或太小,請輸入0-100之間的整數")fmt.Fprintf(w, "您輸入的年齡太大或太小,請輸入0-100之間的整數")}// 3. 驗證中文if m, _ := regexp.MatchString(`^[\x{4e00}-\x{9fa5}]+$`, r.Form.Get("zhname")); !m {fmt.Println("驗證有誤,請輸入中文")fmt.Println(w, "驗證有誤,請輸入中文")}// 4. 驗證英文if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("enname")); !m {fmt.Println("驗證有誤,請輸入英文")fmt.Println(w, "驗證有誤,請輸入英文")}// 5. 郵箱if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {fmt.Println("驗證有誤,請輸入正確郵箱地址")fmt.Println(w, "驗證有誤,請輸入正確郵箱地址")}// 6. 驗證手機號if m, _ := regexp.MatchString(`^(1[3|5|6|7|8][0-9]\d{8})$`, r.Form.Get("mobile")); !m {fmt.Println("請輸入正確手機號碼")fmt.Fprintf(w, "驗證有誤,請輸入正確手機號碼!")}// 7. 下拉菜單xueli := r.Form.Get("xueli")res1 := checkSelect(xueli)if !res1 {fmt.Println("請選擇正確的下拉列表!")fmt.Fprintf(w, "請選擇正確的下拉列表!")}// 8. 單選按鈕sex := r.Form.Get("sex")res2 := checkSex(sex)if !res2 {fmt.Println("請選擇正確的性別!")fmt.Fprintf(w, "請選擇正確的性別!")}// 9. 復選框hobby := r.Form["hobby"]res3 := checkHobby(hobby)if !res3 {fmt.Println("請選擇正確的愛好!")fmt.Fprintf(w, "請選擇正確的愛好!")}// 10. 身份證號if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m {fmt.Println("請選擇正確的身份證號!")fmt.Fprintf(w, "請選擇正確的身份證號!")}// 請求的是登錄數據,那么執行登錄的邏輯判斷fmt.Println("用戶名: ", r.Form["username"])fmt.Println("密碼: ", r.Form["pwd"])fmt.Println("中文名: ", r.Form["zhname"])fmt.Println("英文名: ", r.Form["enname"])fmt.Println("年齡: ", r.Form["age"])fmt.Println("性別: ", r.Form["sex"])fmt.Println("郵箱: ", r.Form["email"])fmt.Println("電話: ", r.Form["mobile"])fmt.Println("身份證號: ", r.Form["usercard"])fmt.Println("愛好: ", r.Form["hobby"])fmt.Println("學歷: ", r.Form["xueli"])fmt.Println("頭像: ", r.Form["myfile"])}}// 驗證下拉列表 func checkSelect(xueli string) bool {slice := []string{"xiaoxue", "chuzhong", "gaozhong", "dazhuan", "benke", "shuoshi", "boshi", "lieshi"}for _, v := range slice {if v == xueli {return true}}return false }// 驗證單選按鈕 func checkSex(sex string) bool {slice := []string{"male", "female", "other"}for _, v := range slice {if v == sex {return true}}return false }// 驗證復選框 func checkHobby(hobby []string) bool {slice := []string{"game", "girl", "money", "power"}hobby2 := Slice_diff(hobby, slice)return hobby2 == nil }func Slice_diff(slice1, slice2 []string) (diffslice []string) {for _, v := range slice1 {if !InSlice(v, slice2) {diffslice = append(diffslice, v)}}return }// 判斷是一個切片中是否包含指定的數值 func InSlice(val string, slice []string) bool {for _, v := range slice {if v == val {return true}}return false }func main() {http.HandleFunc("/register", register) // 設置訪問的路由err := http.ListenAndServe(":8080", nil) // 設置監聽的端口if err != nil {log.Fatal("ListenAndServe: ", err)} }

輸入信息后提交:

3、 預防跨站腳本

現在的網站包含大量的動態內容以提高用戶體驗,比過去要復雜得多。所謂動態內容,就是根據用戶環境和需要,Web應用程序能夠輸出相應的內容。動態站點會受到一種名為 “跨站腳本攻擊”(Cross Site Scripting, 安全專家們通常將其縮寫成 XSS)的威脅,而靜態站點則完全不受其影響。

攻擊者通常會在有漏洞的程序中插入JavaScript、VBScript、 ActiveX或Flash以欺騙用戶。一旦得手,他們可以盜取用戶帳戶信息,修改用戶設置,盜取/污染cookie和植入惡意廣告等。

對XSS最佳的防護應該結合以下兩種方法:一是驗證所有輸入數據,有效檢測攻擊(這個我們前面小節已經有過介紹);另一個是對所有輸出數據進行適當的處理,以防止任何已成功注入的腳本在瀏覽器端運行。

那么Go里面是怎么做這個有效防護的呢?Go的 html/template 里面帶有下面幾個函數可以幫你轉義

  • func HTMLEscape(w io.Writer, b []byte) //把b進行轉義之后寫到w
  • func HTMLEscapeString(s string) string //轉義s之后返回結果字符串
  • func HTMLEscaper(args …interface{}) string //支持多個參數一起轉義,返回結果字符串

創建go文件,demo03_template.go,代碼如下:

(補充:下面這段代碼自己實現效果與文檔不同,暫時未找到原因。)

package mainimport ("fmt""html/template""log""net/http" )func login(w http.ResponseWriter, r *http.Request) {r.ParseForm()username := r.Form.Get("username")fmt.Println("username:", template.HTMLEscapeString(username)) //輸出到服務器端fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))template.HTMLEscape(w, []byte(username)) //輸出到客戶端//fmt.Fprintf(w, username) //這個寫入到w的是輸出到客戶端的(補充:下面更改的應是這里的username) }func main() {http.HandleFunc("/login", login) //設置訪問的路由err := http.ListenAndServe(":8080", nil) //設置監聽的端口if err != nil {log.Fatal("ListenAndServe: ", err)} }

如果我們輸入的username是<script>alert()</script>,那么我們可以在瀏覽器上面看到輸出如下所示:

或者是:

func login2(w http.ResponseWriter, r *http.Request){r.ParseForm()username := r.Form.Get("username")fmt.Println(username)//進行模板解析t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)err = t.ExecuteTemplate(w, "T", username)//如果轉義失敗 拋出對應錯誤 終止程序if err != nil {log.Fatal(err)} }

運行結果也一樣:

Go的html/template包默認幫你過濾了html標簽,但是有時候你只想要輸出這個<script>alert()</script>看起

來正常的信息,該怎么處理?請使用template.HTML類型。

err = t.ExecuteTemplate(w, "T", template.HTML(username))

僅替換一行代碼即可:

瀏覽器運行結果:

(補充:自己的實現效果

4、 防止多次遞交表單

不知道你是否曾經看到過一個論壇或者博客,在一個帖子或者文章后面出現多條重復的記錄,這些大多數是因為用戶重復遞交了留言的表單引起的。由于種種原因,用戶經常會重復遞交表單。通常這只是鼠標的誤操作,如雙擊了遞交按鈕,也可能是為了編輯或者再次核對填寫過的信息,點擊了瀏覽器的后退按鈕,然后又再次點擊了遞交按鈕而不是瀏覽器的前進按鈕。當然,也可能是故意的——比如,在某項在線調查或者博彩活動中重復投票。那我們如何有效的防止用戶多次遞交相同的表單呢?

解決方案是在表單中添加一個帶有唯一值的隱藏字段。在驗證表單時,先檢查帶有該惟一值的表單是否已經遞交過了。如果是,拒絕再次遞交;如果不是,則處理表單進行邏輯處理。另外,如果是采用了Ajax模式遞交表單的話,當表單遞交后,通過javascript來禁用表單的遞交按鈕。

創建一個html的模板文件test.gtpl,添加代碼如下:

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>防止多次提交</title> </head> <body><form action="http://127.0.0.1:8080/login" method="post">用戶名:<input type="text" name="username"><br>密&nbsp&nbsp&nbsp碼:<input type="password" name="password"><br><input type="hidden" name="token" value="{{.}}"><input type="submit" value="登陸"></form> </body> </html>

創建一個go文件,demo04_server.go,代碼如下:

package mainimport ("fmt""html/template""log""net/http""time""crypto/md5""strconv""io""os" )func login(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))fmt.Println("token--->", token)t, _ := template.ParseFiles("test.gtpl")t.Execute(w, token)} else {//請求的是登陸數據,那么執行登陸的邏輯判斷r.ParseForm()token := r.Form.Get("token")if token != "" {//驗證token的合法性fmt.Println("token:", token)} else {//不存在token報錯fmt.Println("token有誤。。")}fmt.Println("username length:", len(r.Form["username"][0]))fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //輸出到服務器端fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))template.HTMLEscape(w, []byte(r.Form.Get("username"))) //輸出到客戶端} }func main() {http.HandleFunc("/login", login) //設置訪問的路由err := http.ListenAndServe(":8080", nil) //設置監聽的端口if err != nil {log.Fatal("ListenAndServe: ", err)} }

我們看到token已經有輸出值,你可以不斷的刷新,可以看到這個值在不斷的變化。這樣就保證了每次顯示form表單的時候都是唯一的,用戶遞交的表單保持了唯一性。

我們的解決方案可以防止非惡意的攻擊,并能使惡意用戶暫時不知所措,然后,它卻不能排除所有的欺騙性的動機,對此類情況還需要更復雜的工作。

5、 處理文件上傳

你想處理一個由用戶上傳的文件,比如你正在建設一個類似Instagram的網站,你需要存儲用戶拍攝的照片。這種需求該如何實現呢?

要使表單能夠上傳文件,首先第一步就是要添加form的enctype屬性,enctype屬性有如下三種情況:

application/x-www-form-urlencoded 表示在發送前編碼所有字符(默認) multipart/form-data 不對字符編碼。在使用包含文件上傳控件的表單時,必須使用該值。 text/plain 空格轉換為 "+" 加號,但不對特殊字符編碼。

新建html頁面(upload.html),html頁面代碼:

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>文件上傳</title> </head> <body><form enctype="multipart/form-data" action="http://127.0.0.1:8080/upload" method="post"><input type="file" name="uploadfile"/><br><input type="hidden" name="token" value="{{.}}"/><br><input type="submit" value="upload"/></form> </body> </html>

新建go文件(demo05_uploadserver.go),go文件代碼:

package mainimport ("fmt""html/template""log""net/http""time""crypto/md5""strconv""io""os" )func main() {http.HandleFunc("/upload", upload)err := http.ListenAndServe(":8080", nil) //設置監聽的端口if err != nil {log.Fatal("ListenAndServe: ", err)} }// 處理/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)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自動會去調用。而且 ParseMultipartForm 調用一次之后,后面再次調用不會再有效果。

通過上面的實例我們可以看到我們上傳文件主要三步處理:

  • 表單中增加 enctype=“multipart/form-data”
  • 服務端調用 r.ParseMultipartForm ,把上傳的文件存儲在內存和臨時文件中
  • 使用r.FormFile獲取文件句柄,然后對文件進行存儲等處理。
  • 文件 handler 是 multipart.FileHeader,里面存儲了如下結構信息:

    type FileHeader struct {Filename stringHeader textproto.MIMEHeader// contains filtered or unexported fields}

    我們通過上面的實例代碼打印出來上傳文件的信息如下:

    (補充:自己實現的直接報錯了。。

    )

    6、 客戶端上傳文件

    我們上面的例子演示了如何通過表單上傳文件,然后在服務器端處理文件,其實Go支持模擬客戶端表單功能支持文件上傳,詳細用法請看如下示例:

    創建一個go文件,來表示客戶端:demo06_uploadclient.go,代碼如下:

    package mainimport ("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}//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 := "./正則驗證.docx"postFile(filename, target_url) }

    以上代碼詳細展示了客戶端如何向服務器上傳一個文件,客戶端通過 multipart.Write 把文件的文本流寫入一個緩存中,然后調用http的Post方法把緩存傳到服務器。

    如果你還有其他普通字段例如 username 之類的需要同時寫入,那么可以調用 multipart 的 WriteField 方法寫很多其他類似的字段。

    總結

    以上是生活随笔為你收集整理的【Go Web学习笔记】第三章 Go与表单的操作的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。