Skip to content

CORS

OPTIONS 缓存机制

浏览器对于 OPTIONS 预检请求是有缓存机制的,以减少不必要的网络开销。这种缓存是通过 Access-Control-Max-Age 响应头来控制的。

  • Access-Control-Max-Age:

    • 这个响应头是由服务器在响应 OPTIONS 预检请求时设置的。
    • 它指定了浏览器可以缓存 OPTIONS 预检请求结果的时间 (以秒为单位)。
    • Access-Control-Max-Age: 3600 表示浏览器可以将 OPTIONS 预检请求的结果缓存 1 小时。
  • 缓存原理:

    • 当浏览器发送 OPTIONS 预检请求并收到响应后,会检查响应头中是否包含 Access-Control-Max-Age
    • 如果有 Access-Control-Max-Age,浏览器会将该响应及其 CORS 相关的头部信息(如 Access-Control-Allow-Origin、Access-Control-Allow-Methods、 Access-Control-Allow-Headers 等)缓存起来。
    • 在缓存有效期内,如果同一源向同一目标 URL 发送具有相同头部和方法的跨域请求,浏览器会直接从缓存中读取 CORS 信息,而不会再发送 OPTIONS 预检请求。
    • 当缓存过期后,浏览器会再次发送 OPTIONS 预检请求。
  • 不使用缓存的情况:

    • 如果服务器没有设置 Access-Control-Max-Age,或者设置为 0,浏览器将不会缓存 OPTIONS 请求的结果,每次跨域请求都会发送 OPTIONS 预检请求。
    • 如果浏览器缓存过期,即使是相同的请求,也会发送新的 OPTIONS 预检请求。
    • 如果跨域请求的头部或者方法发生了变化,浏览器会重新发起 OPTIONS 预检请求。

即使 OPTIONS 预检请求失败,浏览器仍然可能发出真实的请求,这通常是浏览器的一种默认行为,浏览器尝试发送请求,然后再根据CORS配置进行拦截或者放行。这个真实请求往往会显示为失败,是因为浏览器没有从服务端拿到合法的 CORS 响应头,例如: Access-Control-Allow-Origin, 从而拦截了这个响应,并阻止浏览器显示请求成功。

是否每次跨域都需要先发送 OPTIONS 请求?
答案是不一定。这取决于以下因素:
  • 是否符合简单请求:

    • 如果跨域请求是 "简单请求" (即使用 GET、HEAD 或 POST 方法,且 Content-Type 为 application/x-www-form-urlencoded、 multipart/form-data 或 text/plain),则不会发送预检请求。
    • 如果添加了任何自定义头部,或者 Content-Type 不是上面三种简单的情况,则需要发送预检请求。
  • 缓存:

    • 如果浏览器已经缓存了当前请求的 OPTIONS 结果,并且缓存未过期,则不会再次发送 OPTIONS 请求。
    • 缓存是否存在取决于服务器响应头中的 Access-Control-Max-Age, 浏览器会按照此值缓存。
  • 请求头和方法变化:

    • 如果跨域请求的头部信息或者方法发生了变化(例如,添加了新的自定义请求头,或者使用了不同的方法),则浏览器会认为这是一个新的请求,需要重新发送 OPTIONS 预检请求。

      • OPTIONS 请求是跨域请求的一部分,但不是每次跨域都需要发送。
      • 浏览器会缓存 OPTIONS 响应,缓存时间由服务器的 Access-Control-Max-Age 响应头控制。
      • 了解缓存机制和合理配置 CORS 策略,可以优化你的跨域请求。
简单请求也可能跨域
  • 简单请求只是不发送 OPTIONS 预检请求: 这意味着浏览器直接发送你的 GET, HEAD 或者符合条件的 POST 请求到服务器,而不会先发送 OPTIONS 请求去询问是否可以进行跨域请求
  • 同源策略仍然生效: 即使是简单请求,同源策略仍然存在。浏览器接收到服务器响应之后,仍然会检查服务器返回的 Access-Control-Allow-Origin 响应头,看是否允许当前的跨域请求。 如果服务器没有设置 Access-Control-Allow-Origin 头,或者设置的值不允许当前源访问,浏览器依然会阻止响应,从而产生跨域错误。
Express cors
js
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*')
    res.header(
        'Access-Control-Allow-Headers',
        'Authorization,X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method,x'
    )
    res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PATCH, PUT, DELETE')
    res.header('Allow', 'GET, POST, PATCH, OPTIONS, PUT, DELETE')
    res.header('Access-Control-Max-Age', '3600')
    if (req.method === 'OPTIONS') {
        res.sendStatus(200)
    } else {
        next()
    }
})