go内置的net/http包,提供了http相关操作的实现。
HTTP协议
作为文本传输协议,HTTP的协议头是文本数据,HTTP请求头的首行会包含请求的方法、路径和协议版本,接下来是多个HTTP协议头以及携带的负载。
go的HTTP客户端包含以下比较重要的结构体:
net/http.Client
:HTTP客户端;是级别较高的抽象,它提供了HTTP的一些细节,包括Cookies和重定向;net/http.Transport
:处理HTTP/HTTPS协议的底层实现细节,其中会包含连接重用、构建请求以及发送请求等功能;net/http.persistConn
:封装了一个TCP的持久连接,是与远程交换消息的句柄(Handle);
请求
net/http.Request
是HTTP的请求,其中包含HTTP请求的方法、URL、协议版本、协议头以及请求体等字段。
type Request struct {
Method string
URL *url.URL
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
Header Header
Body io.ReadCloser
...
Response *Response
}
库中提供了http.NewRequest
用于创建请求的方法,会校验HTTP请求的字段并根据输入的参数拼装成新的请求结构体。
http.Client
库中提供了一个http.DefaultClient,可用作缺省的客户端;此客户端没有超时处理,若服务端无返回,会一直挂在那儿,可通过设定超时时间,来避免此情形:
httpClient = &http.Client{
Timeout: 5 * time.Second,
}
请求示例
返回应答中的body在用完时要及时关闭:
defer resp.Body.Close()
GET
若没有其他额外参数,可直接通过Get方式获取:
response,_ := http.Get("http://www.baidu.com")
defer response.Body.Close()
body,_ := ioutil.ReadAll(response.Body)
fmt.Println(string(body))
若要添加参数,如添加token头信息,则需要先构造请求:
req, _ := http.NewRequest("GET", targetUrl, nil)
req.Header.Add("token", "xxxx")
response, err := httpClient.Do(req)
// ...
POST
POST请求通常有两种形式:
- kv形式:如
form-data
和x-www-form-urlencoded
; - json形式:如
application/json
;
KV形式
通过Post直接发送KV键值对:
payload := strings.NewReader("key=myValue")
response, err := http.Post(targetUrl, "application/x-www-form-urlencoded", payload)
通过PostForm发送:
import (
"net/http"
"net/url"
)
payload := url.Values{"key": {"MyValue"}, "id": {"123"}}
response, err := http.PostForm(targetUrl, payload)
JSON形式
body := `{"seq":"xxxxx","user":"` + user + `","psw":"` + psw + `"}`
req, err := http.NewRequest("POST", targetUrl, bytes.NewBuffer([]byte(body)))
if err != nil {
log.Println("[ERROR] NewRequest of token fail:", err)
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err := httpClient.Do(req)
if err != nil {
log.Println("[ERROR] token request fail:", err)
return err
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("[ERROR] read token response body fail:", err)
return err
}
var result map[string]interface{}
err = json.Unmarshal(respBody, &result)
if err != nil {
log.Println("[ERROR] Unmarshal token response fail:", err)
return err
}
if v, ok := result["token"]; ok {
if token, got := v.(string); got {
log.Printf("[DEBUG] token got: %v", token)
return nil
}
}
DELETE
http中没有直接提供delete方法,只能通过request构造:
req, _ := http.NewRequest("DELETE", targetUrl, nil)
req.Header.Add("token", "xxxx")
response, err := httpClient.Do(req)
错误
HTTP请求在重用Request结构体可能会出现以下错误:
http: ContentLength=XXX with Body length 0
//req定义在for循坏外
req, err := http.NewRequest("POST", url, bytes.NewReader(data))
// 多次请求
for {
resp, err := httpClient.Do(req)
//...
}
这是因为:第一次发送http请求失败后,后续尝试请求时req的body被清空了;body是个io.Reader,第一次发送后,Request.Body已经被读并且stream被关闭了,后续请求再读取就为空了。
所以req定义要与Do请求在一起:
for {
req, err := http.NewRequest("POST", url, bytes.NewReader(data))
resp, err := httpClient.Do(req)
//...
}