首先須要知道client是如何經過 http 協議 實現信息和參數的傳遞,以及server是怎麼接受參數.php
能夠看兩篇博客 :html
Go發起Http請求及獲取相關參數golang
golang web開發獲取get、post、cookie參數web
client 發送請求主要使用的是 net/http 包中提供的方法來實現編程
tcp socket 通信須要本身封裝協議下篇總結.json
?
分割URL和傳輸數據,參數之間以&
相連.如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD
。若是數據是英文字母/數字,原樣發送,若是是空格,轉換爲+,若是是中文/其餘字符,則直接把字符串用BASE64加密,得出如:%E4%BD%A0%E5%A5%BD
,其中%XX中的XX爲該符號以16進製表示的ASCII。服務器
參考:淺談HTTP中Get與Post的區別cookie
func httpGet() { //發送get 請求 resp, err := http.Get("http://www.01happy.com/demo/accept.php?id=1") if err != nil { // handle error } defer resp.Body.Close() //讀取response body, err := ioutil.ReadAll(resp.Body) if err != nil { // handle error } fmt.Println(string(body)) }
使用這個方法的話,第二個參數要設置成 application/x-www-form-urlencoded
,不然post參數沒法傳遞。app
若是是多個普通參數,使用 "&"
進行鏈接, 拼成字符串. 如 strings.NewReader("name=cjb&age=12&sex=man")
dom
func httpPost() { resp, err := http.Post("http://www.01happy.com/demo/accept.php", "application/x-www-form-urlencoded", strings.NewReader("name=cjb")) if err != nil { fmt.Println(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { // handle error } fmt.Println(string(body)) }
http.PostForm 底層依然是http.Post, 只是默認已經設置了 application/x-www-form-urlencoded
func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) { return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) }
因此在傳數據的時候可使用 url.Values{} (type Values map[string][]string
) 進行設置值
func httpPostForm() { resp, err := http.PostForm("http://www.01happy.com/demo/accept.php", url.Values{"key": {"Value"}, "id": {"123"}}) if err != nil { // handle error } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { // handle error } fmt.Println(string(body)) }
有時須要在請求的時候設置頭參數、cookie之類的數據,就可使用http.Do方法。
必需要設定Content-Type爲application/x-www-form-urlencoded
,post參數纔可正常傳遞
若是是多個普通參數,使用 "&"
進行鏈接, 拼成字符串. 如 strings.NewReader("name=cjb&age=12&sex=man")
func httpDo() { client := &http.Client{} req, err := http.NewRequest("POST", "http://www.01happy.com/demo/accept.php", strings.NewReader("name=cjb")) if err != nil { // handle error } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Cookie", "name=anny") resp, err := client.Do(req) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { // handle error } fmt.Println(string(body)) }
這裏能夠設置鏈接後讀取超市等,這個時候須要用到 http.Transport
package main import ( "fmt" "io/ioutil" "net/http" "strings" "time" ) var timeout = time.Duration(20 * time.Second) func dialTimeout(network, addr string) (net.Conn, error) { return net.DialTimeout(network, addr, timeout) } func main() { tr := &http.Transport{ //使用帶超時的鏈接函數 Dial: dialTimeout, //創建鏈接後讀超時 ResponseHeaderTimeout: time.Second * 2, } client := &http.Client{ Transport: tr, //總超時,包含鏈接讀寫 Timeout: timeout, } req, _ := http.NewRequest("GET", "http://www.haiyun.me", nil) req.Header.Set("Connection", "keep-alive") res, err := client.Do(req) if err != nil { return } defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(string(body)) for k, v := range res.Header { fmt.Println(k, strings.Join(v, "")) } }
使用代理或指定出口ip
//使用HTTP PROXY proxyUrl, err := url.Parse("http://host:port") tr := &http.Transport{ Proxy: http.ProxyURL(proxyUrl), } //指定出口IP ief, err := net.InterfaceByName("eth0") addrs, err := ief.Addrs() addr := &net.TCPAddr{ IP: addrs[0].(*net.IPNet).IP, } dia := net.Dialer{LocalAddr: addr} tr := &http.Transport{ Dial: dia.Dial, }
要發起head請求能夠直接使用http client的 Head()方法
// Head issues a HEAD to the specified URL. If the response is one of the // following redirect codes, Head follows the redirect after calling the // Client's CheckRedirect function: // // 301 (Moved Permanently) // 302 (Found) // 303 (See Other) // 307 (Temporary Redirect) func (c *Client) Head(url string) (resp *Response, err error) { req, err := NewRequest("HEAD", url, nil) if err != nil { return nil, err } return c.doFollowingRedirects(req, shouldRedirectGet) }
post 帶文件的客戶端, 須要使用 mime/multipart
包將數據封裝成一個form.
package main import ( "bytes" "fmt" "io" "io/ioutil" "mime/multipart" "net/http" "os" ) func postFile(url, filename, path, deviceType, deviceId string, filePath string) error { //打開文件句柄操做 file, err := os.Open(filePath) if err != nil { fmt.Println("error opening file") return err } defer file.Close() //建立一個模擬的form中的一個選項,這個form項如今是空的 bodyBuf := &bytes.Buffer{} bodyWriter := multipart.NewWriter(bodyBuf) //關鍵的一步操做, 設置文件的上傳參數叫uploadfile, 文件名是filename, //至關於如今還沒選擇文件, form項裏選擇文件的選項 fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename) if err != nil { fmt.Println("error writing to buffer") return err } //iocopy 這裏至關於選擇了文件,將文件放到form中 _, err = io.Copy(fileWriter, file) if err != nil { return err } //獲取上傳文件的類型,multipart/form-data; boundary=... contentType := bodyWriter.FormDataContentType() //這個很關鍵,必須這樣寫關閉,不能使用defer關閉,否則會致使錯誤 bodyWriter.Close() //這裏就是上傳的其餘參數設置,可使用 bodyWriter.WriteField(key, val) 方法 //也能夠本身在從新使用 multipart.NewWriter 從新創建一項,這個再server 會有例子 params := map[string]string{ "filename" : filename, "path" : path, "deviceType" : deviceType, "deviceId" : deviceId, } //這種設置值得彷彿 和下面再重新建立一個的同樣 for key, val := range params { _ = bodyWriter.WriteField(key, val) } //發送post請求到服務端 resp, err := http.Post(url, 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() { url := "http://localhost:8088/upload" filename := "json.zip" path := "/eagleeye" deviceType := "iphone" deviceId := "e6c5a83c5e20420286bb00b90b938d92" file := "./json.zip" //上傳的文件 postFile(url, filename, path, deviceType, deviceId, file) }
使用 go http.request 的三個屬性Form、PostForm、MultipartForm,來處理參數
r表示*http.Request類型,w表示http.ResponseWriter類型
go中參數傳遞爲值傳遞,由於會在多個地方使用到 request 中傳遞的參數,其底層是struct 因此使用*Request。而ResponseWriter 是一個 interface{} 因此無所謂指針不指針,只要傳遞進去符合接口的類型就能夠了。
r.ParseForm() r.Form.Get("filename")
詳細例子查看示例 get 請求參數
這種取法在一般狀況下都沒有問題,可是若是是以下請求則沒法取到須要的值:
<form action="http://localhost:9090/?id=1" method="POST"> <input type="text" name="id" value="2" /> <input type="submit" value="submit" /> </form>
由於r.Form包含了get和post參數,而且以post參數爲先,上例post參數和get參數都有id,因此應當會取到post參數2。雖然這種狀況並很少見,可是從嚴謹的角度來看程序上仍是應當處理這種狀況。立馬補上改進代碼:
queryForm, err := url.ParseQuery(r.URL.RawQuery) if err == nil && len(queryForm["id"]) > 0 { fmt.Fprintln(w, queryForm["id"][0]) }
Content-Type = application/x-www-form-urlencoded
r.ParseForm() //第一種方式 id := r.Form.Get("id") //第二種方式 id2 := r.PostForm.Get("id") //第三種方式,底層是r.Form id3 := r.FormValue("id") //第四種方式,底層是FormValue id4 := r.PostFormValue("id")
詳細例子查看示例 普通 post
**Content-Type=multipart/form-data **
由於須要上傳文件,因此表單enctype要設置成multipart/form-data。此時沒法經過PostFormValue來獲取值,由於golang庫裏還未實現這個方法
//由於上傳文件的類型是multipart/form-data 因此不能使用 r.ParseForm(), 這個只能得到普通post r.ParseMultipartForm(32 << 20) //上傳最大文件限制32M //文件 file, handler, err := r.FormFile("uploadfile") if err != nil { fmt.Println(err)//上傳錯誤 } defer file.Close() //普通參數同上普通post user := r.Form.Get("user")
cookie, err := r.Cookie("id") if err == nil { fmt.Fprintln(w, "Domain:", cookie.Domain) fmt.Fprintln(w, "Expires:", cookie.Expires) fmt.Fprintln(w, "Name:", cookie.Name) fmt.Fprintln(w, "Value:", cookie.Value) } r.Cookie返回*http.Cookie類型,能夠獲取到domain、過時時間、值等數據。
client :
package main import ( "net/http" ) func main() { httpGet() } func httpGet() { //發送get 請求 resp, err := http.Get("http://127.0.0.1:9090/upload?id=1&filename=test.zip") if err != nil { // handle error } defer resp.Body.Close() }
server :
package main import ( "net/http" "log" "fmt" ) func main() { http.HandleFunc("/upload", upload) err := http.ListenAndServe("127.0.0.1:9090", nil) //設置監聽的端口 if err != nil { log.Fatal("ListenAndServe: ", err) } } func upload(w http.ResponseWriter, r *http.Request) { fmt.Println(r.Method) //GET //這個很重要,必須寫 r.ParseForm() id := r.Form.Get("id") filename := r.Form.Get("filename") fmt.Println(id, filename) // 1 test.zip //第二種方式,底層是r.Form id2 := r.FormValue("id") filename2 := r.FormValue("filename") fmt.Println(id2, filename2) // 1 test.zip }
client :
package main import ( "net/http" "strings" ) func main() { httpPost() } func httpPost() { //發送get 請求 resp, err := http.Post("http://127.0.0.1:9090/upload", "application/x-www-form-urlencoded", strings.NewReader("id=1&filename=test.zip")) if err != nil { // handle error } defer resp.Body.Close() }
server :
package main import ( "net/http" "log" "fmt" ) func main() { http.HandleFunc("/upload", upload) err := http.ListenAndServe("127.0.0.1:9090", nil) //設置監聽的端口 if err != nil { log.Fatal("ListenAndServe: ", err) } } func upload(w http.ResponseWriter, r *http.Request) { fmt.Println(r.Method) //POST //這個很重要,必須寫 r.ParseForm() //第一種方式 id := r.Form.Get("id") filename := r.Form.Get("filename") fmt.Println(id, filename) // 1 test.zip //第二種方式 id2 := r.PostForm.Get("id") filename2 := r.PostForm.Get("filename") fmt.Println(id2, filename2, "===2====") // 1 test.zip //第三種方式,底層是r.Form id3 := r.FormValue("id") filename3 := r.FormValue("filename") fmt.Println(id3, filename3, "===3===") // 1 test.zip //第四種方式,底層是FormValue id4 := r.PostFormValue("id") filename4 := r.PostFormValue("filename") fmt.Println(id4, filename4, "=====4====") // 1 test.zip }
client :
//client package main import ( "fmt" "io/ioutil" "net/http" "net/url" ) func main() { httpPostSimple() } func httpPostSimple() { resp, err := http.PostForm("http://localhost:8080/send", url.Values{"value": {"postValue"}}) if err != nil { panic(err) } defer resp.Body.Close() resResult, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(resResult)) }
Server :
//Server package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/send", func(response http.ResponseWriter, request *http.Request) { request.ParseForm() result := request.Method + " " if request.Method == "POST" { result += request.PostFormValue("value") } else if request.Method == "GET" { result += request.FormValue("value") } fmt.Println(result) fmt.Println(request.Header) response.Write([]byte(result)) }) http.ListenAndServe(":8080", nil) }
參考:
https://matt.aimonetti.net/posts/2013/07/01/golang-multipart-file-upload-example/
client :
//json.zip 文件和該程序位於同一文件夾下 package main import ( "bytes" "fmt" "io" "io/ioutil" "mime/multipart" "net/http" "os" ) func postFile() error { //打開文件句柄操做 file, err := os.Open("json.zip") if err != nil { fmt.Println("error opening file") return err } defer file.Close() bodyBuf := &bytes.Buffer{} bodyWriter := multipart.NewWriter(bodyBuf) //關鍵的一步操做 fileWriter, err := bodyWriter.CreateFormFile("uploadfile", "json.zip") if err != nil { fmt.Println("error writing to buffer") return err } //iocopy _, err = io.Copy(fileWriter, file) if err != nil { return err } ////設置其餘參數 //params := map[string]string{ // "user": "test", // "password": "123456", //} // ////這種設置值得彷彿 和下面再重新建立一個的同樣 //for key, val := range params { // _ = bodyWriter.WriteField(key, val) //} //和上面那種效果同樣 //創建第二個fields if fileWriter, err = bodyWriter.CreateFormField("user"); err != nil { fmt.Println(err, "----------4--------------") } if _, err = fileWriter.Write([]byte("test")); err != nil { fmt.Println(err, "----------5--------------") } //創建第三個fieds if fileWriter, err = bodyWriter.CreateFormField("password"); err != nil { fmt.Println(err, "----------4--------------") } if _, err = fileWriter.Write([]byte("123456")); err != nil { fmt.Println(err, "----------5--------------") } contentType := bodyWriter.FormDataContentType() bodyWriter.Close() resp, err := http.Post("http://127.0.0.1:9090/upload", 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() { postFile() }
bodyWriter.Close() 這裏只能寫在發送以前,不能使用defer 去關閉, 這個很關鍵
server :
package main import ( "fmt" "log" "net/http" ) // 處理/upload 邏輯 func upload(w http.ResponseWriter, r *http.Request) { fmt.Println("method:", r.Method) //POST //由於上傳文件的類型是multipart/form-data 因此不能使用 r.ParseForm(), 這個只能得到普通post r.ParseMultipartForm(32 << 20) //上傳最大文件限制32M user := r.Form.Get("user") password := r.Form.Get("password") file, handler, err := r.FormFile("uploadfile") if err != nil { fmt.Println(err, "--------1------------")//上傳錯誤 } defer file.Close() fmt.Println(user, password, handler.Filename) //test 123456 json.zip } func main() { http.HandleFunc("/upload", upload) err := http.ListenAndServe("127.0.0.1:9090", nil) //設置監聽的端口 if err != nil { log.Fatal("ListenAndServe: ", err) } }