Go 入门系列参考于互联网资料与 人民邮电出版社 《Go 语言入门经典》 与 《Effective Go》,编写目的在于学习交流,如有侵权,请联系删除
超文本传输协议 (Hypertext Transfer Protocol, HTTP)是一种在互联网上收发资源的网络协议, 用于传输图像、HTML文档和Json等 , 本文内容:
理解 HTTP
要理解 HTTP 请求的结构,可以使用工具 curl。
script1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| $ curl -s -o /dev/null -v http://blog.forgiveher.cn * Trying 119.84.129.199:80... * TCP_NODELAY set * Connected to blog.forgiveher.cn (119.84.129.199) port 80 (#0) > GET / HTTP/1.1 > Host: blog.forgiveher.cn > User-Agent: curl/7.68.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 301 Moved Permanently < Server: Tengine < Date: Sun, 02 Feb 2020 17:33:39 GMT < Content-Type: text/html < Content-Length: 278 < Connection: keep-alive < Location: https://blog.forgiveher.cn/ < Via: kunlun8.cn1492[,0] < Timing-Allow-Origin: * < EagleId: 7754811c15806648198814536e < { [278 bytes data] * Connection #0 to host blog.forgiveher.cn left intact
|
- 以字符 > 打头的行描述了客户端发送的请求
- 以字符 < 打头的行描述收到的请求
- 请求的详细信息描述了随请求发送的一些报头,如客户端的一些信息
- 响应详细描述了一些报头,这些报头指出了响应的内容类型、长度和发送时间
以上是简单的 HTTP 结构,如果需要进行的是更复杂的HTTP交互,就还必须对 HTTP 规范有更深入的认识
发送 GET 请求
Go 语言在 net/http 包中提供了一个快捷方法,可用于发出简单的GET请求。使用这个方法意味着不需要考虑如何配置 HTTP 客户端以及如何设置请求报头。如果只是要从远程获取一些数据,那么默认配置完全够用
1 2 3 4 5 6 7 8 9 10 11 12
| func main() { response, err := http.Get("https://ipconfig.io") if err != nil { log.Fatal(err) } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { log.Fatal(err) } fmt.Printf("%s", body) }
|
执行将看到
112.64.233.*
发送 POST 请求
标准库中的 net/http 包也提供了用于发送简单的 POST 请求的快捷方法 —— Post,它支持设置内容以及发送数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "fmt" "io/ioutil" "log" "net/http" "strings" )
func main() { postData := strings.NewReader(`{"some": "json"}`) response, err := http.Post("https://httpbin.org/post", "application/json", postData) if err != nil { log.Fatal(err) } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { log.Fatal(err) } fmt.Printf("%s", body) }
|
执行后,从 httpbin 返回:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| { "args": {}, "data": "{\"some\": \"json\"}", "files": {}, "form": {}, "headers": { "Accept-Encoding": "gzip", "Content-Length": "16", "Content-Type": "application/json", "Host": "httpbin.org", "User-Agent": "Go-http-client/2.0", "X-Amzn-Trace-Id": "Root=1-5e371094-70fca52004157328c068ccbc" }, "json": { "some": "json" }, "origin": "112.64.233.*", "url": "https://httpbin.org/post" }
|
进一步控制 HTTP 请求
要进一步控制 HTTP 请求,应使用自定义的 HTTP 客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "fmt" "io/ioutil" "log" "net/http" )
func main() { client := &http.Client{} request, err := http.NewRequest("GET", "https://ipconfig.io", nil) if err != nil { log.Fatal(err) } response, err := client.Do(request) defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { log.Fatal(err) } fmt.Printf("%s", body) }
|
对应使用自定 HTTP 客户端解读如下:
- 不使用 net/http 包的快捷方法 Get,而创建一个 HTTP 客户端
- 使用方法 NewRequests 向 https://ifconfig.io 发出 GET 请求
- 使用方法 Do 发送请求并处理响应
使用自定的HTTP客户端意味着可对请求设置报头,基本身份验证和cookies。鉴于使用快捷方法和自定义HTTP客户端,发出请求所需代码的差别很小,建议除非要完成的任务非常简单,否则都使用自定义 HTTP 客户端
调试 HTTP 请求
创建 HTTP 客户端时,了解收发请求和响应的报头和数据对整个流程很有用。为此使用fmt
来输出各项数据,但net/http/httputil/
也提供了能够轻松调试 HTTP 客户端和服务器的方法。这个包中的方法 DumpRequestOut
和 DumpResponse
能够让您查看请求和响应。
您可在调试完成后删除它,或者使用 os.Getenv("DEBUG")
设置环境变量来进行调试
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
| package main
import ( "fmt" "io/ioutil" "log" "net/http" "net/http/httputil" "os" )
func main() { debug := os.Getenv("DEBUG") client := &http.Client{} request, err := http.NewRequest("GET", "https://ipconfig.io", nil) if err != nil { log.Fatal(err) } if debug == "1" { debugRequest, err := httputil.DumpRequestOut(request, true) if err != nil{ log.Fatal(err) } fmt.Printf("%s\n", debugRequest) } response, err := client.Do(request) defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { log.Fatal(err) } fmt.Printf("%s", body) }
|
script1 2 3 4 5 6 7 8 9 10
| > set DEBUG=1
> go run dirname.go GET / HTTP/1.1 Host: ipconfig.io User-Agent: Go-http-client/1.1 Accept-Encoding: gzip
112.64.233.*
|
如果需要调试响应,则使用 DumpResponse
方法,如果想要获取 json 数据,则使用 request.Header.Set("Accept", "application/json")
处理超时数据
HTTP 事务会为接收响应等待一些时间。客户端向服务器发送请求后,完全无法知道响应会在多长时间内返回。在底层,有大量影响响应速度的变数:
- DNS 查找速度
- 打开到服务器 IP 地址的 TCP 套接字速度
- 建立 TCP 连接的速度
- TLS 握手的速度(如果连接是 TLS 的)
- 向服务器发送数据的速度
- 重定向的速度
- Web 服务器返回响应的速度
- 将数据传输到客户端的速度
使用默认的 HTTP 客户端时,没有对请求设置超时时间,这意味着如果服务器没有响应,就会一直等待或挂起。对于任何请求,都应该设置超时请求,这样如果请求在没有规定的时间内完成则返回错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func main() { client := &http.Client{Timeout: 1 * time.Second} request, err := http.NewRequest("GET", "https://google.com", nil) request.Header.Set("Accept", "application/json") if err != nil { log.Fatal(err) } response, err := client.Do(request) defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { log.Fatal(err) } fmt.Printf("%s", body) }
|
还可以通过创建一个传输(transport)并将其传递给客户端,可更细致地控制超时:控制 HTTP 连接的各个阶段。在大多数情况下,使用 Timeout 就足以控制整个 HTTP事务,但在 Go 语言中,还可以通过创建传输来控制 HTTP 事务的各个部分
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
| tr := &http.Transport{ Proxy: nil, DialContext: (&net.Dialer{ Timeout: 0, Deadline: time.Time{}, LocalAddr: nil, FallbackDelay: 0, KeepAlive: 0, Resolver: nil, Control: nil, }).DialContext, DialTLS: nil, TLSClientConfig: nil, TLSHandshakeTimeout: 0, DisableKeepAlives: false, DisableCompression: false, MaxIdleConns: 0, MaxIdleConnsPerHost: 0, MaxConnsPerHost: 0, IdleConnTimeout: 0, ResponseHeaderTimeout: 0, ExpectContinueTimeout: 0, TLSNextProto: nil, ProxyConnectHeader: nil, MaxResponseHeaderBytes: 0, WriteBufferSize: 0, ReadBufferSize: 0, ForceAttemptHTTP2: false, } client := &http.Client{Transport: tr}
|
问题列表