第 40 课:HTTP 客户端
所属模块: 网络编程
难度: 初级
标签: http, client, get, post, request
上一课: 正则表达式 regexp
下一课: HTTP 服务器基础
1. 学习目标
完成本课学习后,你将能够:
- 理解 Go 语言中
net/http包在发起 HTTP 请求中的作用。 - 使用
http.Get函数发送简单的 GET 请求并处理响应。 - 使用
http.Post和http.NewRequest函数发送带有请求体的 POST 请求。 - 正确读取和解析 HTTP 响应的状态码、头部和正文。
- 掌握处理 HTTP 请求中常见错误的基本方法。
2. 核心概念
在现代互联网中,我们编写的程序经常需要与其他 Web 服务或 API 进行交互,就像你在浏览器中访问网页一样。这个过程的核心就是 HTTP 客户端。Go 语言标准库中的 net/http 包为我们提供了强大且易用的 HTTP 客户端功能。
关键点:
- 发起请求: 使用
http.Get(url)发送 GET 请求,使用http.Post(url, contentType, body)发送 POST 请求。更灵活的方式是先用http.NewRequest(method, url, body)创建一个请求对象,再使用http.DefaultClient.Do(req)发送。 - 处理响应: 每个请求都会返回一个
*http.Response对象。最关键的一步是永远记得在使用完响应体后关闭它 (resp.Body.Close()),这能防止资源泄漏。 - 状态码:
resp.StatusCode是一个整数,告诉你请求的结果。200表示成功,404表示未找到,500表示服务器内部错误等。 - 读取正文: 响应的主体(通常是我们关心的数据,如 JSON 或 HTML)在
resp.Body中。我们使用io.ReadAll(resp.Body)或io.Copy将其读取到内存中。
下面,我们将通过具体的代码示例来掌握这些概念。
3. 代码示例
示例 1:发送简单的 GET 请求
这个示例将演示如何向一个公开的测试 API 发送 GET 请求,并读取返回的 JSON 数据。
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
// 1. 目标 URL
url := "https://jsonplaceholder.typicode.com/todos/1" // 一个公开的测试 API
// 2. 发送 GET 请求
resp, err := http.Get(url)
if err != nil {
fmt.Printf("发送请求失败: %v\n", err)
return
}
// 3. 非常重要:在函数结束时关闭响应体,释放网络连接等资源
defer resp.Body.Close()
// 4. 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("读取响应体失败: %v\n", err)
return
}
// 5. 输出结果
fmt.Println("HTTP 状态码:", resp.StatusCode)
fmt.Println("响应头 (Content-Type):", resp.Header.Get("Content-Type"))
fmt.Println("响应体 (JSON):")
fmt.Println(string(body))
}
预期输出(类似):
HTTP 状态码: 200
响应头 (Content-Type): application/json; charset=utf-8
响应体 (JSON):
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
示例 2:发送带 JSON 的 POST 请求
这个示例将向同一个 API 发送一个 POST 请求,创建一个新的“待办事项”。
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
// 定义一个结构体,对应要发送的 JSON 数据
type Todo struct {
UserID int `json:"userId"`
Title string `json:"title"`
}
func main() {
// 1. 准备要发送的数据
todo := Todo{
UserID: 1,
Title: "Learn Go HTTP Client",
}
// 将结构体编码为 JSON 格式的字节切片
jsonData, err := json.Marshal(todo)
if err != nil {
fmt.Printf("JSON 编码失败: %v\n", err)
return
}
url := "https://jsonplaceholder.typicode.com/todos"
// 2. 发送 POST 请求
// 第一个参数是 URL,第二个是内容类型,第三个是请求体(需要是 io.Reader 类型)
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
fmt.Printf("发送 POST 请求失败: %v\n", err)
return
}
defer resp.Body.Close() // 别忘了关闭!
// 3. 读取并处理响应
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("读取响应失败: %v\n", err)
return
}
fmt.Printf("POST 请求状态码: %d\n", resp.StatusCode)
fmt.Printf("服务器返回的创建结果 (ID 通常为固定值): %s\n", string(body))
}
预期输出:
POST 请求状态码: 201
服务器返回的创建结果 (ID 通常为固定值): {
"userId": 1,
"title": "Learn Go HTTP Client",
"id": 201
}
4. 实践练习
练习 1:基础 GET 请求与文本处理
编写一个程序,向 https://www.baidu.com 发送 GET 请求,读取其 HTML 响应,并打印出以下信息:
- HTTP 状态码。
- 响应体的总字节数。
- 响应体中是否包含字符串
"百度"(使用strings.Contains)。
提示: 你可能需要处理中文编码,但此网站通常使用 UTF-8。
练习 2:POST 表单数据
使用 http.PostForm 函数(Post 的一个便捷变体,专门用于发送 application/x-www-form-urlencoded 类型的数据),向一个假想的登录 API https://httpbin.org/post 发送登录信息。
- 用户名:
gopher - 密码:
go123打印出服务器返回的 JSON 数据(它会回显你发送的表单数据)。
练习 3:带自定义头部的 GET 请求
有时 API 需要认证信息放在请求头中。使用 http.NewRequest 和 client.Do 方法,向 https://httpbin.org/headers 发送一个 GET 请求,并在请求头中添加一个自定义字段 X-Custom-Header: MyGoClient。打印出服务器返回的 JSON,验证你的头部是否被成功发送。
5. 常见错误
- 忘记关闭响应体 (
resp.Body.Close()):这是最严重的资源泄漏错误。如果不关闭,底层的 TCP 连接将无法被复用,最终会导致程序或服务器资源耗尽。务必使用defer语句。 - 未检查错误:
http.Get,resp.Body.Read等操作都可能返回错误。忽略这些错误会导致程序在遇到网络问题时莫名其妙地崩溃或产生错误结果。 - 未检查 HTTP 状态码:请求成功返回 (
err == nil) 只代表网络层面的通信完成,但服务器可能返回了404 Not Found或500 Internal Server Error。始终检查resp.StatusCode。 - 未设置请求超时:默认的
http.DefaultClient没有请求超时限制。如果目标服务器响应缓慢,你的程序会一直阻塞。在生产代码中,建议创建自定义的http.Client并设置合理的超时时间。 - 大响应体直接读入内存:对于非常大的响应体(如文件下载),使用
io.ReadAll会一次性占用大量内存。更优的做法是使用流式处理,例如将resp.Body直接io.Copy到一个文件中。
6. 小结
本课我们学习了 Go 语言中进行网络请求的基础——HTTP 客户端编程。
- 核心包:
net/http提供了开箱即用的 HTTP 客户端功能。 - GET 请求: 使用
http.Get(url)快捷方法。 - POST 请求: 使用
http.Post(url, contentType, body)或更灵活的http.NewRequest+client.Do组合。 - 响应处理: 重点关注
resp.StatusCode和resp.Body。切记用defer resp.Body.Close()释放资源。 - 错误处理: 严谨地处理每一个可能返回的
error,并检查 HTTP 状态码以确保业务逻辑正确。
掌握了 HTTP 客户端,你的程序就具备了与互联网世界其他服务对话的能力,这是构建现代分布式应用的基础。下一课,我们将站在服务端,学习如何创建一个能响应这些请求的 HTTP 服务器。