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-data和x-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.Client 和 resty.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 请求
PUT 与 POST 类似,示例如下所示:
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,或扫描下面的二维码添加关注:

作者: Surpassme
来源: http://www.jianshu.com/u/28161b7c9995/
http://www.cnblogs.com/surpassme/
声明:本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出 原文链接 ,否则保留追究法律责任的权利。如有问题,可发送邮件 联系。让我们尊重原创者版权,共同营造良好的IT朋友圈。

浙公网安备 33010602011771号