go client http post upload上傳及 server 參數獲取

go upload 遇到的問題

首先須要知道client是如何經過 http 協議 實現信息和參數的傳遞,以及server是怎麼接受參數.php

能夠看兩篇博客 :html

Go發起Http請求及獲取相關參數golang

golang web開發獲取get、post、cookie參數web

客戶端 發送請求方式

client 發送請求主要使用的是 net/http 包中提供的方法來實現編程

tcp socket 通信須要本身封裝協議下篇總結.json

http get 請求

  1. GET請求的數據會附在URL以後(就是把數據放置在HTTP協議頭中), ?分割URL和傳輸數據,參數之間以&相連.
  2. GET方式提交的數據最多隻能是1024字節,理論上POST沒有限制

如: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))
}

普通 http post 請求

http.Post 方式

使用這個方法的話,第二個參數要設置成 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.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))

}

更復雜狀況 http.Client Do()

有時須要在請求的時候設置頭參數、cookie之類的數據,就可使用http.Do方法。

必需要設定Content-Typeapplication/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,
}

參考:GO HTTP client客戶端使用

只發送head

要發起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請求

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,來處理參數

  • Form:存儲了post、put和get參數,在使用以前須要調用ParseForm方法。
  • PostForm:存儲了post、put參數,在使用以前須要調用ParseForm方法。
  • MultipartForm:存儲了包含了文件上傳的表單的post參數,在使用前須要調用ParseMultipartForm方法。

r表示*http.Request類型,w表示http.ResponseWriter類型

go中參數傳遞爲值傳遞,由於會在多個地方使用到 request 中傳遞的參數,其底層是struct 因此使用*Request。而ResponseWriter 是一個 interface{} 因此無所謂指針不指針,只要傳遞進去符合接口的類型就能夠了。

get 參數獲取

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

普通的post表單請求

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

有文件上傳 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 獲取

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、過時時間、值等數據。

示例

get 請求參數

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

}

普通 post 請求

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
}

普通post 使用 PostForm

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)
	}
}

參考例子網站

golang web開發獲取get、post、cookie參數

go http 服務器編程

Go發起Http請求及獲取相關參數

http://stackoverflow.com/questions/20205796/golang-post-data-using-the-content-type-multipart-form-data

相關文章
相關標籤/搜索