客户端:发送请求
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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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.client结构体来请求
1
2
3
4
|
import "net/http"
...
clt := http.Client{}
resp, err := clt.Get("http://wwww.baidu.com")
|
http.Post 请求
使用这个方法的话,第二个参数要设置成 application/x-www-form-urlencoded ,否则post参数无法传递。
如果是多个普通参数,使用 “&” 进行连接, 拼成字符串. 如 strings.NewReader(“name=cjb&age=12&sex=man”) 或者用url.Values.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func httpPost() {
data := url.Values{"name":{"cjb"}, "age":{"12"},"sex":{"man"}}
body := strings.NewReader(data.Encode())
resp, err := http.Post("http://www.01happy.com/demo/accept.php",
"application/x-www-form-urlencoded",
body)
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))
}
|
或者还可以
1
2
3
4
5
6
7
8
9
10
|
import (
"net/http"
"net/url"
)
...
var r http.Request
r.ParseForm()
r.Form.Add("xxx", "xxx")
body := strings.NewReader(r.Form.Encode())
http.Post("xxxx", "application/x-www-form-urlencoded", body)
|
也可以使用实例化的http client的post方法
其实本质上直接使用包的函数和实例化的http client是一样的,包的函数底层也仅仅是实例化了一个DefaultClient,然后调用的DefaultClient的方法。下面给出使用实例化的http client的post方法:
1
2
3
4
5
6
7
8
9
|
import (
"net/http"
"net/url"
)
...
data := url.Values{"start":{"0"}, "offset":{"xxxx"}}
body := strings.NewReader(data.Encode())
clt := http.Client{}
resp, err := clt.Post("xxxxxxx", "application/x-www-form-urlencoded", body)
|
还有
1
2
3
4
5
6
7
8
9
10
11
|
import (
"net/http"
"net/url"
)
...
var r http.Request
r.ParseForm()
r.Form.Add("xxx", "xxx")
body := strings.NewReader(r.Form.Encode())
clt := http.Client{}
clt.Post("xxxx", "application/x-www-form-urlencoded", body)
|
http.PostForm 请求
http.PostForm 底层依然是http.Post, 只是默认已经设置了 application/x-www-form-urlencoded
1
2
3
|
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) 进行设置值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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))
}
|
还有
1
2
3
4
5
6
7
8
|
import (
"net/http"
"net/url"
)
...
data := url.Values{"start":{"0"}, "offset":{"xxxx"}}
clt := http.Client{}
clt.PostForm("xxxx", data)
|
http.Client.Do()
如果稍微看一下源码,就会发现以上方式都是用以下这种最本质的请求方式,使用http.NewRequest函数.
有时需要在请求的时候设置头参数、cookie之类的数据,就可以使用http.Do方法。
必须要设定Content-Type为application/x-www-form-urlencoded,post参数才可正常传递
如果是多个普通参数,使用 “&” 进行连接, 拼成字符串. 如 strings.NewReader(“name=cjb&age=12&sex=man”)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//使用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,
}
|
http.Client.Head
要发起head请求可以直接使用http client的 Head()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
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)
}
|
net/http包没有封装直接使用请求带header的get或者post方法,所以,要想请求中带header,只能使用NewRequest方法。
1
2
3
4
5
6
7
8
9
10
11
12
|
import (
"net/http"
)
...
req, err := http.NewRequest("POST", "xxxxx", body)
//此处还可以写req.Header.Set("User-Agent", "myClient")
req.Header.Add("User-Agent", "myClient")
clt := http.Client{}
clt.Do(req)
|
有一点需要注意:在添加header操作的时候,req.Header.Add和req.Header.Set都可以,但是在修改操作的时候,只能使用req.Header.Set。
这俩方法是有区别的,Golang底层Header的实现是一个map[string][]string
,:
- req.Header.Set方法如果原来Header中没有值,那么是没问题的,如果有值,会将原来的值替换掉。
- req.Header.Add是在原来值的基础上,再append一个值.
例如,原来header的值是“s”,我后req.Header.Add一个”a”的话,变成了[s a]。但是,获取header值的方法req.Header.Get确只取第一个,所以,如果原来有值,重新req.Header.Add一个新值的话,req.Header.Get得到的值不变。
打印response响应
1
2
3
4
5
6
7
8
|
import (
"net/http"
"net/url"
"io/ioutil"
)
...
content, err := ioutil.ReadAll(resp.Body)
respBody := string(content)
|
之后如果想再次ioutil.ReadAll(resp.Body)的时候会发现读到的是空。它是一个io.ReadCloser接口,包含了Reader和Closer接口:
1
2
3
4
|
type ReadCloser interface {
Reader
Closer
}
|
ioutil.ReadAll()是有记录偏移量,所以会出现第二次读取读不到的情况。那么该如何解决这个问题呢?
有一个方法是再造一个io.ReadCloser,如下:
1
|
resp.Body = ioutil.NopCloser(bytes.NewBuffer(content))
|
但是上述的做法实际上覆盖了原来的Body,原来的Body是实现了Close方法的,这里直接对Body赋值,是否需要对原来的Body调用 Close方法?
1
2
3
|
bodyBytes, _ := ioutil.ReadAll(req.Body)
req.Body.Close() // 这里调用Close
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
|
在response中对body的引用有两处,req.Body 和 reqBody 传入处理程序的是req ,最后finishRequest 里关闭的是 reqBody ,所以我再处理程序里对 req.Body所做的赋值更改,是不影响reqBody的引用值的,finishRquest对reqBody的Close调用还是可以做到安全重用底层buffer的.
上述代码中的req.Body 和 response 中的 reqBody 是两个变量。初期,req.Body 和 reqBody 中存放了同一个地址。但是,当 req.body = io.NoCloser 时,只是改变了 req.Body 中的指针,而 reqBody 仍旧指向原始请求的 body,故不需要在实际代码里执行关闭。
使用cookie
Golang提供了cookie的包net/http/cookiejar
1
2
3
4
5
6
7
8
|
...
url, err := url.Parse("https://www.wukong.com/")
jar, err := cookiejar.New(nil)
jar.SetCookies(url, cookies)//这里的cookies是[]*http.Cookie
wukongClt := http.Client{Transport:nil, Jar:jar}
wukongClt.Get("xxxxx")
|
文中的cookies类型是[] *http.cookie可以自己实例化,有时候也可以从response中直接使用resp.Cookies()直接拿到。
Golang的cookie是和http.client联系在一起的,作为http.client的一个属性存在。因此,要想在Golang中使用cookie,就必须想办法构造http.client
使用代理
在Golang中使用http proxy,也必须构造自己的http.client,需要将http.client结构体的一个属性Transport自己实例化好。
当使用环境变量$http_proxy或$HTTP_PROXY作为代理时
1
2
3
4
5
|
//从环境变量$http_proxy或$HTTP_PROXY中获取HTTP代理地址
func GetTransportFromEnvironment() (transport *http.Transport) {
transport = &http.Transport{Proxy : http.ProxyFromEnvironment}
return
}
|
当使用自己搭建http代理时
参数proxy_addr即代理服务器IP端口号,例如:”http://xxx.xxx.xxx.xxx:6000“,注意,必须加上"http"
1
2
3
4
5
6
7
8
9
|
func GetTransportFieldURL(proxy_addr *string) (transport *http.Transport) {
url_i := url.URL{}
url_proxy, error := url_i.Parse(*proxy_addr)
if error != nil{
fmt.Println(error.Error())
}
transport = &http.Transport{Proxy : http.ProxyURL(url_proxy)}
return
}
|
使用的时候,首先调用函数,拿到对应的transport,即使用GetTransportFieldURL或者GetTransportFieldURL函数,然后构建自定义的http.client :
1
2
3
4
5
|
ProxyUrl := "http://xxx.xxx.xxx.xxx:6000"
transport := GetTransportFieldURL(&ProxyUrl)
clt := http.Client{Transport:transport}
clt.Get("http://www.baidu.com")
|
服务端:获取请求
使用 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请求
1
2
3
|
r.ParseForm()
r.Form.Get("filename")
|
这种取法在通常情况下都没有问题,但是如果是如下请求则无法取到需要的值:
1
2
3
4
|
<form action="http://localhost:9090/?id=1" method="POST">
<input type="text" name="id" value="2" />
<input type="submit" value="submit" />
</form>
|
在golang中,虽然使用r.FormValue可以获取query string和multipart所有内容。但是当query string和multipart有key重复时,使用FormValue只会取multipart的值,使用r.URL.Query().Get只会取得query string的值
因此使用r.URL.Query().Get来获取query string,使用r.FormValue来获取multipart不要混用
1
|
r.URL.Query().Get("filename")
|
普通的post表单请求
Content-Type = application/x-www-form-urlencoded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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库里还未实现这个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//因为上传文件的类型是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 获取
1
2
3
4
5
6
7
|
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 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
package main
import (
"fmt"
"log"
"net/http"
)
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
id := r.URL.Query().Get("id")
filename := r.URL.Query().Get("filename")
fmt.Println(id, filename) // 1 test.zip
//这个很重要,必须写
r.ParseForm()
//第二种方式,底层是r.Form
id2 := r.FormValue("id")
filename2 := r.FormValue("filename")
fmt.Println(id2, filename2) // 1 test.zip
}
|
普通 post 请求
client :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
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 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
//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 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
//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)
}
|
有文件上传
client :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
//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 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
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) //缓存区最大值
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)
}
}
|
参考:
https://www.zhihu.com/question/329045911