📖 Web 开发¶
学习时间: 约 5-6 小时 | 难度: ⭐⭐⭐ 中级 | 前置知识: Go基础语法、接口、并发编程
📚 章节概述¶
Go 凭借出色的并发能力和内置的 net/http 库,成为构建高性能 Web 服务的热门选择。本章将从标准库 HTTP 服务器出发,到 Gin 框架的生产级实践,覆盖路由、中间件、请求处理、JSON API、文件上传、WebSocket、优雅关停等核心主题。
上图展示了从客户端到路由、中间件、处理器再到 JSON 响应的核心请求流程。
🎯 学习目标¶
- 精通 net/http 标准库的 Handler 模式
- 掌握 Gin 框架的路由、中间件、绑定
- 理解 RESTful API 设计原则
- 学会实现认证/授权中间件
- 掌握请求验证和错误处理
- 了解优雅关停和生产部署
📌 概念:net/http 标准库¶
1.1 基础 HTTP 服务器¶
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
// 设置响应头
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
// 写入状态码(必须在 Write 之前)
w.WriteHeader(http.StatusOK)
// 写入响应体
fmt.Fprintf(w, "Hello, %s! 时间: %s", r.URL.Path[1:], time.Now().Format(time.RFC3339))
}
func main() {
// 注册处理器
http.HandleFunc("/hello/", helloHandler)
// 启动服务器
log.Println("服务器启动在 :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
1.2 自定义 Handler¶
// http.Handler 接口 —— Go 标准库中 HTTP 请求处理的核心抽象
// 任何实现了 ServeHTTP 方法的类型都可以作为 HTTP 请求处理器
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// 实现 Handler 接口 —— 通过结构体封装依赖(依赖注入模式)
// 将数据库连接注入到处理器中,便于测试和复用
type APIHandler struct {
db *sql.DB
}
// ServeHTTP 根据 HTTP 方法分发到对应的处理函数
func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.handleGet(w, r)
case http.MethodPost:
h.handlePost(w, r)
default:
// 返回 405 Method Not Allowed 状态码
http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
}
}
// handleGet 处理 GET 请求,返回 JSON 格式响应
func (h *APIHandler) handleGet(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// 使用 json.Encoder 直接写入 ResponseWriter,避免中间缓冲
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
// handlePost 处理 POST 请求,解析请求体中的 JSON 数据
func (h *APIHandler) handlePost(w http.ResponseWriter, r *http.Request) {
var data map[string]any
// 从请求体解码 JSON,使用流式解码器避免一次性读取全部内容
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
http.Error(w, "无效的 JSON", http.StatusBadRequest)
return
}
// 返回 201 Created 表示资源创建成功
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(data)
}
1.3 Go 1.22 增强路由¶
// Go 1.22 新增了路径参数和方法匹配,无需第三方路由库即可构建 RESTful API
func main() {
mux := http.NewServeMux()
// 方法匹配 —— 在路径前指定 HTTP 方法,实现精准路由
mux.HandleFunc("GET /api/users", listUsers)
mux.HandleFunc("POST /api/users", createUser)
// 路径参数 —— 用 {id} 捕获 URL 中的动态部分
mux.HandleFunc("GET /api/users/{id}", getUser)
mux.HandleFunc("PUT /api/users/{id}", updateUser)
mux.HandleFunc("DELETE /api/users/{id}", deleteUser)
// 通配符 —— {path...} 匹配剩余所有路径段,适合文件服务
mux.HandleFunc("GET /files/{path...}", serveFile)
log.Fatal(http.ListenAndServe(":8080", mux))
}
func getUser(w http.ResponseWriter, r *http.Request) {
// PathValue 是 Go 1.22 新增方法,用于提取路由中的命名参数
id := r.PathValue("id")
fmt.Fprintf(w, "用户 ID: %s", id)
}
1.4 中间件模式¶
// 中间件本质是 Handler 的装饰器(高阶函数模式)
// 接收一个 Handler 并返回增强后的新 Handler
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()
// 包装 ResponseWriter 以拦截并记录状态码(默认200)
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
// 调用下一个处理器(洋葱模型:进入内层)
next.ServeHTTP(wrapped, r)
// 请求处理完成后记录日志(洋葱模型:返回外层)
log.Printf("%s %s %d %s",
r.Method, r.URL.Path, wrapped.statusCode, time.Since(start))
})
}
// responseWriter 包装器 —— 通过嵌入原始 ResponseWriter 实现代理模式
// 拦截 WriteHeader 调用以捕获实际的 HTTP 状态码
type responseWriter struct {
http.ResponseWriter
statusCode int
}
// WriteHeader 覆写:先记录状态码,再委托给原始 ResponseWriter
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// CORS 中间件 —— 处理跨域资源共享,允许浏览器跨域访问 API
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 设置 CORS 响应头,允许所有来源访问
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")
// OPTIONS 预检请求直接返回,不传递给后续处理器
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
// ChainMiddleware 将多个中间件按顺序包装到处理器上
// 从后向前遍历,保证执行顺序与参数顺序一致
func ChainMiddleware(handler http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/data", dataHandler)
// 链式中间件:请求依次经过 CORSMiddleware → LoggingMiddleware → mux
handler := ChainMiddleware(mux, LoggingMiddleware, CORSMiddleware)
http.ListenAndServe(":8080", handler)
}
📌 概念:Gin 框架¶
2.1 Gin 基础¶
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// gin.Default() 创建引擎并自动注册 Logger(日志)和 Recovery(崩溃恢复)中间件
r := gin.Default()
// 基本路由 —— gin.H 是 map[string]any 的快捷写法,自动序列化为 JSON
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "pong"})
})
// 路由参数 —— :id 是路径参数,通过 c.Param("id") 获取
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"id": id})
})
// 查询参数 —— DefaultQuery 在参数不存在时返回默认值
// 例如 /search?q=golang&page=2
r.GET("/search", func(c *gin.Context) {
query := c.DefaultQuery("q", "")
page := c.DefaultQuery("page", "1")
c.JSON(http.StatusOK, gin.H{
"query": query,
"page": page,
})
})
// 启动 HTTP 服务器,监听 8080 端口
r.Run(":8080")
}
2.2 请求绑定与验证¶
// 创建用户请求体 —— binding 标签定义验证规则(基于 go-playground/validator)
// required: 必填; min/max: 字符串长度范围; email: 邮箱格式; gte/lte: 数值范围; oneof: 枚举值
type CreateUserRequest struct {
Name string `json:"name" binding:"required,min=2,max=50"` // 姓名:必填,2-50字符
Email string `json:"email" binding:"required,email"` // 邮箱:必填,需符合邮箱格式
Password string `json:"password" binding:"required,min=8"` // 密码:必填,至少8字符
Age int `json:"age" binding:"required,gte=1,lte=150"` // 年龄:必填,1-150之间
Role string `json:"role" binding:"oneof=admin user guest"` // 角色:仅允许 admin/user/guest
}
// 更新用户请求体 —— 使用指针类型 + omitempty 实现部分更新(PATCH 语义)
// 指针为 nil 表示不更新该字段,非 nil 时才触发验证
type UpdateUserRequest struct {
Name *string `json:"name" binding:"omitempty,min=2,max=50"`
Email *string `json:"email" binding:"omitempty,email"`
Age *int `json:"age" binding:"omitempty,gte=1,lte=150"`
}
// 分页查询参数 —— form 标签用于绑定 URL 查询参数(?page=1&page_size=20&sort=asc)
type QueryParams struct {
Page int `form:"page" binding:"gte=1"` // 页码:从1开始
PageSize int `form:"page_size" binding:"gte=1,lte=100"` // 每页条数:1-100
Sort string `form:"sort" binding:"oneof=asc desc"` // 排序方向:升序或降序
}
func createUser(c *gin.Context) {
var req CreateUserRequest
// ShouldBindJSON 从请求体解析 JSON 并自动执行 binding 标签中的验证规则
// 验证失败返回错误,由开发者自行处理(比 BindJSON 更灵活)
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "参数验证失败",
"details": err.Error(),
})
return
}
// 验证通过,处理业务逻辑...
c.JSON(http.StatusCreated, gin.H{
"message": "用户创建成功",
"user": gin.H{
"name": req.Name,
"email": req.Email,
},
})
}
2.3 完整 RESTful API¶
// User 数据模型 —— json 标签控制 JSON 序列化时的字段名
type User struct {
ID uint `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
// UserHandler HTTP 处理器 —— 持有 service 层引用,遵循 Handler → Service → Repository 分层架构
type UserHandler struct {
service *UserService
}
// NewUserHandler 构造函数 —— 依赖注入 Service 层
func NewUserHandler(svc *UserService) *UserHandler {
return &UserHandler{service: svc}
}
// RegisterRoutes 将 CRUD 路由注册到路由组 —— RESTful 风格
// GET /users → 列表 | POST /users → 创建
// GET /users/:id → 详情 | PUT /users/:id → 更新
// DELETE /users/:id → 删除
func (h *UserHandler) RegisterRoutes(rg *gin.RouterGroup) {
users := rg.Group("/users")
{
users.GET("", h.List)
users.POST("", h.Create)
users.GET("/:id", h.Get)
users.PUT("/:id", h.Update)
users.DELETE("/:id", h.Delete)
}
}
// List 分页查询用户列表
func (h *UserHandler) List(c *gin.Context) {
// 从查询参数中获取分页信息,提供合理的默认值
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
// 传递 context 以支持超时控制和请求取消
users, total, err := h.service.List(c.Request.Context(), page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取用户列表失败"})
return
}
// 返回分页响应,包含数据、总数和分页信息
c.JSON(http.StatusOK, gin.H{
"data": users,
"total": total,
"page": page,
"page_size": pageSize,
})
}
// Get 根据 ID 查询单个用户 —— 区分 404(不存在)和 500(内部错误)
func (h *UserHandler) Get(c *gin.Context) {
// 解析路径参数并转换为 uint64 类型
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户 ID"})
return
}
user, err := h.service.GetByID(c.Request.Context(), uint(id))
if err != nil {
// 使用 errors.Is 精确匹配错误类型,返回对应的 HTTP 状态码
if errors.Is(err, ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败"})
return
}
c.JSON(http.StatusOK, gin.H{"data": user})
}
// Create 创建用户 —— 绑定并验证请求体,成功返回 201
func (h *UserHandler) Create(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
user, err := h.service.Create(c.Request.Context(), req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建失败"})
return
}
// 201 Created 表示资源已成功创建
c.JSON(http.StatusCreated, gin.H{"data": user})
}
// Update 更新用户信息 —— 从路径获取 ID,从请求体获取更新数据
func (h *UserHandler) Update(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
var req UpdateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
user, err := h.service.Update(c.Request.Context(), uint(id), req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新失败"})
return
}
c.JSON(http.StatusOK, gin.H{"data": user})
}
// Delete 删除用户 —— 成功返回确认消息
func (h *UserHandler) Delete(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if err := h.service.Delete(c.Request.Context(), uint(id)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
}
2.4 Gin 中间件实战¶
// JWT 认证中间件 —— 从请求头提取并验证 JWT Token
// 返回闭包函数(gin.HandlerFunc),通过闭包捕获 secret 密钥
func JWTAuthMiddleware(secret string) gin.HandlerFunc {
return func(c *gin.Context) {
// 从 Authorization 请求头获取 Token
tokenStr := c.GetHeader("Authorization")
if tokenStr == "" {
// AbortWithStatusJSON 终止后续中间件/处理器的执行并返回错误
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "缺少 Authorization 头",
})
return
}
// 去掉 "Bearer " 前缀,提取实际的 JWT 字符串
if len(tokenStr) > 7 && tokenStr[:7] == "Bearer " {
tokenStr = tokenStr[7:]
}
// 解析并验证 JWT Token(检查签名、过期时间等)
claims, err := ParseJWT(tokenStr, secret)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "无效的 token",
})
return
}
// 将认证信息存入 Gin Context,后续处理器可通过 c.Get() 获取
c.Set("user_id", claims.UserID)
c.Set("role", claims.Role)
c.Next() // 继续执行后续中间件和处理器
}
}
// RateLimitMiddleware 基于 IP 的滑动窗口速率限制中间件
// limit: 时间窗口内最大请求数; window: 时间窗口长度
func RateLimitMiddleware(limit int, window time.Duration) gin.HandlerFunc {
// 客户端计数器结构
type client struct {
count int
lastSeen time.Time
}
// 使用 map 存储每个 IP 的请求计数(闭包中共享状态)
clients := make(map[string]*client)
var mu sync.Mutex // 互斥锁保证并发安全
return func(c *gin.Context) {
ip := c.ClientIP()
mu.Lock()
cl, exists := clients[ip]
// 新客户端或超出时间窗口 → 重置计数器
if !exists || time.Since(cl.lastSeen) > window {
clients[ip] = &client{count: 1, lastSeen: time.Now()}
mu.Unlock()
c.Next()
return
}
// 累加请求计数
cl.count++
cl.lastSeen = time.Now()
// 超过限制 → 返回 429 Too Many Requests
if cl.count > limit {
mu.Unlock()
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "请求过于频繁",
})
return
}
mu.Unlock()
c.Next()
}
}
// RequestIDMiddleware 请求追踪中间件 —— 为每个请求分配唯一 ID,便于日志追踪和问题排查
func RequestIDMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 优先使用客户端传入的请求 ID(支持分布式链路追踪)
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
// 客户端未提供则自动生成 UUID
requestID = uuid.New().String()
}
// 同时存入 Context 和响应头,方便上下游使用
c.Set("request_id", requestID)
c.Header("X-Request-ID", requestID)
c.Next()
}
}
// setupRouter 配置路由和中间件 —— 演示全局中间件与路由组中间件的区别
func setupRouter() *gin.Engine {
// gin.New() 创建无默认中间件的引擎(与 gin.Default() 不同)
r := gin.New()
// 全局中间件 —— 所有路由都会经过
r.Use(gin.Recovery()) // 崩溃恢复,防止 panic 导致进程退出
r.Use(RequestIDMiddleware()) // 请求追踪
r.Use(RateLimitMiddleware(100, time.Minute)) // 每分钟每 IP 最多100次请求
// 公开路由 —— 无需认证即可访问
public := r.Group("/api/v1")
{
public.POST("/login", loginHandler)
public.POST("/register", registerHandler)
}
// 需要认证的路由 —— 路由组级中间件,仅该组路由需要 JWT 认证
auth := r.Group("/api/v1")
auth.Use(JWTAuthMiddleware("my-secret"))
{
auth.GET("/profile", profileHandler)
auth.PUT("/profile", updateProfileHandler)
}
return r
}
📌 概念:优雅关停¶
func main() {
r := setupRouter()
srv := &http.Server{
Addr: ":8080",
Handler: r,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// 在 goroutine 中启动服务器
go func() {
log.Printf("服务器启动在 %s", srv.Addr)
if err := srv.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("正在关闭服务器...")
// 给已有连接 30 秒处理时间
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("强制关闭: %v", err)
}
log.Println("服务器已关闭")
}
💻 代码示例:完整 API 项目结构¶
myapi/
├── cmd/
│ └── server/
│ └── main.go # 入口
├── internal/
│ ├── handler/ # HTTP 处理器
│ │ ├── user.go
│ │ └── middleware.go
│ ├── service/ # 业务逻辑
│ │ └── user.go
│ ├── repository/ # 数据层
│ │ └── user.go
│ ├── model/ # 数据模型
│ │ └── user.go
│ └── config/ # 配置
│ └── config.go
├── pkg/
│ └── response/ # 统一响应
│ └── response.go
├── go.mod
└── Makefile
// pkg/response/response.go — 统一响应格式
// 封装通用的 API 响应结构,确保所有接口返回格式一致
package response
import (
"net/http"
"github.com/gin-gonic/gin"
)
// Response 统一响应结构体
// Code=0 表示成功,非0表示错误码; omitempty 使 Data 为空时不输出该字段
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
// Success 返回 200 成功响应
func Success(c *gin.Context, data any) {
c.JSON(http.StatusOK, Response{
Code: 0,
Message: "success",
Data: data,
})
}
// Created 返回 201 资源创建成功响应
func Created(c *gin.Context, data any) {
c.JSON(http.StatusCreated, Response{
Code: 0,
Message: "created",
Data: data,
})
}
// Error 返回错误响应,statusCode 作为 HTTP 状态码和业务错误码
func Error(c *gin.Context, statusCode int, message string) {
c.JSON(statusCode, Response{
Code: statusCode,
Message: message,
})
}
// Paginated 返回分页查询响应,包含数据列表和分页元信息
func Paginated(c *gin.Context, data any, total, page, pageSize int) {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": data,
"total": total,
"page": page,
"page_size": pageSize,
})
}
✅ 最佳实践¶
1. 使用 context 传递请求级数据¶
func (h *Handler) GetUser(c *gin.Context) {
// 从 HTTP 请求中提取 context,用于传递超时、取消信号和请求级元数据
ctx := c.Request.Context()
// context 在 Handler → Service → Repository 整个调用链中透传
user, err := h.service.GetByID(ctx, id)
}
2. 分离 handler/service/repository 层¶
3. 统一的错误响应格式¶
// ✅ 统一格式
{"code": 400, "message": "参数验证失败", "details": [...]}
// ❌ 不统一
{"error": "bad request"} // 有时这样
{"msg": "fail"} // 有时那样
4. 生产环境使用 TLS¶
5. 合理配置超时¶
srv := &http.Server{
ReadTimeout: 15 * time.Second, // 读取整个请求的超时时间
ReadHeaderTimeout: 5 * time.Second, // 仅读取请求头的超时(防慢速攻击)
WriteTimeout: 15 * time.Second, // 写入响应的超时时间
IdleTimeout: 60 * time.Second, // Keep-Alive 连接空闲超时
MaxHeaderBytes: 1 << 20, // 1 MB,限制请求头大小防止内存滥用
}
🎯 面试题¶
Q1: net/http 的 Handler 和 HandlerFunc 有什么关系?¶
A: Handler 是接口(要求实现 ServeHTTP 方法),HandlerFunc 是函数类型适配器——它本身是 func(ResponseWriter, *Request) 类型,并实现了 Handler 接口(func (f HandlerFunc) ServeHTTP(w, r) { f(w, r) })。这使得普通函数可以直接用作 Handler。
Q2: Gin 的中间件是如何执行的?Next() 有什么作用?¶
A: Gin 中间件按注册顺序组成链表。c.Next() 会暂停当前中间件,执行后续中间件和 handler,全部完成后返回继续执行当前中间件的剩余代码。c.Abort() 会终止后续中间件的执行。类似洋葱模型:请求从外到内,响应从内到外。
Q3: 如何实现 HTTP 服务的优雅关停?¶
A: 1) 使用 signal.Notify 监听 SIGINT/SIGTERM;2) 收到信号后调用 srv.Shutdown(ctx);3) Shutdown 会停止接受新连接,等待已有连接处理完毕(或超时后强制关闭);4) 关闭数据库连接等资源。
Q4: Go 的 HTTP 服务器为什么高性能?¶
A: 1) 每个请求在独立的 goroutine 中处理(goroutine 很轻量,初始 2-8KB 栈);2) 基于 epoll/kqueue 的非阻塞 I/O 多路复用;3) 标准库代码高度优化;4) 无GIL限制,真正的多核并发;5) 内存分配少(对象池化、零拷贝写入)。
Q5: ShouldBindJSON 和 BindJSON 的区别?¶
A: BindJSON 在绑定失败时自动返回 400 响应并调用 c.Abort()。ShouldBindJSON 只返回错误,让开发者自行决定如何处理。推荐使用 ShouldBind* 系列以获得更灵活的错误处理控制。
Q6: 如何防止 Go HTTP 服务的常见安全问题?¶
A: 1) 设置 ReadTimeout/WriteTimeout 防止慢速攻击;2) 限制 MaxHeaderBytes 和请求体大小;3) 使用 CORS 中间件限制跨域;4) JWT/Session 认证;5) 速率限制防止暴力攻击;6) 输入验证和 SQL 参数化查询;7) TLS 加密通信。
📋 学习检查清单¶
- 能使用 net/http 构建基本 HTTP 服务器
- 理解 Handler 接口和中间件链模式
- 掌握 Go 1.22 的增强路由特性
- 能使用 Gin 框架构建 RESTful API
- 掌握请求绑定和参数验证
- 能实现认证/限流/日志等中间件
- 理解优雅关停的实现方式
- 了解标准项目结构和分层架构