README.md 5.8 KB

第 20 课:HTTP 客户端与简单服务

学习目标

学会区分客户端、服务端、请求、响应与状态码,将上一课的 JSON 用在网络通信中。先读 main.go,运行成功后再看 main_test.go。本课只使用标准库,不安装第三方依赖。

本例不访问外部网站:程序自己在本机回环地址启动临时 HTTP 服务,自动选择可用端口,客户端发起一次请求,函数返回时关闭服务。地址每次可能不同;如果系统禁止本地监听,运行和部分测试可能失败。

1. 请求与响应

客户端  -- HTTP 请求 -->  服务端
客户端  <-- HTTP 响应 --  服务端

请求包含方法、路径、查询参数等;响应包含状态码、响应头和响应体。示例请求相当于:

GET /hello?name=小朱

实际传输时,中文、空格和特殊字符会进行 URL 编码。

状态码 本课含义
200 请求成功,响应体是 JSON
400 姓名缺失或全是空白
405 请求方法不是 GET

HTTP 错误状态与网络 error 是两回事:服务端返回 400 时,客户端可能已经成功收到 HTTP 响应,client.Get 的 err 仍可能是 nil,因此还需要检查 StatusCode。

2. 服务端处理函数

func greetingHandler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")
    // 检查方法和参数后写回 JSON。
}
  • w:用于写响应状态、响应头和响应体的接口。
  • r:请求指针,可读取 Method 和 URL 等信息。
  • Query().Get("name"):获取第一个同名查询参数;找不到时得到空字符串。
  • http.Error:写入指定状态码和纯文本错误响应。
  • json.NewEncoder(w).Encode(...):直接将 Go 值编码到响应体,通常会追加换行。

应先设置响应头,再写状态和正文。本例写正文时默认状态是 200。响应一旦开始写入,后续编码出错时不一定还能更改状态码,因此示例只记录写入错误。

http.NewServeMux 是路由器;HandleFunc("/hello", greetingHandler) 将路径交给处理函数。这里使用普通路径注册,方法检查在处理函数里完成。

3. 客户端请求

params := url.Values{}
params.Set("name", name)
response, err := client.Get(baseURL + "/hello?" + params.Encode())

url.Values 负责正确编码查询参数,不要手工拼接未经编码的用户输入。

请求成功取得 response 后,应关闭 response.Body;本例使用 defer。请求失败时 response 可能不存在,不能在检查错误之前就 defer 关闭。

示例的 http.Client 设置三秒 Timeout,避免无限等待。服务端返回非 200 时,函数返回一个包含状态码的错误;本课为简洁没有读取错误响应体。

成功响应通过 io.LimitReader 限制读取,再检查是否超过 64 KiB,避免无限读取。之后用 json.Unmarshal 解析。只解析成功不代表业务内容一定有效,真实项目还要验证字段、内容类型等。

非成功状态下直接关闭未完全读取的响应体,可能影响连接复用;它不会让这个示例错误地继续解析失败响应,真实客户端需结合连接复用、错误体大小上限和协议设计处理。

4. 为什么这里使用 httptest

server := httptest.NewServer(mux)
defer server.Close()

httptest 是标准库中的测试辅助工具。本例借它在本机快速启动临时服务,简化启动、选端口和关闭流程,方便一次运行看完整请求链路。

它适合教学和测试,不是生产服务器的部署方式。真实服务通常使用 http.Server 配置地址、超时等并调用 ListenAndServe,还要考虑优雅关闭、日志、身份认证和 HTTPS。本课不会在公网暴露服务。

本例 HTTP 明文且只在本机回环地址演示,不要用于传输敏感信息或当作生产安全配置。

5. 自动测试

  • httptest.NewRequest + NewRecorder:不经过真实网络,直接调用处理函数,检查状态和内容。
  • httptest.NewServer:启动临时本地服务,测试完整的客户端与服务端交互。
  • 测试覆盖有效请求、空姓名、错误方法、中文和特殊字符、非法 JSON、响应过大。

这种测试不依赖外部网站,但 NewServer 仍需要本机网络监听权限。测试服务通过 defer 关闭。

编译并运行

在仓库根目录执行:

New-Item -ItemType Directory -Force bin/20_http | Out-Null
go build -o ./bin/20_http/http_demo.exe ./lessons/20_http
.\bin\20_http\http_demo.exe

修改源码后需要重新编译。也可快速编译并运行:

go run ./lessons/20_http

预期输出(地址和端口每次可能不同):

本地临时服务: http://127.0.0.1:随机端口
客户端收到: 你好,小朱!
演示结束,临时服务即将关闭

运行本课测试:

go test -count=1 -v ./lessons/20_http

动手练习

  • 把客户端姓名改成自己的名字,观察返回内容。
  • 姓名中加入空格、中文和 &,理解 URL 编码的作用。
  • 在测试中用 POST 请求,确认返回 405。
  • 为返回数据增加一个字段,同步修改结构体、服务端和测试。
  • 增加一个请求失败时的测试;不要依赖公网网站。
  • 将客户端响应大小上限调小,验证超限错误,再恢复。

如果要另行手敲,放到本课的 practice 子目录,用 go run ./lessons/20_http/practice 运行,不要在同一个包里重复声明 main 或同名函数。

下一课学习 goroutine 并发基础,先理解如何启动任务和等待任务结束。

上一课:命令行输入 · 学习总览