Go 语言 net/http 包使用教程
Go 语言的 net/http 包提供了 HTTP 客户端和服务端的实现,是开发 Web 应用和进行网络请求的核心模块。
1. 基础概念
在 Go 的 net/http 模块中,核心概念包括:
http.Request: 表示客户端发给服务器的请求,包含方法、URL、请求头、请求体等信息。http.Response: 表示服务器返回给客户端的响应,包含状态码、响应头、响应体等信息。http.Handler: 一个接口,定义了如何处理 HTTP 请求。它有一个方法:ServeHTTP(w http.ResponseWriter, r *http.Request)。http.ResponseWriter: 一个接口,用于向客户端发送 HTTP 响应。
2. 客户端 (Client) 使用
使用 Go 编写 HTTP 客户端非常简单,通常使用 http.Get()、http.Post() 等简便函数,或者使用更灵活的 http.Client。
发起 GET 请求
这是最简单的请求方式,用于获取资源。
package main
import (
"fmt"
"net/http"
"io" // 用于处理请求体和响应体
)
func main() {
// 1. 发起 GET 请求
resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
if err != nil {
fmt.Println("请求出错:", err)
return
}
// 2. 确保在函数结束时关闭响应体,释放资源
defer resp.Body.Close()
// 3. 检查 HTTP 状态码
if resp.StatusCode != http.StatusOK {
fmt.Printf("请求失败,状态码: %d\n", resp.StatusCode)
return
}
// 4. 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("读取响应体出错:", err)
return
}
// 5. 打印结果
fmt.Printf("响应状态: %s\n", resp.Status)
fmt.Printf("响应体:\n%s\n", body)
}发起 POST 请求
通常用于向服务器提交数据,例如 JSON 或表单数据。
package main
import (
"fmt"
"net/http"
"strings"
"io"
)
func main() {
// 假设我们要发送的 JSON 数据
jsonData := `{"title": "foo", "body": "bar", "userId": 1}`
// 1. 发起 POST 请求,指定内容类型为 application/json
resp, err := http.Post(
"https://jsonplaceholder.typicode.com/posts", // URL
"application/json", // Content-Type
strings.NewReader(jsonData), // 请求体数据源
)
if err != nil {
fmt.Println("请求出错:", err)
return
}
defer resp.Body.Close()
// ... (后续读取响应体的步骤与 GET 请求类似) ...
body, _ := io.ReadAll(resp.Body)
fmt.Printf("响应状态: %s\n", resp.Status)
fmt.Printf("响应体:\n%s\n", body)
}自定义请求头和客户端
对于更复杂的场景(例如设置超时、添加自定义 Header),你需要使用 http.NewRequest 构造请求,并使用 http.Client 来执行。
package main
import (
"fmt"
"net/http"
"time"
"io"
)
func main() {
// 1. 创建一个自定义的请求 (Request)
req, err := http.NewRequest("GET", "https://api.github.com/users/google", nil)
if err != nil {
// ...
return
}
// 2. 添加自定义请求头
req.Header.Add("Accept", "application/json")
req.Header.Add("User-Agent", "Go-Client-V1")
// 3. 创建一个自定义的 HTTP 客户端 (Client),设置超时
client := &http.Client{
Timeout: 10 * time.Second, // 设置 10 秒超时
}
// 4. 执行请求
resp, err := client.Do(req)
if err != nil {
fmt.Println("请求执行出错:", err)
return
}
defer resp.Body.Close()
// ... (后续读取响应体的步骤) ...
body, _ := io.ReadAll(resp.Body)
fmt.Printf("响应状态: %s\n", resp.Status)
// 打印部分响应体
if len(body) > 200 {
fmt.Printf("响应体 (部分):\n%s...\n", body[:200])
}
}3. 服务端 (Server) 实现
Go 的 net/http 模块让构建高性能的 HTTP 服务器变得异常简单。
基本服务器搭建
使用 http.HandleFunc 注册一个路由处理器,然后用 http.ListenAndServe 启动服务器。
package main
import (
"fmt"
"net/http"
)
// 处理器函数(Handler Function)的签名必须是 func(w http.ResponseWriter, r *http.Request)
func homeHandler(w http.ResponseWriter, r *http.Request) {
// w: 响应写入器,用于向客户端发送数据
// r: 请求对象,包含客户端发来的所有信息
fmt.Println("接收到一个请求:", r.URL.Path)
// 写入响应体
w.Write([]byte("Hello, Go HTTP Server!"))
}
func main() {
// 1. 注册路由和处理器函数
// "/" 表示所有请求都会匹配,除非有更精确的匹配
http.HandleFunc("/", homeHandler)
// 2. 启动服务器,监听 8080 端口
fmt.Println("服务器正在 127.0.0.1:8080 上运行...")
// http.ListenAndServe 会阻塞当前 goroutine
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("服务器启动失败:", err)
}
// 可以在浏览器访问 http://localhost:8080/ 进行测试
}处理不同路径的请求
可以注册不同的处理器来处理不同的 URL 路径。
// ... (引入和 main 函数结构不变) ...
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello from the /hello page!"))
}
func main() {
http.HandleFunc("/", homeHandler) // 处理 http://localhost:8080/
http.HandleFunc("/hello", helloHandler) // 处理 http://localhost:8080/hello
// ... (启动服务器) ...
}获取请求参数和表单数据
你可以从 http.Request 对象中获取 URL 查询参数、表单数据或 JSON 数据。
func paramHandler(w http.ResponseWriter, r *http.Request) {
// **1. 获取 URL 查询参数 (Query Parameters)**
// 访问 http://localhost:8080/params?name=Go&age=15
name := r.URL.Query().Get("name")
age := r.URL.Query().Get("age")
fmt.Fprintf(w, "查询参数 Name: %s, Age: %s\n", name, age)
// **2. 获取表单数据 (POST)**
// 确保请求方法是 POST
if r.Method == "POST" {
// 解析表单数据 (对于 POST/PUT 请求)
r.ParseForm()
postName := r.FormValue("name") // 也可以用 r.Form.Get("name")
fmt.Fprintf(w, "表单数据 Name: %s\n", postName)
}
}
func main() {
http.HandleFunc("/params", paramHandler)
// ... (启动服务器) ...
}设置响应头和状态码
在发送响应体之前,你可以设置 HTTP 响应头和状态码。
func responseHandler(w http.ResponseWriter, r *http.Request) {
// 1. 设置响应头 (Header)
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Custom-Header", "Golang-Server")
// 2. 写入状态码
// 默认是 http.StatusOK (200),可以根据需要更改
// 例如,返回一个“未授权” (401)
if r.URL.Query().Get("auth") != "true" {
w.WriteHeader(http.StatusUnauthorized) // 写入状态码
w.Write([]byte(`{"error": "Unauthorized"}`)) // 写入响应体
return
}
// 3. 正常响应
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message": "Success"}`))
}4. 进阶主题
创建自定义的 http.Server
http.ListenAndServe(":8080", nil) 实际上是创建了一个默认的 http.Server 实例。对于生产环境,我们通常会创建自定义的 http.Server 来配置 超时 等高级选项。
func main() {
// 1. 定义一个多路复用器 (Router/Mux)
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)
// 2. 创建自定义的 Server 实例
server := &http.Server{
Addr: ":8080",
Handler: mux, // 使用自定义的路由
ReadTimeout: 5 * time.Second, // 请求头和请求体读取超时
WriteTimeout: 10 * time.Second, // 响应写入超时
IdleTimeout: 120 * time.Second, // 连接最大空闲时间
}
// 3. 启动服务器
fmt.Println("服务器正在 127.0.0.1:8080 上运行...")
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
fmt.Println("服务器启动失败:", err)
}
}中间件 (Middleware) 概念
中间件是一种模式,用于在 HTTP 处理器执行前后执行一些通用逻辑,例如:日志记录、身份验证、Gzip 压缩 等。
一个简单的日志中间件示例如下:
// LogMiddleware 是一个 HOC (High-Order Component) / 高阶函数
func LogMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Pre-processing: 在调用实际处理器前执行
start := time.Now()
// 调用链中的下一个 Handler (即真正的业务逻辑)
next.ServeHTTP(w, r)
// Post-processing: 在实际处理器执行后执行
duration := time.Since(start)
fmt.Printf("[%s] %s %s took %v\n",
time.Now().Format("2006-01-02 15:04:05"),
r.Method, r.URL.Path, duration)
})
}
// 在 main 函数中使用:
func main() {
// 1. 创建真正的业务处理器
helloHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello from the inner handler!"))
})
// 2. 将业务处理器包装在中间件中
wrappedHandler := LogMiddleware(helloHandler)
// 3. 注册中间件包装后的处理器
http.Handle("/hello", wrappedHandler)
// ... (启动服务器) ...
}自定义 Transport
虽然我们通常使用 http.Client 来发起请求,但真正负责执行底层网络通信(包括连接的建立、连接池的管理、数据的传输、TLS 握手等)的是 http.Transport 结构体。
通过自定义 http.Transport,你可以对 HTTP 客户端的底层网络行为进行精细控制。比如:配置代理 (Proxy),使请求通过 HTTP/SOCKS 代理服务器发送。
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
func main() {
// 代理服务器地址
proxyURL, err := url.Parse("http://127.0.0.1:8899")
if err != nil {
fmt.Println("解析代理服务器地址失败:", err)
return
}
// 创建一个Transport对象,并设置代理
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
client := &http.Client{
Transport: transport,
}
// 准备要发送的数据
data := map[string]string{
"name": "小明",
"age": "18",
}
jsonData, err := json.Marshal(data)
if err != nil {
fmt.Println("JSON编码失败:", err)
return
}
// 发送POST请求
resp, err := client.Post("https://jsonplaceholder.typicode.com/posts",
"application/json",
bytes.NewBuffer(jsonData))
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close()
fmt.Printf("状态码: %d\n", resp.StatusCode)
}