25.项目实践

25.1 发送HTTP请求

25.1.1 HTTP请求方法

    根据 HTTP 标准,HTTP 请求可以使用多种请求方法,日常开发中常用的请求方法如下所示:

方法 主要功能
GET 向指定页面发起查询请求,并返回消息主体
POST 向指定页面提交数据并进行处理,例如提交表单或上传文件等
PUT 向指定页面提交数据并进行处理,一般常用于更新数据
DELTE 向指定页面发起删除某个数据请求

    对有使用 Python 发起 HTTP 请求经验的人而言,一定会非常熟悉 requests 包,而今天我们就一起来学习一下在 Go 语言中,如何发起 HTTP 请求。

25.1.2 net/http

    Go 语言标准库中的 net/http 提供了 HTTP 客户端和服务端的实现。我们来分别学习一下。

25.1.2.1 GET 请求

    使用 net/http 发起 GET 请求,一般可以分为两种带参数无参数两种,示例分别如下所示:

  • 无参数的HTTP GET 请求
// 不带参数的 GET 请求
func HttpGet(apiUrl string) (string, error) {
	resp, err := http.Get(apiUrl)
	if err != nil {
		fmt.Printf("Http Get failed,err:%+v\n", err)
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("%Read Body err:+v\n", err)
		return "", err
	}
	if resp.StatusCode != http.StatusOK {
		fmt.Printf("Http Get status is not 200")
	}
	return string(body), nil
}

// 带参数的Get请求
func HttpGetWithParams(apiUrl string, params []map[string]string, headers []map[string]string) (string, error) {
	client := &http.Client{Timeout: 5 * time.Second}
	req, err := http.NewRequest(http.MethodGet, apiUrl, nil)
	if err != nil {
		return "", err
	}

	query := req.URL.Query()

	for _, item := range params {
		for k, v := range item {
			query.Set(k, v)
		}
	}

	for _, item := range headers {
		for k, v := range item {
			req.Header.Set(k, v)
		}
	}

	req.URL.RawQuery = query.Encode()

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

    请求返回结果如下所示:

- 不带查询参数
resp: {
  "args": {}, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Host": "httpbin.org", 
    "User-Agent": "Go-http-client/1.1", 
    "X-Amzn-Trace-Id": "Root=1-696e2e55-0372571340f35ebd69949291"
  }, 
  "url": "http://httpbin.org/get"
}

- 带查询参数
resp: {
  "args": {
    "age": "28", 
    "location": "Shanghai", 
    "name": "Surpass"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Host": "httpbin.org", 
    "User-Agent": "Go-http-client/1.1", 
    "X-Amzn-Trace-Id": "Root=1-696e2f6b-54b082f550e4df5175c6574b", 
    "X-Host": "www.surpass.net", 
    "X-Host-Agent": "supass-agent-demo"
  }, 
  "url": "http://httpbin.org/get?age=28&location=Shanghai&name=Surpass"
}

    通过查看源码,可以看出 http.Get() 的最终实现,还是使用的NewRequest 如下所示:

func (c *Client) Get(url string) (resp *Response, err error) {
	req, err := NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	return c.Do(req)
}

25.1.2.2 POST 请求

    在 POST 发送请求,一般常用的可分为以下两种形式:

  • 通过key-value形式,例如 form-datax-www-form-urlencode
  • 通过 JSON 形式传送,一般需要设置 Content-Type: application/json
func HttpPostKV(apiUrl string, datas []map[string]string) (string, error) {
	queryParams := url.Values{}
	for _, data := range datas {
		for k, v := range data {
			queryParams.Set(k, v)
		}
	}
	resp, err := http.PostForm(apiUrl, queryParams)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

func HttpPostJsonData(apiUrl string, datas []map[string]string, contentType string) (string, error) {
	jsonData, err := json.Marshal(datas)
	if err != nil {
		return "", err
	}

	resp, err := http.Post(apiUrl, contentType, bytes.NewReader(jsonData))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

func HttpPost(apiUrl string, data any) (string, error) {
	client := http.Client{Timeout: 5 * time.Second}
	var req *http.Request
	var err error
	switch v := data.(type) {
	case string:
		req, err = http.NewRequest(http.MethodPost, apiUrl, strings.NewReader(v))
		if err != nil {
			return "", err
		}
		req.Header.Set("Content-Type", "plain/text")
	case map[string]string:
		dataValues := url.Values{}
		for key, value := range v {
			dataValues.Set(key, value)
		}
		req, err = http.NewRequest(http.MethodPost, apiUrl, bytes.NewReader([]byte(dataValues.Encode())))
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	case []map[string]any, map[string]any:
		jsonData, err := json.Marshal(v)
		if err != nil {
			return "", err
		}
		req, err = http.NewRequest(http.MethodPost, apiUrl, bytes.NewReader(jsonData))
		if err != nil {
			return "", err
		}
		req.Header.Add("Content-Type", "application/json")
	default:
		return "", errors.New("不支持的类型")
	}

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	return string(body), nil
}

    请求返回结果如下所示:

- key-value 形式
resp: {
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "age": "28", 
    "location": "Shanghai", 
    "name": "Surpass"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "37", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Go-http-client/1.1", 
    "X-Amzn-Trace-Id": "Root=1-696e374a-459aa5dd6a5485f17b558bed"
  }, 
  "json": null, 
  "url": "http://httpbin.org/post"
}

- 发送JSON数据

resp: {
  "args": {}, 
  "data": "[{\"name\":\"Surpass\"},{\"location\":\"Shanghai\"},{\"age\":\"28\"}]", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "57", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "User-Agent": "Go-http-client/1.1", 
    "X-Amzn-Trace-Id": "Root=1-696e33ce-477621793575225f2f93b989"
  }, 
  "json": [
    {
      "name": "Surpass"
    }, 
    {
      "location": "Shanghai"
    }, 
    {
      "age": "28"
    }
  ], 
  "url": "http://httpbin.org/post"
}

    http.PostForm 查看源码可知,就是设置了默认的 Content-Type:application/x-www-form-urlencoded,如下所示:

func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) {
	return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}

    而 http.Post 则要求显式传递 Content-Type,如下所示:

func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) {
	req, err := NewRequest("POST", url, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", contentType)
	return c.Do(req)
}

25.1.2.3 PUT 请求

    在标准库没有提供PUT的简化形式,因此我们可以使用 http.NewRequest 的形式,示例代码如下所示:

func HttpPut(apiUrl string, data []map[string]any, contentType string) (string, error) {
	jsonData, err := json.Marshal(data)
	if err != nil {
		return "", err
	}
	client := http.DefaultClient

	req, err := http.NewRequest(http.MethodPut, apiUrl, bytes.NewReader(jsonData))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", contentType)
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

    运行结果如下所示:

    "Accept-Encoding": "gzip", 
    "Content-Length": "55", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "User-Agent": "Go-http-client/1.1", 
    "X-Amzn-Trace-Id": "Root=1-696e432a-50a86b2065a9f7582eaac02a"
  }, 
  "json": [
    {
      "name": "Surpass"
    }, 
    {
      "location": "Shanghai"
    }, 
    {
      "age": 28
    }
  ], 
  "url": "http://httpbin.org/put"
}

25.1.2.4 DELETE 请求

    在标准库没有提供DELETE的简化形式,因此我们可以使用 http.NewRequest 的形式,示例代码如下所示:

func HttpDelete(apiUrl string, data []map[string]any, contentType string) (string, error) {
	jsonData, err := json.Marshal(data)
	if err != nil {
		return "", err
	}
	client := http.DefaultClient

	req, err := http.NewRequest(http.MethodDelete, apiUrl, bytes.NewReader(jsonData))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", contentType)
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

    运行结果如下所示:

resp: {
  "args": {}, 
  "data": "[{\"name\":\"Surpass\"},{\"location\":\"Shanghai\"},{\"age\":28}]", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "55", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "User-Agent": "Go-http-client/2.0", 
    "X-Amzn-Trace-Id": "Root=1-696e44de-72235dd64e1672fd1983d224"
  }, 
  "json": [
    {
      "name": "Surpass"
    }, 
    {
      "location": "Shanghai"
    }, 
    {
      "age": 28
    }
  ], 
  "url": "https://httpbin.org/delete"
}

25.1.3 go-resty 库

    在 Go 语言中,使用标准库 net/http 可以轻松完成各样的 HTTP 请求,例如一个典型的 GET 请求的步骤如下所示:

  • 创建一个 http.Clinet 对象 client
  • 创建一个 http.Request 对象 request ,然后传入请求方法、URL地址和请求体
  • 通过 client 调用方法 Do,并传入 request ,获取响应
  • 读取响应体,获取响应的具体内容

    虽然通过标准库可以很方便发起一个 HTTP 请求,但也存在使用繁琐,导致效率偏低的问题,如下所示:

  • 需要手动设置 Content-Type ,而且不同的 Content-Type 有不同的设置

JSON 格式需要先序列化为字符串或二进制序列,x-www-form-urlencode 需要先进行 encode

  • 需要手动解析响应内容。因为 body 拿到的是一个字节流,需要使用者自己对字节流进行解析。

    因此基于以上各种原因,导致 net/http 虽然灵活,但使用门槛也偏高。因此,也出现了很多优秀的第三方库,在 net/http 库的基础上进行封装,降低了使用难度,也提高了开发效率。后面我们学习和使用的第三方库为 go-resty

    其主要特征如下所示:

  • 使用简单且支持链式调用
  • 支持路径参数
  • 支持重试机制
  • 并发安全
  • 支持自动压缩,例如gzip
  • 支持基本验证、Bearer 验证等
  • 支持HTTP/1.1、HTTP/2、HTTP/3

以上特征,可以查阅官方文档:https://resty.dev/#key-features

    在学习 go-resty 前,我们先来看看基于 go-resty 如何发送一个 GET 请求,示例如下所示:

package main

import (
	"fmt"

	"github.com/go-resty/resty/v2"
)

func main() {
	apiUrl:="https://httpbin.org/get"
	client := resty.New()

	resp,err:=client.R().
		SetHeaders(map[string]string{
			"X-HOST":"www.surpass.net",
			"X-Agent":"agent.surpass.net",
		}).
		SetBasicAuth("suprass","myPassword").
		SetQueryParams(map[string]string{
			"name":"Surpass",
			"location":"Shanghai",
			"sport":"ride",
		}).
		Get(apiUrl)
	
	if err != nil {
		panic(err)
	}
	fmt.Printf("resp: %v\n", resp)
}

    运行结果如下所示:

resp: {
  "args": {
    "location": "Shanghai", 
    "name": "Surpass", 
    "sport": "ride"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Authorization": "Basic c3VwcmFzczpteVBhc3N3b3Jk", 
    "Host": "httpbin.org", 
    "User-Agent": "go-resty/2.17.1 (https://github.com/go-resty/resty)", 
    "X-Agent": "agent.surpass.net", 
    "X-Amzn-Trace-Id": "Root=1-696fa409-0de9eeab3b32a36d0843165f", 
    "X-Host": "www.surpass.net"
  }, 
  "url": "https://httpbin.org/get?location=Shanghai&name=Surpass&sport=ride"
}

    从上述代码中可以看出来,比使用标准库 net/http 要方便很多且也更加简洁,同时也可以总结出基本使用步骤如下所示:

  • 调用 resty.New() 创建 resty.Client 对象 client
  • 调用 client.R() 创建 resty.Request 对象 request
  • 调用 resty.Request 对象的 SetXXX 方法设置相应的请求配置项,例如:Headers查询参数鉴权信息
  • 调用 resty.Request 对象的 GET 方法发起请求,并接收对应的响应和错误信息

    如果进一步查看源码,可以得知,resty 库的基本请求功能是通过 resty.Clientresty.Request 来完成的,源码片断如下所示:

func (c *Client) R() *Request {
	r := &Request{
		QueryParam:    url.Values{},
		FormData:      url.Values{},
		Header:        http.Header{},
		Cookies:       make([]*http.Cookie, 0),
		PathParams:    map[string]string{},
		RawPathParams: map[string]string{},
		Debug:         c.Debug,
		AuthScheme:    c.AuthScheme,

		client:              c,
		multipartFiles:      []*File{},
		multipartFields:     []*MultipartField{},
		jsonEscapeHTML:      c.jsonEscapeHTML,
		log:                 c.log,
		responseBodyLimit:   c.ResponseBodyLimit,
		generateCurlOnDebug: c.generateCurlOnDebug,
		unescapeQueryParams: c.unescapeQueryParams,
	}
	return r
}

func (c *Client) NewRequest() *Request {
	return c.R()
}

func (r *Request) SetHeaders(headers map[string]string) *Request {
	for h, v := range headers {
		r.SetHeader(h, v)
	}
	return r
}

    由于官方文档已经非常详细了,下面就简单介绍下一些常用的用法

25.1.3.1 GET 请求
  • 1.基本查询请求
	client := resty.New()
	resp, err := client.R().Get(apiUrl)
  • 2.带查询参数
	client := resty.New()
	resp, err := client.R().
		SetQueryParams(map[string]string{
			"name":     "Surpass",
			"location": "Shanghai",
			"sport":    "ride",
		}).
		Get(apiUrl)
  • 3.带有路径参数
    // 注意这里的路径参数写法 
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetQueryParams(map[string]string{
			"name":     "Surpass",
			"location": "Shanghai",
			"sport":    "ride",
		}).
		SetPathParams(map[string]string{
			"method": "get",
		}).
		Get(apiUrl)
  • 4.设置Header和基本鉴权信息
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetQueryParams(map[string]string{
			"name":     "Surpass",
			"location": "Shanghai",
			"sport":    "ride",
		}).
		SetPathParams(map[string]string{
			"method": "get",
		}).
		SetHeaders(map[string]string{
			"X-HOST":"www.surpass.net",
			"X-Token":"surpass.token",
		}).
		SetBasicAuth("surpass","myPassword").
		Get(apiUrl)
25.1.3.2 POST 请求

    主要示例如下所示:

  • 1.使用 struct
type UserInfo struct {
	name     string
	location string
	sport    string
}

func main() {
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetBody(UserInfo{
			name:     "Surpass",
			location: "Shanghai",
			sport:    "ride",
		}).
		SetPathParams(map[string]string{
			"method": "post",
		}).
		SetHeaders(map[string]string{
			"X-HOST":       "www.surpass.net",
			"X-Token":      "surpass.token",
			"Content-Type": "application/json",
		}).
		SetBasicAuth("surpass", "myPassword").
		Post(apiUrl)

	if err != nil {
		panic(err)
	}
	fmt.Printf("resp: %v\n", resp)
}
  • 2.使用 Map
func main() {
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetBody(map[string]string{
			"name":     "Surpass",
			"location": "Shanghai",
			"sport":    "ride",
		}).
		SetPathParams(map[string]string{
			"method": "post",
		}).
		SetHeaders(map[string]string{
			"X-HOST":       "www.surpass.net",
			"X-Token":      "surpass.token",
			"Content-Type": "application/json",
		}).
		SetBasicAuth("surpass", "myPassword").
		Post(apiUrl)

	if err != nil {
		panic(err)
	}
	fmt.Printf("resp: %v\n", resp)
}
  • 3.使用 string
func main() {
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetBody(`{
			"name":     "Surpass",
			"location": "Shanghai",
			"sport":    "ride",
		}`).
		SetPathParams(map[string]string{
			"method": "post",
		}).
		SetHeaders(map[string]string{
			"X-HOST":       "www.surpass.net",
			"X-Token":      "surpass.token",
			"Content-Type": "application/json",
		}).
		SetBasicAuth("surpass", "myPassword").
		Post(apiUrl)

	if err != nil {
		panic(err)
	}
	fmt.Printf("resp: %v\n", resp)
}
  • 4.使用 bytes
func main() {
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetBody([]byte(`{
			"name":     "Surpass",
			"location": "Shanghai",
			"sport":    "ride",
		}`)).
		SetPathParams(map[string]string{
			"method": "post",
		}).
		SetHeaders(map[string]string{
			"X-HOST":       "www.surpass.net",
			"X-Token":      "surpass.token",
			"Content-Type": "application/json",
		}).
		SetBasicAuth("surpass", "myPassword").
		Post(apiUrl)

	if err != nil {
		panic(err)
	}
	fmt.Printf("resp: %v\n", resp)
}
  • 5.使用 io.Reader
func main() {
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetBody(strings.NewReader(`{
			"name":     "Surpass",
			"location": "Shanghai",
			"sport":    "ride",
		}`)).
		SetPathParams(map[string]string{
			"method": "post",
		}).
		SetHeaders(map[string]string{
			"X-HOST":       "www.surpass.net",
			"X-Token":      "surpass.token",
			"Content-Type": "application/json",
		}).
		SetBasicAuth("surpass", "myPassword").
		Post(apiUrl)

	if err != nil {
		panic(err)
	}
	fmt.Printf("resp: %v\n", resp)
}
  • 6.使用 Multipart Form Data
func main() {
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetMultipartFormData(map[string]string{
			"name":     "Surpass",
			"location": "Shanghai",
			"sport":    "ride",
		}).
		SetPathParams(map[string]string{
			"method": "post",
		}).
		SetHeaders(map[string]string{
			"X-HOST":       "www.surpass.net",
			"X-Token":      "surpass.token",
			"Content-Type": "application/json",
		}).
		SetBasicAuth("surpass", "myPassword").
		Post(apiUrl)

	if err != nil {
		panic(err)
	}
	fmt.Printf("resp: %v\n", resp)
}
  • 7.使用 Form Data
func main() {
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetFormData(map[string]string{
			"name":     "Surpass",
			"location": "Shanghai",
			"sport":    "ride",
		}).
		SetPathParams(map[string]string{
			"method": "post",
		}).
		SetHeaders(map[string]string{
			"X-HOST":       "www.surpass.net",
			"X-Token":      "surpass.token",
			"Content-Type": "application/json",
		}).
		SetBasicAuth("surpass", "myPassword").
		Post(apiUrl)

	if err != nil {
		panic(err)
	}
	fmt.Printf("resp: %v\n", resp)
}
25.1.3.3 PUT 请求

    PUTPOST 类似,示例如下所示:

func main() {
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetBody(map[string]string{
			"name":     "Surpass",
			"location": "Shanghai",
			"sport":    "ride",
		}).
		SetPathParams(map[string]string{
			"method": "put",
		}).
		SetHeaders(map[string]string{
			"X-HOST":       "www.surpass.net",
			"X-Token":      "surpass.token",
		}).
		SetBasicAuth("surpass", "myPassword").
		Put(apiUrl)

	if err != nil {
		panic(err)
	}
	fmt.Printf("resp: %v\n", resp)
}
25.1.3.4 DELETE 请求

    示例如下所示:

  • 1.简单的删除示例
func main() {
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetPathParams(map[string]string{
			"method": "delete",
		}).
		SetHeaders(map[string]string{
			"X-HOST":       "www.surpass.net",
			"X-Token":      "surpass.token",
		}).
		SetBasicAuth("surpass", "myPassword").
		Delete(apiUrl)

	if err != nil {
		panic(err)
	}
	fmt.Printf("resp: %v\n", resp)
}
  • 2.带Body的删除
func main() {
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetBody(map[string]string{
			"name":     "Surpass",
			"location": "Shanghai",
			"sport":    "ride",
		}).
		SetPathParams(map[string]string{
			"method": "delete",
		}).
		SetHeaders(map[string]string{
			"X-HOST":       "www.surpass.net",
			"X-Token":      "surpass.token",
		}).
		SetBasicAuth("surpass", "myPassword").
		Delete(apiUrl)

	if err != nil {
		panic(err)
	}
	fmt.Printf("resp: %v\n", resp)
}
25.1.3.5 查看响应

    示例如下所示:

func main() {
	apiUrl := "https://httpbin.org/{method}"
	client := resty.New()
	resp, err := client.R().
		SetPathParams(map[string]string{
			"method": "delete",
		}).
		SetHeaders(map[string]string{
			"X-HOST":  "www.surpass.net",
			"X-Token": "surpass.token",
		}).
		SetBasicAuth("surpass", "myPassword").
		Delete(apiUrl)

	if err != nil {
		panic(err)
	}
	fmt.Println("Response Info:")
	fmt.Println("Status Code:", resp.StatusCode())
	fmt.Println("Status:", resp.Status())
	fmt.Println("Proto:", resp.Proto())
	fmt.Println("Time:", resp.Time())
	fmt.Println("Recevied At:", resp.ReceivedAt())
	fmt.Println("Size:", resp.Size())
	fmt.Println("Header:")
	for key, val := range resp.Header() {
		fmt.Println("\t", key, "->", val)
	}
	fmt.Println("Cookies:")
	for key, val := range resp.Cookies() {
		fmt.Println("\t", key, "->", val)
	}
	fmt.Println("Body:", string(resp.Body()))
}

    运行结果如下所示:

Response Info:
Status Code: 200
Status: 200 OK
Proto: HTTP/2.0
Time: 2.0619554s
Recevied At: 2026-01-23 23:20:31.5318754 +0800 CST m=+2.065461101
Size: 496
Header:
	 Server -> [gunicorn/19.9.0]
	 Access-Control-Allow-Origin -> [*]
	 Access-Control-Allow-Credentials -> [true]
	 Date -> [Fri, 23 Jan 2026 15:20:09 GMT]
	 Content-Type -> [application/json]
	 Content-Length -> [496]
Cookies:
Body: {
  "args": {},
  "data": "",
  "files": {},
  "form": {},
  "headers": {
    "Accept-Encoding": "gzip",
    "Authorization": "Basic c3VycGFzczpteVBhc3N3b3Jk",
    "Host": "httpbin.org",
    "User-Agent": "go-resty/2.17.1 (https://github.com/go-resty/resty)",
    "X-Amzn-Trace-Id": "Root=1-697391a9-7283eeff24e1f9ef2d4ee197",
    "X-Host": "www.surpass.net",
    "X-Token": "surpass.token"
  },
  "json": null,
  "url": "https://httpbin.org/delete"
}

如果想更加深入了解源码,可以参考这篇文章:https://zhuanlan.zhihu.com/p/593338887

本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

posted @ 2026-07-19 23:20  Surpassme  阅读(4)  评论(0)    收藏  举报