Skip to content

Middleware, Validation, CORS

Phase 03 — Go Web 涵盖:Middleware · Validation · CORS


1. 学习目标

完成本知识点后,你应该能够:

  • 实现 net/http 中间件链(logging、recovery、auth)
  • 理解中间件的洋葱模型和执行顺序
  • 对请求参数和 JSON body 进行校验
  • 配置 CORS 响应头,允许 Vue 开发服务器跨域访问
  • 返回一致的错误响应格式
  • 构建可被 Vue + Vite 前端调用的 API 服务

2. 为什么需要

开发时 Vue 跑在 localhost:5173,Go API 在 localhost:8080——不同源,浏览器会拦截跨域请求,除非服务端返回正确的 CORS 头。作为前端开发者你熟悉这个问题,现在要在 Go 侧解决。

中间件把横切关注点(日志、认证、CORS、panic recovery)从业务 handler 中剥离。Validation 保证进入业务逻辑的数据合法,减少 bug 和安全隐患。


3. 核心概念

3.1 中间件模型

go
func Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // before
        next.ServeHTTP(w, r)
        // after
    })
}

多个中间件嵌套:Logging(CORS(Recovery(mux)))

3.2 常见中间件

中间件作用
Logging记录 Method、Path、耗时
Recoveryrecover panic → 500
CORS设置 Access-Control-* 头
Auth校验 JWT / API Key
RequestID注入追踪 ID

3.3 Validation

Go 标准库无内置 validation 框架。常用方式:

  • 手动校验(简单项目)
  • github.com/go-playground/validator(Gin 默认集成)

校验内容:必填、长度、范围、格式(email、uuid)。

3.4 CORS 关键头

Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true

OPTIONS 预检请求需单独处理。


4. 基础语法

完整示例见 workspace/phase-03/middleware-validation-cors/main.go

go
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strings"
    "time"
)

func cors(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
        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 == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func logging(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 recovery(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Println("panic:", err)
                w.WriteHeader(http.StatusInternalServerError)
                _ = json.NewEncoder(w).Encode(map[string]string{"message": "internal error"})
            }
        }()
        next.ServeHTTP(w, r)
    })
}

type CreateDeviceReq struct {
    ID   string `json:"id"`
    Name string `json:"name"`
    Type string `json:"type"`
}

func validateCreateDevice(req CreateDeviceReq) error {
    if strings.TrimSpace(req.ID) == "" {
        return fmt.Errorf("id is required")
    }
    if len(req.Name) < 2 || len(req.Name) > 50 {
        return fmt.Errorf("name length must be 2-50")
    }
    allowed := map[string]bool{"agv": true, "sensor": true}
    if !allowed[req.Type] {
        return fmt.Errorf("type must be agv or sensor")
    }
    return nil
}

func chain(middlewares ...func(http.Handler) http.Handler) func(http.Handler) http.Handler {
    return func(final http.Handler) http.Handler {
        for i := len(middlewares) - 1; i >= 0; i-- {
            final = middlewares[i](final)
        }
        return final
    }
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("POST /api/devices", createDeviceHandler)

    handler := chain(cors, logging, recovery)(mux)
    log.Fatal(http.ListenAndServe(":8080", handler))
}

示例中 validateCreateDeviceimport "fmt"


5. 代码解析

go
if r.Method == http.MethodOptions {
    w.WriteHeader(http.StatusNoContent)
    return
}

浏览器跨域 POST 带自定义 Header 时会先发 OPTIONS 预检,必须返回 CORS 头并 204。

go
chain(cors, logging, recovery)(mux)

中间件执行顺序:cors → logging → recovery → mux。响应路径相反(洋葱模型)。

go
func validateCreateDevice(req CreateDeviceReq) error { ... }

校验逻辑独立于 handler,可单元测试。失败返回 400 + 错误信息。


6. JavaScript / TypeScript 对比

概念Go net/httpExpress
中间件func(http.Handler) http.Handler(req, res, next) => {}
链式手动 nest 或 helperapp.use() 顺序注册
CORS手动设 Header 或 rs/cors 库cors npm 包
校验手动 / validatorexpress-validator, zod
panicrecovery middleware错误 middleware

关键差异

  1. Go 中间件是包装 Handler,不是独立函数链
  2. 前端 axios 的 withCredentials 需要 Allow-Credentials: true 且 Origin 不能是 *
  3. Validation 错误应返回结构化 JSON,方便 Vue 表单展示

7. 常见错误

错误 1:CORS 只加在 GET handler

CORS 应在最外层 middleware 统一处理,包括 OPTIONS。

错误 2:Allow-Origin: *

同时设置 Allow-Credentials: true 会被浏览器拒绝。

错误 3:校验失败返回 500

go
// ❌ 参数错误是客户端问题
// ✅ http.StatusBadRequest (400)

错误 4:中间件忘记 call next

go
func bad(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("before")
        // 忘记 next.ServeHTTP(w, r) → 请求挂起
    })
}

错误 5:ResponseWriter 被多次 WriteHeader

recovery 和 handler 都 WriteHeader 会 log 警告。可用 wrapper 记录 status。


8. 实际应用

Vue 开发环境 CORS

go
// 开发环境允许 Vite 默认端口
allowedOrigins := []string{
    "http://localhost:5173",
    "http://127.0.0.1:5173",
}

生产环境改为实际域名。

数字孪生 — 创建设备校验

go
type CreateDeviceReq struct {
    ID   string  `json:"id" validate:"required,min=3,max=20"`
    Type string  `json:"type" validate:"required,oneof=agv sensor camera"`
    X    float64 `json:"x" validate:"gte=0,lte=1000"`
    Y    float64 `json:"y" validate:"gte=0,lte=1000"`
}

校验失败:

json
HTTP 400
{"code": 400, "message": "validation failed", "errors": [{"field":"type","msg":"must be agv, sensor or camera"}]}

9. 深入理解

9.1 ResponseWriter wrapper

记录 status code 和 bytes written,用于 logging middleware:

go
type responseWriter struct {
    http.ResponseWriter
    status int
}
func (rw *responseWriter) WriteHeader(code int) {
    rw.status = code
    rw.ResponseWriter.WriteHeader(code)
}

9.2 上下文传值

middleware 可注入 request ID:

go
ctx := context.WithValue(r.Context(), requestIDKey, id)
r = r.WithContext(ctx)

9.3 与 Gin 中间件对比

Gin 的 c.Next() 类似 call next,但 API 更简洁。原理相同。


10. 练习

详细题目见 exercises/phase-03-web/03-middleware-validation-cors.md

Level 1 — 基础

练习 1.1:实现 logging middleware,打印 Method 和 Path。

练习 1.2:实现 CORS middleware,处理 OPTIONS。

练习 1.3:用 curl 和浏览器验证跨域请求。

Level 2 — 应用

练习 2.1:实现 recovery middleware,handler 内 panic 返回 500 JSON。

练习 2.2:validateCreateDevice,三种错误场景返回 400。

练习 2.3:chain 函数组合三个 middleware。

Level 3 — 综合

练习 3.1:统一 ErrorResponse struct,400/404/500 格式一致。

练习 3.2:Vue 前端 axios 调用 POST /api/devices,确认 CORS 正常。

Level 4 — 项目实践

练习 4.1:在 workspace/phase-03/middleware-validation-cors/ 完成带 middleware + validation + CORS 的 Device API。


11. 学习检查

  1. 中间件洋葱模型的执行顺序是什么?
  2. 什么是 CORS 预检请求?如何处理?
  3. Allow-Origin: *Allow-Credentials: true 能同时使用吗?
  4. 校验错误应返回什么状态码?
  5. recovery middleware 中 recover 应放在哪里?
  6. 如何让 Vue dev server 成功调用 Go API?

12. 下一步

已完成下一知识点关系
Middleware, Validation, CORSGin, JWT, API design框架简化开发 + 认证

标准库 middleware 理解后,学 Gin 和 JWT 完成 Project 01 基础 API。


学习导航

上一篇:net/http、Handler、Router · 对应练习 · 下一篇:Gin、JWT、API Design