You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gin框架下PATCH请求CORS预检失败问题求助

PATCH请求预检OPTIONS无CORS响应头导致跨域错误

问题场景

Golang使用gin-gonic/gin搭建后端,前端React搭配Axios,GET、POST请求跨域正常,但发送PATCH请求时触发预检OPTIONS请求,浏览器报错:

CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

预检OPTIONS请求响应头无任何CORS相关字段,仅返回200和空内容。

后端路由配置

package main

import (
    "book_renting/api"
    "log"
    "net/http"

    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-gonic/contrib/cors"
    "github.com/gin-gonic/gin"
    _ "github.com/lib/pq"
)

func main() {

    router := gin.Default()
    store := cookie.NewStore([]byte("your-secret-key"))
    store.Options(sessions.Options{MaxAge: 60 * 60 * 24})

    router.Use(cors.Default())
    router.Use(sessions.Sessions("sessions", store))

    router.Use(func(c *gin.Context) {
        host := c.Request.Header.Get("Origin")
        c.Writer.Header().Set("Access-Control-Allow-Origin", host)
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
        if c.Request.Method == "OPTIONS" {
            log.Println("Handling OPTIONS request")
            c.AbortWithStatus(http.StatusNoContent)
            return
        }
        log.Println("Executing CORS middleware")
        c.Next()
    })

    router.POST("/login", api.HandleLogin)
    router.GET("/logout", api.HandleLogout)
    router.POST("/register", api.HandleRegister)
    router.GET("/getCookie", api.GetCookieSession)

    router.GET("/books", api.GetBooksAPI)
    router.GET("/books/:id", api.BookByIdAPI)
    router.PATCH("/rent/:id", api.RentBookAPI)
    router.PATCH("/return/:id", api.ReturnBookAPI)
    router.Run("localhost:3000")
}

前端Axios代码

import axios from 'axios'

const url = 'http://localhost:3000'

export const loginUser = async (credentials) => await axios.post(`${url}/login`, credentials, {withCredentials: true})
export const logoutUser = async () => await axios.get(`${url}/logout`, {withCredentials: true})
export const registerUser = () => axios.post(`${url}/register`)
export const fetchBooks = () => axios.get(`${url}/books`, { withCredentials: true })
export const fetchBookByID = (book_id) => axios.get(`${url}/books/${book_id}`, { withCredentials: true })
export const rentBook = (book_id) => axios.patch(`${url}/rent/${book_id}`, { withCredentials: true })
export const returnBook = (book_id) => axios.patch(`${url}/return/${book_id}`, { withCredentials: true })

响应头对比

  • 正常GET请求响应头:
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Origin: http://localhost:3001
Content-Type: application/json; charset=utf-8
Date: Sat, 10 Jun 2023 22:12:11 GMT
Content-Length: 495
  • PATCH预检OPTIONS请求响应头:
HTTP/1.1 200 OK
Date: Sat, 10 Jun 2023 22:12:12 GMT
Content-Length: 0

补充的curl请求及响应

curl命令:

curl 'http://localhost:3000/return/2' \
  -X 'OPTIONS' \
  -H 'Accept: */*' \
  -H 'Accept-Language: en-US,en;q=0.9,pl-PL;q=0.8,pl;q=0.7' \
  -H 'Access-Control-Request-Headers: content-type' \
  -H 'Access-Control-Request-Method: PATCH' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Origin: http://localhost:3001' \
  -H 'Pragma: no-cache' \
  -H 'Referer: http://localhost:3001/' \
  -H 'Sec-Fetch-Dest: empty' \
  -H 'Sec-Fetch-Mode: cors' \
  -H 'Sec-Fetch-Site: same-site' \
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' \
  --compressed

响应:

HTTP/1.1 200 OK
Date: Sun, 11 Jun 2023 01:22:57 GMT
Content-Length: 0

问题原因及解决方案

核心原因

你同时使用了gin-contrib/cors默认中间件和自定义CORS中间件,且顺序错误:

  1. cors.Default()的默认配置不包含PATCH方法(默认允许的方法是GET, POST, PUT, DELETE, HEAD, OPTIONS),导致预检OPTIONS请求时不认可PATCH方法。
  2. cors.Default()会自动拦截并处理OPTIONS请求,直接返回响应,导致你的自定义CORS中间件根本没被执行(这就是你OPTIONS分支的日志没打印的原因)。

解决方案

方案1:使用gin-contrib/cors自定义配置(推荐)

移除自定义CORS中间件,替换cors.Default()为包含PATCH方法的自定义配置,确保CORS规则覆盖所有需要的场景:

import "time" // 需要额外导入time包

// ... 其他代码不变

// 替换原来的router.Use(cors.Default())
router.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"http://localhost:3001"}, // 明确指定前端域名,避免任意跨域
    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"},
    AllowHeaders:     []string{"Content-Type", "Authorization"},
    AllowCredentials: true,
    MaxAge:           12 * time.Hour, // 预检请求缓存时长,减少重复OPTIONS请求
}))

// 移除自定义的CORS中间件
// router.Use(func(c *gin.Context) { ... })

// ... 后续中间件和路由配置不变

方案2:仅使用自定义CORS中间件

移除gin-contrib/cors中间件,将自定义CORS中间件放在所有中间件的最前面,确保它能先处理OPTIONS请求:

func main() {
    router := gin.Default()
    store := cookie.NewStore([]byte("your-secret-key"))
    store.Options(sessions.Options{MaxAge: 60 * 60 * 24})

    // 自定义CORS中间件放在最前面
    router.Use(func(c *gin.Context) {
        host := c.Request.Header.Get("Origin")
        c.Writer.Header().Set("Access-Control-Allow-Origin", host)
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
        if c.Request.Method == "OPTIONS" {
            log.Println("Handling OPTIONS request")
            c.AbortWithStatus(http.StatusNoContent)
            return
        }
        log.Println("Executing CORS middleware")
        c.Next()
    })

    // 移除router.Use(cors.Default())
    router.Use(sessions.Sessions("sessions", store))

    // ... 后续路由配置不变
}

内容的提问来源于stack exchange,提问作者Vertislav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 09:50:02