Go 先锋
读完需要
速读仅需 5 分钟
1. HTTP 请求简介
HTTP(Hypertext Transfer Protocol)是构建 web 应用通信的基石。
HTTP 工作于客户端-服务端架构上。
HTTP 客户端发起请求,服务器接收请求并返回响应。
HTTP 请求主要由请求行、请求头、请求体组成
请求行 GET /search?name=Golang HTTP/1.1
请求头部 Host: www.baidu.com
User-Agent: Mozilla/5.0
请求体 username=golang&password=123456
常用的 HTTP 方法
GET - 从服务器获取资源
POST - 向服务器发送数据
PUT - 更新服务器上的资源
DELETE - 删除服务器上的资源
HTTP 状态码
服务器返回的响应中都包含一个 HTTP 状态码,常见的有:
200 OK - 请求成功
301 Moved Permanently - 重定向
404 Not Found - 资源未找到 500 Internal Server Error - 服务器内部错误
示例 GET 请求
发送 HTTP GET 请求获取页面,golang 标准库 net/http 可以轻松实现
import "net/http"
func main() {
resp, err := http.Get("http://www.example.com")
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println(resp.Status) // HTTP状态码
}
2. 发送 HTTP 请求
Go 语言 net/http 包提供 HTTP 客户端功能。主要使用两个方法: Get 和 Post。
2.1 http.Get 发送 GET 请求
用 http.Get 发送 GET 请求到指定页面,然后打印响应状态和页面内容
func httpGetDemo() {
resp, err := http.Get("https://www.baidu.com")
if err != nil {
panic(err)
}
defer resp.Body.Close()
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(resp.Status)
fmt.Println(string(bytes))
}
注意程序在读取完响应 Body 后调用 Close 方法关闭网络连接。defer 用来保证连接被关闭。
2.2 http.Post 发送 POST 请求
POST 请求通常用于向服务器提交数据,例如提交表单。下面代码通过 POST 方式传递一个 form 字符串
func httpPostDemo() {
data := strings.NewReader("name=john&age=20")
resp, err := http.Post("http://www.example.com/apply",
"application/x-www-form-urlencoded", data)
}
除了 form 表单,还可以将 json、xml、protobuf 等格式的数据通过 body POST 到服务器。
2.3 自定义请求头
可通过 Header 字段设置自定义头信息
req, _ := http.NewRequest("GET", "http://example.com", nil)
// 设置Header
req.Header.Add("If-None-Match", `W/"wyzzy"`)
resp, _ := http.DefaultClient.Do(req)
添加多个 header 也很简单
req.Header.Add("Accept", "text/html")
req.Header.Add("Connection", "close")
2.4 设置请求超时
Go 语言客户端默认没有超时时间,可能导致长时间等待响应。利用 Timeout 字段可以设置超时
client := http.Client {
Timeout: 5 * time.Second, // 设置超时时间
}
resp, err := client.Get("https://www.slowwly.rocks")
以上 client 在发出请求后最多等待 5 秒,如果服务器没回应会返回错误。
3. 处理 HTTP 响应
发送请求后 equally 要正确处理服务器返回的响应数据。
解析响应
解析 HTTP 响应主要包括以下步骤
检查状态码 StatusCode
读取响应头 Headers
读取响应体 Body
代码示例:
resp, _ := http.Get("https://www.google.com")
fmt.Println(resp.StatusCode)
for k, v := range resp.Header {
fmt.Println(k, v)
}
body, _ := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
响应体解析
很多 Web API 返回 JSON 格式的数据
{"error": 0, "msg": "success"}
Go 语言 解析 JSON 响应体很简单
import "encoding/json"
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
可方便访问结果数据, 类似的,也可以将响应体解析为 XML。
fmt.Println(result["error"])
fmt.Println(result["msg"])
4. 客户端配置
4.1 Cookie
用 Cookie 字段可以管理和发送 HTTP cookie
client := &http.Client{}
var cookies []*http.Cookie
cookies = append(cookies, &http.Cookie{Name:"cookie1", Value:"val1"})
cookies = append(cookies, &http.Cookie{Name:"cookie2", Value:"val2"})
client.Jar = &cookiejar.Jar{}
client.Jar.SetCookies(&url.URL{}, cookies)
以后 client 发出的所有请求都会带上这些 cookie。
4.2 代理
可以为 client 配置 HTTP 代理
proxyURL, _ := url.Parse("http://proxy_host:port")
client := &http.Client{Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}}
4.3 Redirect 策略
通过 CheckRedirect 函数可以自定义处理重定向的逻辑,允许重定向指定次数。
client := &http.Client{
CheckRedirect: func(
req *http.Request, via []*http.Request) error {
// 自定义Redirect策略
return http.ErrUseLastResponse
},
}
4.4 TLS 配置
自定义 Transport 可以控制 TLS 版本、密钥、证书等设置
tr := &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool},
DisableCompression: true,
}
client := &http.Client{Transport: tr}
5. 高级技巧
5.1 连接复用
每次请求都建立新的 TCP 连接效率较低。为此 HTTP 提供了连接复用机制。用 Client.Do 方法可以手动实现请求复用。
client := &http.Client{}
req1, _ := http.NewRequest("GET", "http://example.com", nil)
resp1, _ := client.Do(req1)
// 处理响应1
req2, _ := http.NewRequest("GET", "http://example.com/about", nil)
resp2, _ := client.Do(req2)
// 处理响应2
5.2 请求重试
要实现请求重试, 可在发送请求前检查状态,错误超过一定次数则停止重试。这就可以包装系统 HTTP client 实现请求重试逻辑。
// 自定义Client
type RetryClient struct {
HttpClient *http.Client
Retries int
}
func (c *RetryClient) Get(url string) (*http.Response, error) {
for i := 0; i <= c.Retries; i++ {
resp, err := c.HttpClient.Get(url)
// 如果没有错误就返回响应
if err == nil {
return resp, nil
}
}
return nil, fmt.Errorf("request failed after %d retries", retries)
}
5.3 异步处理
对于耗时较长的请求,可以启动 Goroutine 异步发送,这样不会阻塞进程
func makeAsyncRequest() {
go func() {
resp, err := http.Get("http://example.com/data")
// 处理响应
}()
}
5.4 文件上传
上传文件需要设置请求为 POST,并将文件数据放到请求 body 发送
file, _ := os.Open("test.png")
fileContents, _ := ioutil.ReadAll(file)
resp, _ := http.Post("http://example.com/upload", "image/png", bytes.NewReader(fileContents))
5.5 分块上传大文件
上传超大文件时,可以分块逐步上传
file, _ := os.Open("bigfile.iso")
defer file.Close()
url := "http://example.com/upload"
buf := make([]byte, 1024)
for {
n, _ := file.Read(buf)
if n == 0 {
break
}
}
6. 请求示例
6.1 POST JSON 数据
程序构造一个 JSON 对象,以 POST 方式提交到服务器
func postJSON() {
type Item struct {
Name string `json:"name"`
Price int `json:"price"`
}
itm := Item{"Apple", 5}
jsonData, _ := json.Marshal(itm)
resp, err := http.Post("https://api.example.com/create",
"application/json",
bytes.NewBuffer(jsonData))
}
6.2 文件下载
下载文件实际上就是发送 GET 请求,然后保存响应 Body 到文件
func downloadFile() error {
resp, err := http.Get("http://example.com/file.zip")
if err != nil {
return err
}
defer resp.Body.Close()
// 创建文件
outFile, err := os.Create("./file.zip")
if err != nil {
return err
}
defer outFile.Close()
// 写入响应流到文件
_, err = io.Copy(outFile, resp.Body)
return err
}
7. HTTP 客户端库比较
除了官方的 net/http 包,Go 语言还有很多优秀的 HTTP 客户端库
resty: 简单好用的 HTTP 客户端,使用方法类似于 jQuery。
heimdallr: 提供重试机制和超时控制的强大 HTTP 客户端。
sling: 灵活构造 HTTP 请求的客户端工具。
特性\库 | net/http | resty | heimdallr | sling |
---|---|---|---|---|
Get/Post 方法 | Yes | Yes | Yes | Yes |
请求重试 | 需自行实现 | No | Yes | No |
连接复用 | 需自行实现 | Yes | Yes | Yes |
超时控制 | Transport | Yes | Yes | No |