3. HTTP 服务基础:net/http 包使用、路由注册、请求与响应处理、中间件概念
构建任务调度平台的第一步是搭建稳定、可扩展的 HTTP 服务。Go 标准库 net/http 提供了构建 Web 服务所需的核心原语,理解其工作原理是后续实现 OpenAPI 参数校验的基石。本章将系统讲解 HTTP 服务的启动、路由注册、请求/响应处理以及中间件模式。
3.1 net/http 包核心组件
net/http 包围绕两个核心接口构建:Handler 和 HandlerFunc。任何实现了 ServeHTTP(ResponseWriter, *Request) 方法的对象都可以作为 HTTP 处理器。
| 组件 | 类型 | 说明 |
|---|---|---|
http.Handler |
接口 | 定义 ServeHTTP(ResponseWriter, *Request) 方法 |
http.HandlerFunc |
函数类型 | 将普通函数适配为 Handler 的适配器 |
http.Server |
结构体 | 配置服务器地址、端口、超时等参数 |
http.ServeMux |
结构体 | 默认的路由多路复用器,支持路径匹配 |
核心要点:
http.ListenAndServe 是启动服务的快捷方式,内部创建 http.Server 并调用 ListenAndServe 方法。生产环境建议使用 http.Server 结构体以配置超时等参数。
3.2 路由注册与请求分发
Go 1.22 之前的版本使用 http.ServeMux 进行路由匹配,支持精确路径和前缀匹配。Go 1.22 引入了增强路由,支持方法匹配和路径参数。
3.2.1 基础路由注册
package main
import (
"fmt"
"net/http"
)
func main() {
// 方式一:使用默认 ServeMux
http.HandleFunc("/api/tasks", handleTasks)
http.HandleFunc("/api/tasks/", handleTaskByID) // 前缀匹配
// 方式二:显式创建 ServeMux
mux := http.NewServeMux()
mux.HandleFunc("/api/health", handleHealth)
mux.HandleFunc("/api/version", handleVersion)
// 启动服务
http.ListenAndServe(":8080", mux)
}
func handleTasks(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Task list endpoint")
}
func handleTaskByID(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Task detail endpoint")
}
func handleHealth(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"status":"ok"}`)
}
3.2.2 Go 1.22+ 增强路由
// Go 1.22 支持方法匹配和路径参数
mux := http.NewServeMux()
mux.HandleFunc("GET /api/tasks", listTasks)
mux.HandleFunc("POST /api/tasks", createTask)
mux.HandleFunc("GET /api/tasks/{id}", getTaskByID)
mux.HandleFunc("PUT /api/tasks/{id}", updateTask)
mux.HandleFunc("DELETE /api/tasks/{id}", deleteTask)
func getTaskByID(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // 获取路径参数
fmt.Fprintf(w, "Task ID: %s", id)
}
实践建议: 任务调度平台的 API 设计遵循 RESTful 风格,建议使用 Go 1.22+ 的增强路由,明确声明 HTTP 方法和路径参数,提升代码可读性和安全性。
3.3 请求与响应处理
每个 HTTP 请求都通过 *http.Request 对象暴露,响应通过 http.ResponseWriter 接口写入。正确处理请求解析和响应编码是参数校验的前提。
3.3.1 请求解析
| 数据来源 | 访问方式 | 示例 |
|---|---|---|
| URL 查询参数 | r.URL.Query() |
r.URL.Query().Get("page") |
| 路径参数 | r.PathValue() |
r.PathValue("id") |
| 请求头 | r.Header |
r.Header.Get("Content-Type") |
| JSON 请求体 | json.NewDecoder(r.Body) |
json.NewDecoder(r.Body).Decode(&task) |
| 表单数据 | r.ParseForm() |
r.FormValue("name") |
3.3.2 响应编码
func respondJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func respondError(w http.ResponseWriter, status int, message string) {
respondJSON(w, status, map[string]string{"error": message})
}
// 使用示例
func createTask(w http.ResponseWriter, r *http.Request) {
var task Task
if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
respondError(w, http.StatusBadRequest, "Invalid request body")
return
}
// 业务逻辑...
respondJSON(w, http.StatusCreated, task)
}
常见陷阱: 响应体必须在使用后关闭
r.Body.Close(),否则可能导致资源泄漏。标准库在请求处理结束后会自动关闭,但手动关闭是良好实践。另外,WriteHeader 必须在写入响应体之前调用,且只能调用一次。
3.4 中间件概念与实现
中间件是 HTTP 处理链中的可插拔组件,用于在请求到达处理器之前或之后执行横切关注点(如日志、认证、参数校验)。Go 中中间件的核心模式是 装饰器模式:一个中间件接收 http.Handler,返回一个新的 http.Handler。
3.4.1 中间件模式
// 中间件类型定义
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()
log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr)
next.ServeHTTP(w, r)
log.Printf("[%s] %s completed in %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 == "" || !validateToken(token) {
respondError(w, http.StatusUnauthorized, "Missing or invalid token")
return
}
next.ServeHTTP(w, r)
})
}
// 中间件链组合
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
3.4.2 中间件在任务调度平台中的应用
在任务调度平台中,中间件用于实现以下横切关注点:
- 请求日志:记录每个 API 调用的方法、路径、耗时和状态码
- 认证与鉴权:验证 API Key 或 JWT Token,提取用户身份
- 参数校验:在进入业务逻辑前对请求参数进行格式和类型校验
- 限流控制:基于 IP 或用户 ID 进行请求频率限制
- 超时控制:为每个请求设置最大处理时间,防止资源耗尽
- 恢复处理:捕获 panic,返回 500 错误,避免服务崩溃
中间件执行顺序: 中间件链的执行顺序与注册顺序相反。第一个注册的中间件最先处理请求,最后处理响应。例如
Chain(handler, LoggingMiddleware, AuthMiddleware) 的执行顺序是:Logging → Auth → Handler → Auth → Logging。
3.5 HTTP 服务架构总览
下图展示了任务调度平台 HTTP 服务的完整请求处理流程,从请求进入中间件链到最终响应返回:
3.6 完整服务启动示例
以下代码展示了如何将上述概念整合为一个可运行的任务调度 HTTP 服务:
package main
import (
"log"
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
// 注册任务相关路由
mux.HandleFunc("GET /api/v1/tasks", listTasks)
mux.HandleFunc("POST /api/v1/tasks", createTask)
mux.HandleFunc("GET /api/v1/tasks/{id}", getTask)
mux.HandleFunc("PUT /api/v1/tasks/{id}", updateTask)
mux.HandleFunc("DELETE /api/v1/tasks/{id}", deleteTask)
// 注册健康检查
mux.HandleFunc("GET /api/v1/health", healthCheck)
// 构建中间件链
handler := Chain(mux,
RecoveryMiddleware, // 恢复处理
LoggingMiddleware, // 请求日志
AuthMiddleware, // 认证鉴权
RateLimitMiddleware, // 限流控制
)
// 配置服务器
server := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Printf("Task scheduler API server starting on :8080")
if err := server.ListenAndServe(); err != nil {
log.Fatalf("Server failed: %v", err)
}
}
生产环境配置: 始终为
http.Server 设置超时参数。ReadTimeout 防止慢客户端攻击,WriteTimeout 防止响应写入阻塞,IdleTimeout 控制 Keep-Alive 连接的空闲时间。这些配置是保障服务稳定性的基础。
3.7 本章小结
本章涵盖了使用 Go 标准库 net/http 构建 HTTP 服务的完整知识体系:
- 核心组件:
Handler、HandlerFunc、ServeMux、Server的关系与使用方式 - 路由注册:基础路径匹配与 Go 1.22+ 增强路由的方法匹配和路径参数
- 请求与响应:解析查询参数、路径参数、请求头、JSON 体,以及标准化的 JSON 响应编码
- 中间件模式:装饰器模式实现横切关注点,中间件链的组合与执行顺序
- 服务配置:超时设置、优雅启动与关闭