原文链接:https://xiets.blog.csdn.net/article/details/130866427
版权声明:原创文章禁止转载
专栏目录:Golang 专栏(总目录)
Go 语言内置的 net/http 包提供了简洁而又完善的 HTTP 客户端 和 服务端 的实现,并且 客户端 和 服务端 均支持 HTTP/2.0。
1. HTTP 请求
net/http
包提供了几个常用的 HTTP 请求方法直接使用:
// HEAD 请求
http.Head(url string) (resp *Response, err error)
// GET 请求
http.Get(url string) (resp *Response, err error)
// POST 请求
http.Post(url, contentType string, body io.Reader) (resp *Response, err error)
// POST 提交表单数据 (不支持文件字段), Values 的底层类似是 map[string][]string
http.PostForm(url string, data url.Values) (resp *Response, err error)
// 上面几个 HTTP 请求的便携方法都返回一个 响应对象 和 错误,
// 内部使用的是 http 包中的一个全局 HTTP 客户端对象 http.DefaultClient,
// 读取完响应正文后, 客户端必须调用 resp.Body.Close() 关闭响应正文。
代码示例:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
)
// GetDemo GET 请求示例
func GetDemo() {
// 发送 GET 请求 (自动处理重定向), 返回 响应
resp, err := http.Get("https://httpbin.org/get?aa=bb&cc=123")
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
// 响应状态码 和 HTTP协议版本
fmt.Println(resp.StatusCode, resp.Proto)
// 读取所有的响应内容
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
defer resp.Body.Close()
// 转换为字符串输出响应内容
fmt.Println("Body:", string(body))
}
// PostDemo POST 请求示例
func PostDemo() {
// 发送 POST 请求, 返回 响应
resp, err := http.Post("https://httpbin.org/post", "application/json", strings.NewReader(`{"aa": "bb", "cc": 123}`))
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
// 响应状态码 和 HTTP协议版本
fmt.Println(resp.StatusCode, resp.Proto)
// 读取所有的响应内容
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
defer resp.Body.Close()
// 转换为字符串输出响应内容
fmt.Println("Body:", string(body))
}
// PostForm POST 提交表单示例
func PostFormDemo() {
// 构建表单
values := url.Values{}
values.Add("aa", "Hello World")
values.Add("aa", "你好,世界")
values.Add("bb", "123")
// 打印表单数据的编码
formEncode := values.Encode()
fmt.Println(formEncode) // 输出: aa=Hello+World&aa=%E4%BD%A0%E5%A5%BD%EF%BC%8C%E4%B8%96%E7%95%8C&bb=123
// value := values.Get(key) // 获取 key 对应的第一个 value 值
// has := values.Has(key) // 判断是否包含指定的 key
// values.Set(key, value) // 设置 key 对应的 value 值 (覆盖)
// values.Del(key) // 删除 key 对应的所有 value 值
// 发送 POST 请求提交表单, 自动添加 "Content-Type": "application/x-www-form-urlencoded" 请求头
resp, err := http.PostForm("https://httpbin.org/post", values)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
// 响应状态码 和 HTTP协议版本
fmt.Println(resp.StatusCode, resp.Proto)
// 读取所有的响应内容
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
defer resp.Body.Close()
// 转换为字符串输出响应内容
fmt.Println("Body:", string(body))
}
func main() {
GetDemo()
PostDemo()
PostFormDemo()
}
2. HTTP 客户端
要控制 HTTP 请求头、重定向策略、设置代理 等其他设置,需要创建一个 HTTP 客户端。
2.1 客户端对象 Client
http.Client
类型和方法:
type Client struct {
// (接口类型) 指定发出单个 HTTP 请求的机制,
// 如: 设置代理, 最大空闲连接数, 每个Host的最大连接数, 空闲连接超时时间 等。
// 如果为 nil, 则默认使用 http.DefaultTransport
Transport RoundTripper
// 指定处理重定向的策略。
// 如果不为 nil, 重定向时将调用此函数, req 和 via 分别表示即将到了来的请求和已经发出去的请求。
// 如果 CheckRedirect 为 nil, 则客户端使用其默认策略, 即连续 10 次请求后停止。
CheckRedirect func(req *Request, via []*Request) error
// (接口类型) 用于自动保存/设置请求 cookies。
// 有两个方法: SetCookies(u *url.URL, cookies []*Cookie) 和 Cookies(u *url.URL) []*Cookie
// 如果为 nil, cookie 只有在 Request 请求对象上显示设置才会发送。
Jar CookieJar
// 超时时间, 包括连接时间、重定向、读取响应。默认为 0 表示没有超时。
Timeout time.Duration
}
// HEAD 请求
func (c *Client) Head(url string) (resp *Response, err error)
// GET 请求
func (c *Client) Get(url string) (resp *Response, err error)
// POST 请求
func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error)
// POST 提交表单
func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error)
// 先创建请求对象, 再发送请求
func (c *Client) Do(req *Request) (*Response, error)
// 关闭空闲的连接
func (c *Client) CloseIdleConnections()
http.Transport 类型实现了 RoundTripper
接口:
type Transport struct {
// 设置代理, 函数类型, 支持 "http", "https", "socks5" 代理类型。
// 可以调用 http.ProxyURL(proxyUrl *url.URL) 返回一个设置代理的函数,
// 也可以直接传 http.ProxyFromEnvironment 函数从系统环境变量(HTTP_PROXY/HTTPS_PROXY)中获取代理。
Proxy func(*Request) (*url.URL, error)
// 禁用连接保持。如果为 true, 则禁用 HTTP keep-alives, 并且与服务器的连接仅用于单个 HTTP 请求。
DisableKeepAlives bool
// 是否禁用内容压缩, 即请求时不传 "Accept-Encoding: gzip" 请求头
DisableCompression bool
// 所有 Host 的最大空闲连接数, 0 表示不显示。
MaxIdleConns int
// 每一个 Host 的最大空闲连接数, 0 表示使用 http.DefaultMaxIdleConnsPerHost == 2
MaxIdleConnsPerHost int
// 每一个 Host 的最大连接数, 0 表示不限制。
MaxConnsPerHost int
// 空闲连接的超时时间 (超过该时间关闭空闲连接), 0 表示不限制。
IdleConnTimeout time.Duration
// 读取响应头的超时时间 (不包括读取响应正文的时间), 0 表示不限制。
ResponseHeaderTimeout time.Duration
// 与 "Expect: 100-continue" 相关的参数
ExpectContinueTimeout time.Duration
// 通过代理请求时, CONNECT 请求期间发送到代理的请求头
ProxyConnectHeader Header
// 动态设置发送到代理的请求头。
// 对指定 ip:port(target) 的 CONNECT 请求期间发送到 proxyURL 的请求头
// 如果不为 nil, ProxyConnectHeader 将被忽略。
GetProxyConnectHeader func(ctx context.Context, proxyURL *url.URL, target string) (Header, error)
// 响应头的最大限制大小, 默认为 0 表示不限制。
MaxResponseHeaderBytes int64
// 写入传输时的写入缓冲区的大小。如果为 0, 则默认为 4KB。
WriteBufferSize int
// 从传输中读取时使用的读取缓冲区的大小。如果为 0, 则默认为 4KB。
ReadBufferSize int
// 使用过 Dial()、DialTLS()、DialContext() 或 TLSClientConfig 时是否启用 HTTP/2 (不是指普通的 HTTP 请求)
ForceAttemptHTTP2 bool
// TCP/TLS 相关的自定义配置
Dial func(network, addr string) (net.Conn, error)
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
DialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)
TLSClientConfig *tls.Config
TLSHandshakeTimeout time.Duration
TLSNextProto map[string]func(authority string, c *tls.Conn) RoundTripper
}
cookiejar.Jar 类型实现了 CookieJar
接口:
type Jar struct {
// 没有导出字段
}
// 创建 Jar 对象
cookiejar.New(o *Options) (*Jar, error)
http.Client
代码示例:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"time"
)
func main() {
// 创建传输对象
transport := &http.Transport{
MaxIdleConns: 10,
MaxConnsPerHost: 10,
IdleConnTimeout: 10 * time.Second,
/*TLSClientConfig: &tls.Config{
// 指定不校验 SSL/TLS 证书
InsecureSkipVerify: true,
},*/
}
// 创建 HTTP 客户端
client := &http.Client{
Transport: transport,
Timeout: 15 * time.Second,
}
// 发送 GET 请求
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
// 响应状态码 和 HTTP协议版本
fmt.Println(resp.StatusCode, resp.Proto)
// 读取所有的响应内容
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
defer resp.Body.Close()
// 转换为字符串输出响应内容
fmt.Println("Body:", string(body))
}
2.2 请求对象 Request
http.Request
表示一个请求对象,支持单个请求的各种自定义参数设置。
http.Request
类型和方法:
type Request struct {
Method string // 请求方法: GET, POST, PUT 等, 空字符串默认为 GET, 不支持 CONNECT 方法请求
URL *url.URL // 请求的 URL
Proto string // HTTP 协议 HTTP/1.1 或 HTTP/2
ProtoMajor int // 主协议: 2
ProtoMinor int // 子协议: 0
Header Header // 请求头, 底层类型为 map[string][]string
Body io.ReadCloser // 请求 Body
GetBody func() (io.ReadCloser, error)
ContentLength int64 // 请求内容的长度
TransferEncoding []string // 传输编码
Host string // 主机名
Form url.Values // 表单相关数据
PostForm url.Values
MultipartForm *multipart.Form
// ...
}
// 创建一个 请求对象
request := http.NewRequest(method, url string, body io.Reader) (*Request, error)
// 请求头 相关设置
request.Header.Add(key, value string)
request.Header.Set(key, value string)
request.Header.Del(key string)
request.Header.Get(key string)
request.Header.Values(key string)
// 添加/获取 Cookie
request.AddCookie(c *Cookie)
request.Cookie(name string) (*Cookie, error)
request.Cookies() []*Cookie
// 设置/获取 Basic Auth
request.SetBasicAuth(username, password string)
request.BasicAuth() (username, password string, ok bool)
创建请求对象发送请求,http.Request
代码示例:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
)
func main() {
// 创建 HTTP 客户端
client := &http.Client{}
// 创建请求对象
req, err := http.NewRequest("GET", "https://httpbin.org/get", nil)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
// 发送请求
resp, err := client.Do(req)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
// 响应状态码 和 HTTP协议版本
fmt.Println(resp.StatusCode, resp.Proto)
// 读取所有的响应内容
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
defer resp.Body.Close()
// 转换为字符串输出响应内容
fmt.Println("Body:", string(body))
}
2.3 响应对象 Response
http.Response
表示一个请求返回的响应对象。
http.Response
类型和方法:
type Response struct {
Status string // 状态描述, 如: "200 OK"
StatusCode int // 状态码, 如: 200
Proto string // 协议版本, 如: "HTTP/1.0"
ProtoMajor int // 协议大版本, 如: 1
ProtoMinor int // 协议小版本, 如: 0
// 响应头, 底层类型为 map[string][]string
Header Header
// 响应 Body, 此字段总是非 nil, 即使没有响应 Body 也会设置一个零长度的 Body,
// 需手动调用 Body.Close() 关闭响应。
Body io.ReadCloser
// 内容长度, -1 表示未知。
ContentLength int64
// 包含从最外层到最内层的传输编码。值为 nil, 表示使用 "identity" 编码。
TransferEncoding []string
// 记录 header directed 是否读取 Body 后关闭连接。
Close bool
// 响应是否被压缩
Uncompressed bool
// Trailer
Trailer Header
// 此响应对应的请求
Request *Request
// TLS 连接的信息
TLS *tls.ConnectionState
}
发送 HTTP 请求返回的响应,响应头会立即被读取完,响应正文在读取 Body 字段时按需流式传输。
http.Response
代码示例:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
)
func main() {
// 发送请求, 反应响应
resp, err := http.Get("https://httpbin.org/get")
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
// 响应状态码 和 HTTP协议版本
fmt.Println(resp.StatusCode, resp.Proto)
// 内容长度
fmt.Println("ContentLength:", resp.ContentLength)
// 读取所有的响应头
for key, values := range resp.Header {
fmt.Printf("%s: %v\n", key, values)
}
// 读取所有的响应内容
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
defer resp.Body.Close()
// 转换为字符串输出响应内容
fmt.Println("Body:", string(body))
}
3. 提交表单文件
url.Values
不支持提交表单文件,mime/multipart 包实现了 MIME 复杂表单的构建和解析。
提交表单文件示例 (忽略错误):
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"path/filepath"
)
func main() {
// 创建一个字节缓冲区用于保存 multipart 编码后的复杂表单数据
reqBody := bytes.NewBuffer(nil)
// 创建 复杂表单写入器 (最终会写入到 reqBody 字节缓冲区)
multipartWriter := multipart.NewWriter(reqBody)
// 写入 简单字段
_ = multipartWriter.WriteField("key_aa", "Hello 世界")
_ = multipartWriter.WriteField("key_bb", "123")
// 需要上传的文件的路径
filePath := "demo.go"
// 创建一个 表单文件字段: 第一个参数是文件字段名称, 第二个参数是文件名
// 返回此文件字段内容的一个写入器 和 错误
partFileWriter, _ := multipartWriter.CreateFormFile("key_file", filepath.Base(filePath))
// 把 文件内容 写入到 文件字段内容写入器: file -> partFileWriter
file, _ := os.Open(filePath)
_, _ = io.Copy(partFileWriter, file)
_ = file.Close()
// 关闭 表单写入器
_ = multipartWriter.Close()
// 到这里, 复杂表单内容的请求 Body 已被编码写入到 reqBody 字节缓冲区中,
// 然后 POST 提交数据时直接读取出来作为请求 Body 即可。
// 获取当前复杂表单的内容类型, 请求头 Content-Type" 的值, 格式: ": "multipart/form-data; boundary=******"
contentType := multipartWriter.FormDataContentType()
// 发送 POST 请求, 提交 复杂表单数据
resp, err := http.Post("https://httpbin.org/post", contentType, reqBody)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
// 响应状态码 和 HTTP协议版本
fmt.Println(resp.StatusCode, resp.Proto)
// 读取所有的响应内容
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
_ = resp.Body.Close()
// 转换为字符串输出响应内容
fmt.Println("Body:", string(body))
}
4. Cookie 保持
http.CookieJar
接口类型表示一个 CookieJar,用于保存响应的 Cookie,以及在请求时设置对应的 Cookie。
type CookieJar interface {
// 保存指定 url 的 Cookie, 返回响应时调用
SetCookies(u *url.URL, cookies []*Cookie)
// 返回指定 url 请求需要设置的 Cookie, 发送请求时调用
Cookies(u *url.URL) []*Cookie
}
cookiejar.Jar
类型实现了 http.CookieJar
接口。
http.CookieJar
代码示例:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"os"
"time"
)
func main() {
// 创建一个 CookieJar
jar, err := cookiejar.New(nil)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
// 创建 HTTP 客户端
client := &http.Client{
Timeout: 15 * time.Second,
Jar: jar,
}
// 发送 GET 请求,
// 这个请求会在响应时设置一个 hello=world 的 Cookie,
// 然后重定向到 https://httpbin.org/cookies 返回发送的 Cookie
resp, err := client.Get("https://httpbin.org/cookies/set/hello/world")
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
// 响应状态码 和 HTTP协议版本
fmt.Println(resp.StatusCode, resp.Proto)
// 读取所有的响应内容
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
return
}
defer resp.Body.Close()
// 转换为字符串输出响应内容
fmt.Println("Body:", string(body))
}