跳转至

📖 Web 开发

学习时间: 约 5-6 小时 | 难度: ⭐⭐⭐ 中级 | 前置知识: Go基础语法、接口、并发编程

📚 章节概述

Go 凭借出色的并发能力和内置的 net/http 库,成为构建高性能 Web 服务的热门选择。本章将从标准库 HTTP 服务器出发,到 Gin 框架的生产级实践,覆盖路由、中间件、请求处理、JSON API、文件上传、WebSocket、优雅关停等核心主题。

Go Web请求处理链路

上图展示了从客户端到路由、中间件、处理器再到 JSON 响应的核心请求流程。

🎯 学习目标

  • 精通 net/http 标准库的 Handler 模式
  • 掌握 Gin 框架的路由、中间件、绑定
  • 理解 RESTful API 设计原则
  • 学会实现认证/授权中间件
  • 掌握请求验证和错误处理
  • 了解优雅关停和生产部署

📌 概念:net/http 标准库

1.1 基础 HTTP 服务器

Go
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

Go
// 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
// 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 中间件模式

Go
// 中间件本质是 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 基础

Go
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 请求绑定与验证

Go
// 创建用户请求体 —— 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

Go
// 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 中间件实战

Go
// 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
}

📌 概念:优雅关停

Go
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 项目结构

Text Only
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
Go
// 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 传递请求级数据

Go
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. 统一的错误响应格式

Go
// ✅ 统一格式
{"code": 400, "message": "参数验证失败", "details": [...]}

// ❌ 不统一
{"error": "bad request"}  // 有时这样
{"msg": "fail"}           // 有时那样

4. 生产环境使用 TLS

Go
srv.ListenAndServeTLS("cert.pem", "key.pem")

5. 合理配置超时

Go
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
  • 掌握请求绑定和参数验证
  • 能实现认证/限流/日志等中间件
  • 理解优雅关停的实现方式
  • 了解标准项目结构和分层架构

上一章: 反射与泛型 | 下一章: 实战项目