Golang net/http客户端的使用
Posted 玩家_名狱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang net/http客户端的使用相关的知识,希望对你有一定的参考价值。
测试连接:有个站点,专门为了测试http、https请求,get请求为http://httpbin.org/get、post请求为http://httpbin.org/post、put请求为http://httpbin.org/put等等。发送一个请求给它,他就根据我们请求内容返回我们的请求内容给我们,这样我们就可知道我们发送的结果是什么了。
下面模拟各种方法的请求,各个方法的创建基本都差不多,但是由于Get和Post方法经常使用,所以官方帮我们做了包装,我们只需要一个函数调用就可以了,但是没有包装其他的方法,所以我们要了解基本的创建,再了解包装后的get和post。其实在使用过程中,我们常用的还是基本的创建方法,因为使用过程中我们需要定制一些参数,而包装后的做不了修改。
基本的创建
以get方法为例:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main()
// 创建一个请求对象,返回请求对象。传入参数为:请求方法,url,body要传递的数据。
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
if err != nil
panic(err)
// 虽然我们创建了请求对象,但是没有执行真正的请求。DefaultClient是Client对象,该对象包含了所有的客户端操作;Do方法执行请求,然后返回响应对象
resp, err := http.DefaultClient.Do(req)
if err != nil
panic(err)
// 退出时关闭响应连接
defer resp.Body.Close()
// 获取响应数据,iout.ReadAll方法获取Reader对象的数据,resp.Body就是Reader对象
respData, err := ioutil.ReadAll(resp.Body)
if err != nil
panic(err)
// 打印响应的数据
fmt.Printf("%s", respData)
创建请求对象时,传入参数的请求方法可以写"GET"也可以写上面那个,上面那个是net/http包里面帮我们写好的,其他方法都写好了,包中的源码为:
const (
MethodGet = "GET"
MethodHead = "HEAD"
MethodPost = "POST"
MethodPut = "PUT"
MethodPatch = "PATCH" // RFC 5789
MethodDelete = "DELETE"
MethodConnect = "CONNECT"
MethodOptions = "OPTIONS"
MethodTrace = "TRACE"
)
Post方法、Put方法、Delete方法等方法都和上面的类似,以Put方法为例
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main()
req, err := http.NewRequest(http.MethodPut, "http://httpbin.org/put", nil)
if err != nil
panic(err)
resp, err := http.DefaultClient.Do(req)
if err != nil
panic(err)
defer resp.Body.Close()
respData, err := ioutil.ReadAll(resp.Body)
if err != nil
panic(err)
fmt.Printf("%s", respData)
可以发现,只有在创建请求对象时的参数变化外,其它的都没有变。懂了基本的创建之后,再看看包装的。
包装后的Get方法
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main()
// get请求
req, err := http.Get("http://httpbin.org/get")
if err != nil
panic(err)
defer req.Body.Close()
// 读取响应数据
respData, err := ioutil.ReadAll(req.Body)
if err != nil
panic(err)
fmt.Printf("%s", respData)
可以发现其实包装后的,其实就是一个Get方法包装了http.NewRequest( )和http.DefaultClient.Do( )两个方法,因此可推测出包装后的Post方法和包装后的Get方法类似,只需要该一下名字就行。但是还需要注意一点,get请求时,传的参数是放在url中的,而post请求的参数是在Body中的。对于包装的Get方法,只有一个传入参数,但包装后的Post方法有三个传入参数。Post方法签名为:
func http.Post(url string, contentType string, body io.Reader) (resp *http.Response, err error)
如果没有要传递的参数或数据,我们只需要设置contentType为空字符串,body为nil,也可完成请求。
传递时有参数或数据
上面我们都是简单的使用请求方法,而没有传递参数或者数据,下面开始传递参数或者数据。
在学习传递参数之前,先了解一下net/url包的使用,只要我们设置好参数的键和值,它就能帮我们装换成http协议中的参数写法,如下:
package main
import (
"fmt"
"net/url"
)
func main()
params := make(url.Values)
params.Add("name", "zhong")
params.Add("age", "18")
fmt.Println(params.Encode()
// age=18&name=zhong
你可能疑问我直接写结果的样式只需要几个单词就可以了,为什么还要写这么多代码?
我们的参数中一般都是变量,像结果那样输出的话只需使用字符拼接的方法,很快就写出来了,没那么多代码。但是,在工程代码中,我们看中的是可维护性,也可以说是可读性和可修改性,如果参数很多,那么拼接后就会很长很乱,没有上面写的那么好的可读性。
URL中添加参数
在此之前还需要了解一个net/http包中URL的参数的连接函数,http包下的Request结构体嵌套了URL结构体,URL结构体下的成员变量RawQuery,代表了URL的参数。发起请求时,会自动在请求路径后加上?号,再加上参数。
package main
import (
"fmt"
"net/http"
)
func main()
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
if err != nil
return
fmt.Println(req.URL)
req.URL.RawQuery = "age=18&name=zhong"
fmt.Println(req.URL)
// http://httpbin.org/get
// http://httpbin.org/get?age=18&name=zhong
所以最终的代码为
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
// 设置参数结构并返回结果
func param() string
params := make(url.Values)
params.Add("name", "zhong")
params.Add("age", "18")
return params.Encode()
func main()
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
if err != nil
panic(err)
// 设置请求的参数
req.URL.RawQuery = param()
resp, err := http.DefaultClient.Do(req)
if err != nil
panic(err)
defer resp.Body.Close()
respData, err := ioutil.ReadAll(resp.Body)
if err != nil
return
fmt.Printf("%s", respData)
这样写之后就可以达到发起get请求,请求链接为:http://httpbin.org/get?age=18&name=zhong
所以,不管是GET还是POST或者是PUT等方法,只要URL中需要使用到参数,就可以这样写
请求头中添加或修改参数
和URL中加参数基本类似,只要修改对应的值即可,这里添加一个请求头,使用到http.Request.Header.Add( )函数
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
func main()
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
if err != nil
panic(err)
// 设置请求头参数
req.Header.Add("user-agent", "chrome666")
resp, err := http.DefaultClient.Do(req)
if err != nil
panic(err)
defer resp.Body.Close()
respData, err := ioutil.ReadAll(resp.Body)
if err != nil
return
fmt.Printf("%s", respData)
这里是添加请求头,如果是修改呢?只需要把Add函数换成Set函数即可。哪这两个有什么区别?
如果你使用Add方法添加一个 k - V 键值对请求头后,再次使用Add方法添加一个相同的K,但V不同,结果就是只有第一个K添加成功,第二个设置失败。
如果使用Set方法设置值的时候,上面的请况就是最后一个设置成功,而且Set函数也可以达到Add函数添加请求头的功能
请求体(body)中加参数
前面也提过,只是默认为nil,就是http.NewRequest( )函数的第三个参数,该参数接收的就是要传递的body数据,该参数的类型是io.Reader,也就是Reader参数。在body中添加参数,最常用的是Post方法,代码就以Post方法的写法为例:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
func param() *strings.Reader
params := make(url.Values)
params.Add("name", "zhong")
params.Add("age", "18")
return strings.NewReader(params.Encode())
func main()
req, err := http.NewRequest(http.MethodPost, "http://httpbin.org/post", param())
if err != nil
panic(err)
resp, err := http.DefaultClient.Do(req)
if err != nil
panic(err)
defer resp.Body.Close()
respData, err := ioutil.ReadAll(resp.Body)
if err != nil
return
fmt.Printf("%s", respData)
和URL中添加参数的写法差不多,只是body的数据要在创建Request对象的时候要指定,且接收的是Reader对象,因此在param函数中将字符串类型的参数转换成Reader对象。
获取响应信息
之前我们输出的都是响应包的body信息,没有输出过其它信息,如状态码、cookie、header等。如下图有这么多可选,但是他们的使用方法却不同
如Body是一个io.ReadCloser类型,因此需要使用ioutil.ReadAll( )方法获取里面的数据。
而Status是string类型、StatusCode是int类型,像这种可以直接输出
Header是一个map[string][]string类型,直接输出的话就输出全部,如果需要指定某一个就可以使用Header[“xxxx”]或者Header.Get(“xxxx”)方法取出响应头xxxx参数
文件下载
平常常下载图片、视频、压缩包等文件,一般都是使用Get请求。以下载图片为例:
package main
import (
"io"
"os"
"net/http"
)
func download(url, filename string)
// 发起请求,下载图片数据
resp, err := http.Get(url)
if err != nil
panic(err)
// 创建文件
f, err := os.Create(filename)
if err != nil
panic(err)
// 将下载的图片数据写入到文件中
_, err = io.Copy(f, resp.Body)
if err != nil
panic(err)
func main()
url := "http://i2.hdslb.com/bfs/face/11a11301fa3d0b4b3430ffeffa1f872ed9a933e1.jpg@128w_128h_1o.webp"
filename := "touxiang.png"
download(url, filename)
下载带进度条
在这之前你得了解一些知识
http.Response.ContentLength请求头能得知下载文件的大小
io.Reader对象其实是一个接口,原型如下:
type Reader interface
Read(p []byte) (n int, err error)
那么像前面那样使用io.Copy(dst io.Writer, src io.Reader)函数时,对io.Reader做了什么?
io.Copy( )函数会创建一个字节类型的切片(buf = make([]byte, size)),然后循环调用Reader对象下的Read( )方法(nr, er := src.Read(buf)), 将该字节切片数据写入到io.Writer对象中
懂得以上知识后,我们开始设计进度条的一些细节。
首先我们自己设计一个Reader对象,当然我们不可能亲自实现Read方法,我们可以使用继承的方式继承io.Reader对象,然后还可以对自己设计的Reader对象进行功能的扩充,比如增加一些变量。
增加哪些变量呢?
一个进度条是百分比的,所以要知道总文件长度和正在读取文件的长度,这样就可以计算出百分比了,所以我们就增加这两个变量,总长度由http.Response.ContentLength可知,而正在读取文件的长度可以由Read方法的返回值可知。
package main
import (
"fmt"
"io"
"net/http"
"os"
)
type Reader struct
io.Reader // 继承
AllLength int // 文件的总长度
NowLength int // 正在读取文件的长度
var printchar string = "#"
// os.Copy()函数会不断地调用该方法
func (r *Reader) Read(p []byte) (n int, err error)
// 我们不会实现Read()方法,所以再执行一次Reader对象中的Read方法
// 每次读取http.Response.Body中的数据,就会返回读取的长度
n, err = r.Reader.Read(p)
// 由于网络延迟,不是一次性下载完成,所以要统计读取过的总长度
r.NowLength += n
// 每次刷新屏幕时就增加一个#号
printchar += "#"
// 由于整数除以整数得到的是整数,所以读取过的总长度乘100再除以文件总长度
fmt.Printf("[%-100s] %d %%\\r", printchar, r.NowLength*100/r.AllLength)
return n, err
func download(url, filename string)
resp, err := http.Get(url)
if err != nil
panic(err)
f, err := os.Create(filename)
if err != nil
panic(err)
// 初始化Reader对象。把我们要写入的数据赋给io.Reader
reader := &Reader
Reader: resp.Body,
AllLength: int(resp.ContentLength),
// Copy的io.Reader参数换成了自己设计的Reader对象
_, err = io.Copy(f, reader)
if err != nil
panic(err)
func main()
url := "https://vd2.bdstatic.com/mda-madjt1deuj2r0n97/sc/cae_h264_clips/1610607610/mda-madjt1deuj2r0n97.mp4?auth_key=1621783864-0-0-ca2c58d1b1c1f0e53e433b2227bbf19f&bcevod_channel=searchbox_feed&pd=1&pt=3&abtest="
filename := "shipin.mp4"
download(url, filename)
fmt.Println("\\n")
body数据的格式
在请求的body中提交数据时,可以提交多种格式的数据。主要是form、json、文件这三种格式
form格式
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
func main()
params := make(url.Values)
params.Add("name", "zhong")
params.Add("age", "18")
resp, err := http.Post(
"http://httpbin.org/post",
"application/x-www-form-urlencoded",
strings.NewReader(params.Encode()))
if err != nil
panic(err)
defer resp.Body.Close()
respData, err := ioutil.ReadAll(resp.Body)
if err != nil
return
fmt.Printf("%s", respData)
json格式
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
type ParamJson struct
Name string `json:"name"`
Age int `json:"age"`
func main()
paramJson := &ParamJson
Name: "zhong",
Age: 18,
// 结构体转为json格式,返回的是字节切片
paramjson, _ := json.Marshal(paramJson)
resp, err := http.Post(
"http://httpbin.org/post",
"application/json",
bytes.NewReader(paramjson)) // 将字节切片转为Reader对象
if err != nil
panic(err)
defer resp.Body.Close()
respData, err := ioutil.ReadAll(resp.Body)
if err != nil
return
fmt.Printf("%s", respData)
以上是关于Golang net/http客户端的使用的主要内容,如果未能解决你的问题,请参考以下文章