Golang_15: Go语言 网络编程:HTTP/HTTPS Client 客户端

原文链接: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))
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谢TS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值