您的位置:首页 > 理论基础 > 计算机网络

go client http post upload上传及 server 参数获取

2017-09-06 16:56 465 查看
摘要: 在写上传 客户端 http api 碰到的问题总结

go upload 遇到的问题

首先需要知道client是如何通过 http 协议 实现信息和参数的传递,以及server是怎么接受参数.

可以看两篇博客 :

Go发起Http请求及获取相关参数

golang web开发获取get、post、cookie参数

客户端 发送请求方式

client 发送请求主要使用的是 net/http 包中提供的方法来实现

tcp socket 通讯需要自己封装协议下篇总结.

http get 请求

GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),
?
分割URL和传输数据,参数之间以
&
相连.

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的区别

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参数无法传递。

如果是多个普通参数,使用
"&"
进行连接, 拼成字符串. 如
strings.NewReader("name=cjb&age=12&sex=man")


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

参考: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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  go http psot upload