- 开发无止境 -
Data: 2017-05-05 18:34:14Form: JournalClick: 19
前面两篇文章分别介绍了 Request
与 Respone
,了解知道了HTTP请求与响应的大致结构以及所需的信息。那 Request
与 Respone
之间是如何通过网络进行交互的呢,这时候就需要Client
与Server
来协助与处理了,此篇文章重点介绍Client
部分。
Client
这里顾名思义就是HTTP
客户端,用于发送HTTP
请求( Request
) 并获得响应Respone
。
下面我们来详细介绍下在Go
语言的net/http
包中,Client
是如何被定义以及使用的。
Go语言中的http.Client
结构体是用于发送HTTP
请求并返回响应的组件。它的定义如下:
type Client struct {
Transport RoundTripper
CheckRedirect func(req *Request, via []*Request) error
Jar CookieJar
Timeout time.Duration
}
下面对各个字段进行分别说明:
一个http.RoundTripper
接口类型的对象,只包含一个方法RoundTrip
,它接受一个*http.Request
类型的参数,表示HTTP请求,返回一个*http.Response
类型的响应和一个错误对象,该方法的作用是发送HTTP
请求并返回响应,同时处理可能出现的传输错误,如超时、连接错误、重定向等。
http.RoundTripper
的默认实现是http.Transport
,该实现使用TCP
连接池,支持HTTP/1.1
、HTTP/2
协议,同时还支持HTTPS
、代理、压缩和连接复用等特性。如果需要更灵活地控制HTTP
请求的传输过程,可以自定义实现http.RoundTripper
接口,并将其传递给http.Client
的Transport
字段。
一个函数类型,用于控制HTTP
重定向。默认情况下,http.DefaultCheckRedirect
允许自动跟随HTTP
重定向。
一个http.CookieJar
接口类型的对象,用于管理HTTP cookie
。默认情况下,http.DefaultCookieJar
使用net/http/cookiejar
包中的默认cookie
实现。
一个time.Duration
类型的对象,用于控制HTTP
请求的超时时间。默认情况下,如果该字段没有设置超时时间,即无限期等待响应。
创建一个 Client
也很简单,最简单的创建如下:
Client := &http.Client{}
一行代码搞定,当然也可以带上你自己所需要的参数来创建Client
,比如使用http.Client
的 Timeout
字段创建一个有超时时间的客户端:
Client := &http.Client{
Timeout: 15 * time.Second,
}
有一些更细粒度的超时控制:
Client := &http.Client{
Transport: &Transport{
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
代码当中一些参数,下面列出解释以便理解:
TCP
连接的时间TLS
握手的时间response header
的时间client
在发送包含 Expect: 100-continue
的header
到收到继续发送body
的response
之间的时间等待使用 http.Client
进行发送HTTP
请求以及返回响应,基本流程如下:
http.Client
对象。首先,我们需要创建一个http.Client
对象。可以通过http.DefaultClient
使用默认的HTTP客户端,也可以通过手动创建一个新的http.Client
对象,以便自定义其参数。HTTP
请求。有了http.Client
对象后,我们需要创建一个HTTP
请求。在Request
章节中,我们讲述到http.NewRequest
函数,我们可以通过该函数创建一个新的请求对象,并设置请求URL
、方法、请求体等参数。HTTP
请求。有了请求对象后,将请求对象传递给http.Client
的Do
方法,以便发送HTTP
请求。Do
方法返回一个响应对象,其中包含服务器的响应信息,如状态码、响应头和响应体等。HTTP
响应。我们可以使用响应对象中的方法和属性,如resp.StatusCode
、resp.Header
、resp.Body
等,处理服务器的响应。通常,我们需要读取响应体的内容,并将其解析为合适的数据类型,如JSON
或XML
。HTTP
响应。获取响应后,我们需要确保关闭HTTP
响应的主体。可以使用defer resp.Body.Close()
语句在函数退出时自动关闭响应体,以避免内存泄漏。以下是一个简单的示例代码,演示了使用http.Client
发送HTTP GET
请求的基本流程:
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
// 创建http.Client对象
client := &http.Client{}
// 创建HTTP请求
req, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
panic(err)
}
// 发送HTTP请求
resp, err := client.Do(req)
if err != nil {
panic(err)
}
// 处理HTTP响应
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
上述代码仅仅是以HTTP GET
请求为例子作说明重点是突出请求的标准步骤,更多灵活的组合操作马上进行讲解。
一般情况下,我们并不需要自定义http.Client
来控制控制HTTP
请求的行为和配置,使用net/http
包中默认的 http.DefaultClient
即可,http.DefaultClient
在 client.go 文件中是这样定义的:
var DefaultClient = &Client{}
可以看出默认的http.DefaultClient
并没有设置Client
任何属性值,但是如果我们需要设置HTTP
请求的超时时间、代理、连接池等选项,可能就需要我们自己去定义和创建http.Client
了。
根据Client结构体内容,我们知道http.Client
拥有 Transport
、CheckRedirect
、 Jar
、Timeout
四个属性字段,详细介绍如下:
它用于设置 HTTP
客户端的超时时间,是一个 time.Duration
类型的值,表示客户端在发送请求后等待服务器响应的最大时间。如果在这个时间内服务器没有响应,客户端会放弃请求并返回一个错误。这个超时时间是一个全局的设置,对于所有的 HTTP
请求都生效。
默认情况下,http.Client.Timeout
的值是零,表示没有超时限制。下面是创建一个10秒超时时间的客户端示例:
client := &http.Client{
Timeout: 10 * time.Second,
}
它是一个 http.CookieJar
接口类型的值,表示 HTTP
客户端使用的 cookie
容器。这个容器会自动存储服务器发送给客户端的 cookie
,并在后续的 HTTP
请求中自动发送这些 cookie
给服务器。
默认情况下,http.Client.Jar
是空的,这意味着 HTTP
客户端不会发送任何 cookie
给服务器。
可以创建一个自定义的 cookie
容器,并将其赋值给 http.Client.Jar
属性,例如:
jar, err := cookiejar.New(nil)
if err != nil {
// handle error
}
client := &http.Client{
Jar: jar,
}
它是一个可选的回调函数,用于在 HTTP
客户端进行重定向时决定是否要遵循该重定向。
默认情况下,如果不设置 CheckRedirect
函数,HTTP
客户端会遵循所有的重定向。对于 http.Get
和 http.Head
等高级别的 HTTP 请求函数,它们默认使用一个简单的 CheckRedirect
函数,该函数会在重定向次数超过 10
次时返回一个 http.ErrUseLastResponse
错误。
可以通过自定义 http.Client
的 CheckRedirect
函数来控制 HTTP
客户端的重定向行为。下面是一个简单的例子:
func CheckRedirect(eq http.Request, via []http.Request) error {
if len(via) >= 2 {
return fmt.Errorf("too many redirects")
} return nil
}
func main() {
client := &http.Client{
CheckRedirect: CheckRedirect,
}
}
上述代码重新定义了重定向函数,设定了只允许重定向2次以免出现重定向循环。
它表示了 http.Client
使用的网络传输层。默认情况下,http.Client
使用的是 http.DefaultTransport
,它是一个基于 TCP
的传输层。
http.Client.Transport
是一个接口类型,它定义了如下两个方法:
RoundTrip(req *Request) (*Response, error)
:执行一个 HTTP 请求并返回响应结果或者错误。CancelRequest(req *Request)
:取消一个正在执行的请求如果你需要创建自己的传输层,你需要实现 http.RoundTripper
接口。这个接口只有一个方法:
RoundTrip(req *Request) (*Response, error)
:执行一个 HTTP 请求并返回响应结果或者错误。解释完 http.Client
得几个属性后,我们通过下面几个例子来了解和理解如何进行自定义 http.Client
:
(1) 通过 Transport
字段自定义传输层
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
client := &http.Client{
Transport: transport,
}
resp, err := client.Get("https://example.com")
上述代码自定义一个使用了代理服务器和自签名证书的的传输层。
(2) 通过自定义函数来进行重新制定重定向规则
func userCheckRedirect(req *http.Request, via []*http.Request) error {
//只能执行3次重定向
if len(via) >= 3 {
return errors.New("stopped after 3 redirects")
}
return nil
}
client := &http.Client{
CheckRedirect: userCheckRedirect,
}
通过自定义函数 userCheckRedirect
来控制重定向次数最多为3次,防止无限循环。如果不进行自定义函数,则默认重定向次数为 10
次。
例子就简单举这几个把,完成了 http.Client
自定义,下面我们再看看 http.Request
的自定义。
相当于 http.Client
来说,http.Request
的自定义使用频率更高,更为普遍。
http.Request
它包含了请求的方法、URL
、Header
以及 Body
等信息。我们可以创建一个 http.Request
对象并设置它的http.Request.Header
、 http.Request.Body
、 http.Request.URL
等属性。
在 net/http(一)--Request
章节中讲到过,创建一个新的 http.Request
, 使用的是 http.NewRequest
函数,下面将以代码示例形式讲述各种场景的自定义Request
创建:
(1)自定义请求 Header
:
req, err := http.NewRequest("GET", "https://www.example.com", nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer my-token")
从上代码可以看出可以使用 Request.Header.Set
方法来创建请求头。
(2) 自定义参数:
query := url.Values{}
query.Add("q", "golang")
query.Add("page", "1")
url := &url.URL{
Scheme: "https",
Host: "www.example.com",
Path: "/search",
RawQuery: query.Encode(),
}
req, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
log.Fatal(err)
}
通过 对url.Values
类型数据的添加,并在最后进行 Encode()
处理后赋值给url.URL.RawQuery
字段,这样就可以完成URL
参数的添加。
(3) 自定义 Basic
认证:
eq, err := http.NewRequest("GET", "https://www.example.com", nil)
if err != nil {
log.Fatal(err)
}
username := "my-username"
password := "my-password"
auth := username + ":" + password
base64Encoded := base64.StdEncoding.EncodeToString([]byte(auth))
req.Header.Set("Authorization", "Basic "+base64Encoded)
Basic 认证是比较常见的API调用认证,此处是通过设置请求头的 Authorization完成设置。
(4) 使用 Cookie :
cookie := &http.Cookie{
Name: "session_id",
Value: "my-session-id",
}
// Create the request
url := "https://www.example.com/api/v1"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Fatal(err)
}
req.AddCookie(cookie)
Cookie 在 HTTP 请求中属实常见,代码中通过 Request.AddCookie
方法来设置cookie。
(5) 发送POST表单数据:
url := "https://www.example.com/api/v1/posts"
params := []string{"xjx", "zzz"}
req, err := http.NewRequest("POST", url, nil)
if err != nil {
log.Fatal(err)
}
data := url.Values{}
data.Set("title", title)
data.Set("content", content)
for _, tag := range params {
data.Add("tags", tag)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Body = ioutil.NopCloser(strings.NewReader(data.Encode()))
POST表单数据一般通过设置请求体来发送,而请求体(Request Body
)的数据设置一般跟 请求头的 Content-Type
值相关。
下面列举常见的 Content-Type
构造请求体例子:
[1] application/x-www-form-urlencoded
该类型Request Body
数据一般跟 自定义参数类型,通过将需要发送的表单数据通过url.Values
格式添加并Encode
进行加编码,不同的是最后需要转化为*Reader对象。示例:
url := "http://www.example.com"
contentType := "application/x-www-form-urlencoded"
data := "key1=value1&key2=value2"
requestBody := strings.NewReader(data)
req, _ := http.NewRequest("POST", url, requestBody)
req.Header.Set("Content-Type", contentType)
也可以
url := "http://www.example.com"
contentType := "application/x-www-form-urlencoded"
data := "key1=value1&key2=value2"
requestBody := strings.NewReader(data)
req, _ := http.NewRequest("POST", url, nil)
req.Header.Set("Content-Type", contentType)
req.Body = io.NopCloser(requestBody)
[2]application/json
该类型Request Body
数据是json
格式,非url.Values
类型,其他操作方式差不多,直接看示例:
url := "http://www.example.com"
contentType := "application/json"
data := `{"key1":"xjx","key2":18}`
requestBody := strings.NewReader(data)
req, _ := http.NewRequest("POST", url, requestBody)
req.Header.Set("Content-Type", contentType)
[3] application/xml
该类型Request Body
数据是xml
格式或者[]byte
格式,如果是字符串形式跟JSON
处理一致,[]byte
类型如下:
url := "http://www.example.com"
contentType := "application/xml"
data := []byte(`<?xml version="1.0" encoding="UTF-8"?> <root> <name>John Doe</name> <email>john.doe@example.com</email> </root>`)
requestBody := bytes.NewBuffer(data)
req, _ := http.NewRequest("POST", url, requestBody)
req.Header.Set("Content-Type", contentType)
[4] multipart/form-data
该类型一般用于上传文件,该类型表单数据一般通过二进制传输,我们转为 []byte或者string类型即可,示例如下:
url := "http://www.example.com"
contentType := "multipart/form-data"
data, _ := ioutil.ReadFile("post.txt")
requestBody := bytes.NewReader(data)
req, _ := http.NewRequest("POST", url, requestBody)
req.Header.Set("Content-Type", contentType)
这些例子涵盖了 http.Request
的一些实际场景自定义使用,包括添加请求头、发送 GET
请求参数和发送 POST
表单数据等。你可以根据自己的需求进行更多自定义组合。
下面我们介绍一些使用 http.Client
自带的函数(比如http.Client.Get/http.Get
、http.Client.Post/http.Post
、http.Client.PostForm/http.PostForm
等)发送HTTP
请求操作,这些自带函数基本都是对标请求准流程进行了封装以适应不同场景简便使用。
net/http
包的client.go
文件提供了自带函数来简便的调用Client
通过GET
、POST
等方式请求HTTP
,下面来简单举例说明。
使用net/http
包编写一个简单的发送HTTP
请求的Client
端,可以使用 http.Get
或者 http.Client.Get
函数,这两函数本质是一样的,源码如下:
func Get(url string) (resp *Response, err error) {
return DefaultClient.Get(url)
}
func (c *Client) Get(url string) (resp *Response, err error) {
req, err := NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
从上面代码可以看出, http.Get
最终本质还是调用了 http.Client.Get
函数来发送HTTP
请求,唯一的区别就是 http.Get
使用了默认的DefaultClient
来作为Client
从而可以不用自主创建一个新的Client
,而http.Client.Get
则需要自行创建一个新的Client
(类似 $Client = $&http.Client{}
)来调用。
下面看一个 http.Get
函数无参数请求HTTP示例代码如下:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
//Get方式获取URL的展示信息
resp, err := http.Get("https://www.qq.com")
if err != nil {
fmt.Println("get url failed, err:", err)
} else {
defer resp.Body.Close()
//读取body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read from resp.Body failed,err:", err)
} else {
fmt.Println(string(body))
}
}
}
再来一个关于有参数的HTTP请求示例代码:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
func main() {
apiUrl := "http://www.example.com"
//解析URl字符串获取URL对象
u, err := url.ParseRequestURI(apiUrl)
if err != nil {
fmt.Printf("parse url requestUrl failed,err:%v\n", err)
}
//添加参数
params := url.Values{}
params.Add("age", "10")
params.Add("name", "xjx")
//将参数URL化,生成结果类似:bar=baz&foo=quux格式并赋值给URL.RawQuery
u.RawQuery = params.Encode()
resp, err := http.Get(u.String())
if err != nil {
fmt.Println("get url failed, err:", err)
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read from resp.Body failed,err:", err)
} else {
fmt.Println(string(body))
}
}
}
简易GET
请求大致就是这样了,如果需要更复杂的则需要自定义请求来构造相关的Request
信息了。
上面演示了使用net/http
包发送GET
请求的示例,而发送POST
请求则可以使用 http.Post
或者 http.Client.Post
函数,源码如下:
func Post(url, contentType string, body io.Reader) (resp *Response, err error) {
return DefaultClient.Post(url, contentType, body)
}
func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) {
req, err := NewRequest("POST", url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", contentType)
return c.Do(req)
}
从上面代码可以看出, http.Post
最终本质还是调用了 http.Client.Post
函数来发送HTTP
请求,在此就不再具体分析了。
下面来看下net/http
包发送Post
请求的示例:
package main
import (
"bytes"
"fmt"
"net/http"
)
func main() {
apiUrl := "http://www.example.com"
// 构造类型为 x-www-form-urlencoded 的请求体数据
// 发送 POST 请求
requestBody := bytes.NewBufferString("key1=value1&key2=value2")
resp, err := http.Post(apiUrl, "application/x-www-form-urlencoded", requestBody)
if err != nil {
// 发生错误
fmt.Println("Error occurred while sending request:", err)
return
}
defer resp.Body.Close()
// 读取响应结果
response, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error occurred while reading response:", err)
return
}
// 输出响应结果
fmt.Println(string(response))
}
在http.Post
中第二个函数是可以自己控制的,它的值可以是 Request
中 Content-Type
字段的允许值范围内,不清楚的可以点击Content-Type取值范围查看。
net/http
包发送application/x-www-form-urlencoded
类型的Post
请求还有一个更简便的函数 http.PostForm
或者 http.Client.PostForm
: