外观
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、耗时 |
| Recovery | recover 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: trueOPTIONS 预检请求需单独处理。
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))
}示例中
validateCreateDevice需import "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/http | Express |
|---|---|---|
| 中间件 | func(http.Handler) http.Handler | (req, res, next) => {} |
| 链式 | 手动 nest 或 helper | app.use() 顺序注册 |
| CORS | 手动设 Header 或 rs/cors 库 | cors npm 包 |
| 校验 | 手动 / validator | express-validator, zod |
| panic | recovery middleware | 错误 middleware |
关键差异:
- Go 中间件是包装 Handler,不是独立函数链
- 前端 axios 的
withCredentials需要Allow-Credentials: true且 Origin 不能是* - 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. 学习检查
- 中间件洋葱模型的执行顺序是什么?
- 什么是 CORS 预检请求?如何处理?
Allow-Origin: *和Allow-Credentials: true能同时使用吗?- 校验错误应返回什么状态码?
- recovery middleware 中 recover 应放在哪里?
- 如何让 Vue dev server 成功调用 Go API?
12. 下一步
| 已完成 | 下一知识点 | 关系 |
|---|---|---|
| Middleware, Validation, CORS | Gin, JWT, API design | 框架简化开发 + 认证 |
标准库 middleware 理解后,学 Gin 和 JWT 完成 Project 01 基础 API。