3.1.2 net/http 包详解 #
Go 语言的 net/http
包是标准库中最重要的包之一,它提供了完整的 HTTP 客户端和服务器实现。这个包设计精良,既简单易用又功能强大,是 Go Web 开发的基石。本节将深入解析 net/http
包的核心组件和使用方法。
包结构概览 #
net/http
包的主要组件包括:
net/http/
├── 核心类型
│ ├── Request # HTTP 请求
│ ├── Response # HTTP 响应
│ ├── Header # HTTP 头部
│ └── Cookie # HTTP Cookie
├── 服务器组件
│ ├── Server # HTTP 服务器
│ ├── ServeMux # 请求路由器
│ ├── Handler # 请求处理器接口
│ └── HandlerFunc # 函数适配器
├── 客户端组件
│ ├── Client # HTTP 客户端
│ ├── Transport # 传输层
│ └── RoundTripper # 往返器接口
└── 工具函数
├── ListenAndServe # 启动服务器
├── Get/Post/... # 便捷请求函数
└── 各种工具函数
核心数据结构 #
Request 结构体 #
Request
代表一个 HTTP 请求:
type Request struct {
Method string // HTTP 方法 (GET, POST, PUT, etc.)
URL *url.URL // 请求的 URL
Proto string // 协议版本 ("HTTP/1.0", "HTTP/1.1", "HTTP/2.0")
ProtoMajor int // 协议主版本号
ProtoMinor int // 协议次版本号
Header Header // 请求头部
Body io.ReadCloser // 请求体
GetBody func() (io.ReadCloser, error) // 获取请求体副本的函数
ContentLength int64 // 内容长度
TransferEncoding []string // 传输编码
Close bool // 是否在响应后关闭连接
Host string // Host 头部的值
Form url.Values // 解析后的表单数据
PostForm url.Values // 解析后的 POST 表单数据
MultipartForm *multipart.Form // 解析后的多部分表单数据
Trailer Header // 尾部头部
RemoteAddr string // 远程地址
RequestURI string // 原始请求 URI
TLS *tls.ConnectionState // TLS 连接状态
Cancel <-chan struct{} // 请求取消通道 (已废弃)
Response *Response // 重定向响应 (仅客户端)
// Go 1.7+ 添加的 context 支持
ctx context.Context
}
Response 结构体 #
Response
代表一个 HTTP 响应:
type Response struct {
Status string // 状态行 (e.g. "200 OK")
StatusCode int // 状态码 (e.g. 200)
Proto string // 协议版本
ProtoMajor int // 协议主版本号
ProtoMinor int // 协议次版本号
Header Header // 响应头部
Body io.ReadCloser // 响应体
ContentLength int64 // 内容长度
TransferEncoding []string // 传输编码
Close bool // 是否关闭连接
Uncompressed bool // 是否未压缩
Trailer Header // 尾部头部
Request *Request // 对应的请求
TLS *tls.ConnectionState // TLS 连接状态
}
Header 类型 #
Header
是一个映射类型,用于表示 HTTP 头部:
type Header map[string][]string
Header 提供了丰富的方法:
// 基本操作
func (h Header) Add(key, value string) // 添加头部值
func (h Header) Set(key, value string) // 设置头部值
func (h Header) Get(key string) string // 获取头部值
func (h Header) Del(key string) // 删除头部
func (h Header) Values(key string) []string // 获取所有值
// 特殊方法
func (h Header) Write(w io.Writer) error // 写入头部到 Writer
func (h Header) Clone() Header // 克隆头部
使用示例:
package main
import (
"fmt"
"net/http"
)
func main() {
// 创建头部
header := make(http.Header)
// 设置头部值
header.Set("Content-Type", "application/json")
header.Set("Authorization", "Bearer token123")
// 添加多个值
header.Add("Accept", "application/json")
header.Add("Accept", "text/plain")
// 获取头部值
contentType := header.Get("Content-Type")
fmt.Println("Content-Type:", contentType)
// 获取所有 Accept 值
accepts := header.Values("Accept")
fmt.Println("Accept values:", accepts)
// 遍历所有头部
for key, values := range header {
fmt.Printf("%s: %v\n", key, values)
}
}
Cookie 结构体 #
Cookie
代表一个 HTTP cookie:
type Cookie struct {
Name string
Value string
Path string // 可选
Domain string // 可选
Expires time.Time // 可选
RawExpires string // 仅用于读取 cookie
// MaxAge=0 表示未指定 "Max-Age" 属性
// MaxAge<0 表示立即删除 cookie
// MaxAge>0 表示 Max-Age 属性存在且以秒为单位给出
MaxAge int
Secure bool
HttpOnly bool
SameSite SameSite
Raw string
Unparsed []string // 未解析的属性值对
}
Cookie 操作示例:
package main
import (
"fmt"
"net/http"
"time"
)
func setCookieHandler(w http.ResponseWriter, r *http.Request) {
// 创建 cookie
cookie := &http.Cookie{
Name: "session_id",
Value: "abc123xyz",
Path: "/",
Domain: "example.com",
Expires: time.Now().Add(24 * time.Hour),
MaxAge: 86400, // 24小时
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
}
// 设置 cookie
http.SetCookie(w, cookie)
fmt.Fprint(w, "Cookie 已设置")
}
func getCookieHandler(w http.ResponseWriter, r *http.Request) {
// 获取特定 cookie
cookie, err := r.Cookie("session_id")
if err != nil {
fmt.Fprintf(w, "Cookie 不存在: %v", err)
return
}
fmt.Fprintf(w, "Cookie 值: %s", cookie.Value)
// 获取所有 cookies
cookies := r.Cookies()
fmt.Fprintf(w, "\n所有 Cookies: %d 个", len(cookies))
for _, c := range cookies {
fmt.Fprintf(w, "\n%s = %s", c.Name, c.Value)
}
}
Handler 接口和 HandlerFunc #
Handler 接口 #
Handler
是 HTTP 处理器的核心接口:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
任何实现了 ServeHTTP
方法的类型都可以作为 HTTP 处理器。
HandlerFunc 类型 #
HandlerFunc
是一个适配器,允许普通函数作为 HTTP 处理器:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
使用示例:
package main
import (
"fmt"
"net/http"
)
// 实现 Handler 接口的结构体
type MyHandler struct {
message string
}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "MyHandler: %s", h.message)
}
// 普通函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello from HandlerFunc!")
}
func main() {
// 使用结构体处理器
handler1 := &MyHandler{message: "Hello World"}
http.Handle("/handler", handler1)
// 使用函数处理器
http.HandleFunc("/func", helloHandler)
// 使用匿名函数
http.HandleFunc("/anonymous", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello from anonymous function!")
})
http.ListenAndServe(":8080", nil)
}
ServeMux 路由器 #
ServeMux
是 Go 标准库提供的 HTTP 请求路由器:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
路由模式 #
ServeMux 支持两种路由模式:
- 固定路径:精确匹配
- 子树路径:以
/
结尾,匹配子路径
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
// 固定路径 - 精确匹配
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
})
// 子树路径 - 匹配 /api/ 下的所有路径
mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "API endpoint: %s", r.URL.Path)
})
// 根路径 - 匹配所有未匹配的路径
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
fmt.Fprint(w, "Home Page")
})
// 带主机名的路由
mux.HandleFunc("api.example.com/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "API subdomain")
})
http.ListenAndServe(":8080", mux)
}
路由优先级 #
ServeMux 按照以下优先级匹配路由:
- 精确匹配优先于模式匹配
- 长模式优先于短模式
- 带主机名的模式优先于不带主机名的模式
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
// 这些路由的匹配优先级
mux.HandleFunc("/api/users/profile", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "User Profile (精确匹配)")
})
mux.HandleFunc("/api/users/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Users API (长模式)")
})
mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "API (短模式)")
})
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Root (最短模式)")
})
http.ListenAndServe(":8080", mux)
}
Server 结构体 #
Server
定义了运行 HTTP 服务器的参数:
type Server struct {
Addr string // 监听地址
Handler Handler // 处理器,如果为 nil 则使用 DefaultServeMux
// 超时设置
ReadTimeout time.Duration // 读取超时
ReadHeaderTimeout time.Duration // 读取头部超时
WriteTimeout time.Duration // 写入超时
IdleTimeout time.Duration // 空闲超时
// TLS 配置
TLSConfig *tls.Config
// 连接状态回调
ConnState func(net.Conn, ConnState)
// 错误日志
ErrorLog *log.Logger
// 其他配置
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnContext func(ctx context.Context, c net.Conn) context.Context
BaseContext func(net.Listener) context.Context
}
服务器配置示例 #
package main
import (
"context"
"fmt"
"log"
"net/http"
"time"
)
func main() {
// 创建处理器
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
})
// 配置服务器
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
MaxHeaderBytes: 1 << 20, // 1MB
ErrorLog: log.New(os.Stderr, "HTTP Server: ", log.LstdFlags),
}
// 启动服务器
log.Printf("服务器启动在 %s", server.Addr)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务器启动失败: %v", err)
}
}
优雅关闭 #
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 模拟长时间处理
time.Sleep(2 * time.Second)
fmt.Fprint(w, "Hello World")
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// 在 goroutine 中启动服务器
go func() {
log.Println("服务器启动在 :8080")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务器启动失败: %v", err)
}
}()
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("正在关闭服务器...")
// 创建超时上下文
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 优雅关闭服务器
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("服务器关闭失败: %v", err)
}
log.Println("服务器已关闭")
}
Client 和 Transport #
Client 结构体 #
Client
是 HTTP 客户端:
type Client struct {
Transport RoundTripper // 传输层
CheckRedirect func(req *Request, via []*Request) error // 重定向检查
Jar CookieJar // Cookie 管理器
Timeout time.Duration // 超时时间
}
默认客户端 #
Go 提供了一个默认的客户端:
var DefaultClient = &Client{}
便捷函数如 http.Get
、http.Post
等都使用这个默认客户端。
自定义客户端 #
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
// 创建自定义客户端
client := &http.Client{
Timeout: 10 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 限制重定向次数
if len(via) >= 3 {
return fmt.Errorf("重定向次数过多")
}
return nil
},
}
// 发送请求
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
fmt.Printf("请求失败: %v\n", err)
return
}
defer resp.Body.Close()
fmt.Printf("状态码: %d\n", resp.StatusCode)
fmt.Printf("状态: %s\n", resp.Status)
}
Transport 配置 #
Transport
实现了 RoundTripper
接口,负责实际的 HTTP 传输:
package main
import (
"crypto/tls"
"fmt"
"net/http"
"time"
)
func main() {
// 自定义 Transport
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
DisableCompression: false,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: false,
},
}
// 创建使用自定义 Transport 的客户端
client := &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
fmt.Printf("请求失败: %v\n", err)
return
}
defer resp.Body.Close()
fmt.Printf("状态码: %d\n", resp.StatusCode)
}
ResponseWriter 接口 #
ResponseWriter
接口用于构建 HTTP 响应:
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}
响应写入顺序 #
正确的响应写入顺序:
- 设置头部(可选)
- 调用
WriteHeader
(可选,默认 200) - 写入响应体
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func userHandler(w http.ResponseWriter, r *http.Request) {
user := User{ID: 1, Name: "张三"}
// 1. 设置头部
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-cache")
// 2. 设置状态码(可选)
w.WriteHeader(http.StatusOK)
// 3. 写入响应体
if err := json.NewEncoder(w).Encode(user); err != nil {
// 注意:这里不能再调用 WriteHeader
fmt.Printf("编码错误: %v\n", err)
}
}
func main() {
http.HandleFunc("/user", userHandler)
http.ListenAndServe(":8080", nil)
}
常见错误处理 #
package main
import (
"encoding/json"
"net/http"
)
type ErrorResponse struct {
Error string `json:"error"`
Code int `json:"code"`
Message string `json:"message"`
}
func sendError(w http.ResponseWriter, statusCode int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
errorResp := ErrorResponse{
Error: http.StatusText(statusCode),
Code: statusCode,
Message: message,
}
json.NewEncoder(w).Encode(errorResp)
}
func apiHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
sendError(w, http.StatusMethodNotAllowed, "只支持 GET 方法")
return
}
// 模拟业务逻辑错误
if r.URL.Query().Get("id") == "" {
sendError(w, http.StatusBadRequest, "缺少 id 参数")
return
}
// 正常响应
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message": "success"}`))
}
func main() {
http.HandleFunc("/api", apiHandler)
http.ListenAndServe(":8080", nil)
}
中间件模式 #
虽然标准库没有内置中间件支持,但可以通过函数组合实现:
package main
import (
"fmt"
"log"
"net/http"
"time"
)
// 中间件类型
type Middleware func(http.Handler) http.Handler
// 日志中间件
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 调用下一个处理器
next.ServeHTTP(w, r)
// 记录日志
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
// 认证中间件
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "未授权", http.StatusUnauthorized)
return
}
// 验证 token(这里简化处理)
if token != "Bearer valid-token" {
http.Error(w, "无效 token", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// CORS 中间件
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
// 中间件链
func Chain(middlewares ...Middleware) Middleware {
return func(next http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
next = middlewares[i](next)
}
return next
}
}
func main() {
// 业务处理器
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
})
// 应用中间件链
finalHandler := Chain(
LoggingMiddleware,
CORSMiddleware,
AuthMiddleware,
)(handler)
http.Handle("/api", finalHandler)
// 公开端点(不需要认证)
publicHandler := Chain(
LoggingMiddleware,
CORSMiddleware,
)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Public endpoint")
}))
http.Handle("/public", publicHandler)
log.Println("服务器启动在 :8080")
http.ListenAndServe(":8080", nil)
}
文件服务 #
net/http
包提供了静态文件服务功能:
package main
import (
"net/http"
"log"
)
func main() {
// 方法1:使用 FileServer
fs := http.FileServer(http.Dir("./static/"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// 方法2:使用 ServeFile
http.HandleFunc("/download", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./files/document.pdf")
})
// 方法3:自定义文件处理
http.HandleFunc("/files/", func(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Path[len("/files/"):]
if filename == "" {
http.Error(w, "文件名不能为空", http.StatusBadRequest)
return
}
// 设置下载头部
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
http.ServeFile(w, r, "./uploads/"+filename)
})
log.Println("文件服务器启动在 :8080")
http.ListenAndServe(":8080", nil)
}
最佳实践 #
1. 合理设置超时 #
server := &http.Server{
Addr: ":8080",
ReadTimeout: 10 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
2. 使用上下文控制 #
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case <-time.After(5 * time.Second):
fmt.Fprint(w, "处理完成")
case <-ctx.Done():
// 请求被取消
return
}
}
3. 正确处理错误 #
func handler(w http.ResponseWriter, r *http.Request) {
data, err := processRequest(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(data); err != nil {
log.Printf("编码错误: %v", err)
}
}
4. 使用连接池 #
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
}
client := &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
小结 #
net/http
包是 Go Web 开发的基础,本节详细介绍了:
- 核心数据结构:Request、Response、Header、Cookie 的结构和用法
- 处理器系统:Handler 接口、HandlerFunc 类型和 ServeMux 路由器
- 服务器配置:Server 结构体的配置选项和优雅关闭
- 客户端功能:Client 和 Transport 的配置和使用
- 响应写入:ResponseWriter 接口的正确使用方法
- 高级特性:中间件模式、文件服务等实用功能
掌握这些核心概念和组件,将为后续的 HTTP 服务端和客户端开发奠定坚实的基础。在下一节中,我们将学习如何使用这些组件构建完整的 HTTP 服务器。