go http服务默认不处理options请求,需显式注册路由或确保cors中间件覆盖所有路径,否则跨域预检失败导致405或无响应。

Go HTTP服务默认不处理OPTIONS请求
如果你用net/http或gin、echo等框架部署了API,前端发跨域请求时突然卡在预检(Preflight),浏览器控制台报405 Method Not Allowed或直接无响应——大概率是服务压根没注册OPTIONS路由,或者中间件没覆盖到它。
HTTP协议规定:当请求含Authorization头、自定义头(如X-Request-ID),或Content-Type不是application/x-www-form-urlencoded、multipart/form-data、text/plain三者之一时,浏览器会先发一个OPTIONS请求探路。这个请求不带body,只带Origin、Access-Control-Request-Method、Access-Control-Request-Headers等预检头。服务必须返回200且带上正确的CORS响应头,否则后续请求被拦截。
- 别指望
net/http的DefaultServeMux自动回OPTIONS;它只认GET/POST等显式注册的路由 -
gin的Cors()中间件默认会处理OPTIONS,但仅限于它能“看到”的路由——如果OPTIONS请求路径没被任何router.Any()或router.OPTIONS()捕获,中间件根本不会执行 - 手写中间件时,若用
next(c)放行后才写CORS头,对OPTIONS无效:它没后续handler,next直接返回空响应
手动注册OPTIONS路由最稳妥
与其依赖框架的CORS中间件自动兜底,不如明确把OPTIONS当作一个真实路由来注册。这样路径匹配、头设置、短路逻辑全由你控制,调试也直观。
以net/http为例:
立即学习“go语言免费学习笔记(深入)”;
http.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "OPTIONS" {
w.Header().Set("Access-Control-Allow-Origin", "https://myapp.com")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Request-ID")
w.Header().Set("Access-Control-Expose-Headers", "X-Total-Count")
w.WriteHeader(http.StatusOK)
return
}
// 其余GET/POST逻辑...
})
- 必须放在
if r.Method == "OPTIONS"分支里立即返回,不能漏掉return,否则后续代码可能panic(比如试图读r.Body) -
Access-Control-Allow-Origin不能设为*同时带Credentials(如cookie);若前端用了withCredentials: true,这里得填具体域名 -
Access-Control-Allow-Headers要和前端实际发的自定义头严格一致,大小写敏感;漏一个,预检就失败
用gorilla/handlers时别跳过OPTIONS路径匹配
很多人用handlers.CORS()包,以为加一行http.ListenAndServe(":8080", handlers.CORS(handlers.AllowedOrigins([]string{"*"}))(r))就万事大吉。其实它只是给响应加头,不帮你注册OPTIONS路由——如果请求路径没对应handler,http.NotFound照样返回404,CORS头白加。
- 确保你的
http.ServeMux或gorilla/mux.Router已为所有API路径注册了OPTIONS方法,哪怕只是空处理 - 用
gorilla/mux可统一处理:r.HandleFunc("/api/{path:.*}", optionsHandler).Methods("OPTIONS"),避免每个路径重复写 - 若用
handlers.AllowedOrigins传[]string{"*"},注意它生成的Access-Control-Allow-Origin: *会禁止Access-Control-Allow-Credentials: true,后者一设就触发浏览器报错
gin里Cors()中间件失效的典型场景
gin.Default().Use(cors.Default())看似省事,但以下情况它救不了你:
- 路由组没继承中间件:比如
v1 := r.Group("/v1")之后没调用v1.Use(cors.Default()),那/v1/users下的OPTIONS请求就进不到CORS中间件 - 用了
router.NoRoute()兜底,但没给它配CORS头——404响应不会带Access-Control-Allow-Origin,预检失败 - 动态路由参数导致路径不匹配:如
router.GET("/user/:id", handler),但前端发OPTIONS /user/123,而中间件只在router.Any()或显式router.OPTIONS()下才生效
更稳的做法是显式注册:router.OPTIONS("/user/:id", corsHandler),或者用router.Any("/user/:id", corsHandler, userHandler),把CORS逻辑提前到第一个handler。
复杂点在于,CORS配置不是写一次就完事——Origin要动态校验、Headers要按需暴露、Credentials开关得和前端同步。漏掉任一环,预检就静默失败,你得盯着浏览器Network面板里的OPTIONS请求状态码和响应头逐项比对。










