关于预检请求

跨域资源共享-CORS

在本地电脑双击打开html文件,浏览器地址栏上的url通常是这样的:file:///D:/xxx/index.html

以 file:///文件路径 这种打开文件的方式,叫做本地文件协议 ,它是浏览器用来访问并打开位于用户计算机(本地磁盘)上的文件的协议。

通过 本地文件协议 打开的页面,其 同源策略 会将每个文件甚至每个目录都视为一个独立的、互不信任的"源"。这导致向任何 HTTP 地址发送请求都会被视为 跨源请求 ,而浏览器默认会禁止这种请求,阻止一个"源"的文档或脚本与另一个"源"的资源进行交互,除非对方明确允许。

总而言之,服务器端几乎无法为 file: 协议来源的请求正确配置 CORS

当你尝试在 file: 协议页面中使用 fetchXMLHttpRequest 发送 HTTP 请求时,浏览器控制台会抛出类似以下的错误:

  • Chrome/Edge: Access to fetch at 'http://example.com/' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
  • Firefox: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://example.com/. (Reason: CORS header 'Access-Control-Allow-Origin' missing).

这个错误明确指出了请求是从源 'null'(即 file: 协议)发起的,并且被 CORS 策略阻止了。

所以,我们需要在本地起一个服务,去做跨域的测试。

做跨域测试前,需要了解以下的内容:

预检请求

什么情况下会触发预检请求?

预检请求的成功或者失败是服务端的配置决定的吗?分别是什么配置?

预检请求的作用,是浏览器问服务端,这个请求是否允许访问,如果服务端允许,则预检请求发送成功,然后浏览器才会正式发送第二次请求,对于复杂请求来说。如果预检请求失败了,说明服务端禁止这个请求

如果前端和后端都部署在同一台服务器上,也就是说前后端的地址的域名、端口完全一样,这个时候是否会发生预检请求? 预检请求的触发条件与请求的"源" 无关,而是与请求本身的特性有关。无论是否同源,只要你的请求满足以下任一条件,浏览器就会先发送预检请求:

  1. 使用了非简单方法 (Non-simple Methods):

    • 简单方法GET, HEAD, POST
    • 非简单方法PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH,以及任何自定义方法(如 UPDATE)。
  2. 设置了非简单首部 (Non-simple Headers):

    • 简单首部Accept, Accept-Language, Content-Language, Content-Type (值有限制,见下一条), DPR, Downlink, Save-Data, Viewport-Width, Width
    • 非简单首部 :任何自定义首部(如 X-Requested-With, Authorization(在某些情况下)),以及 Content-Type 的值不属于简单值。
  3. Content-Type 的值不是以下三种之一:

js 复制代码
    -   `application/x-www-form-urlencoded`
    -   `multipart/form-data`
    -   `text/plain`

如果你使用 application/jsontext/xml一定会触发预检

"跨域问题"本质上是浏览器和服务器之间的权限协商问题,而不是前端代码本身的问题。 解决跨域问题的关键永远在于后端服务器的正确配置

在正常情况下,简单请求绝对不会触发预检请求 。而是会直接发送 这个真正的请求(如 GET, POST)到服务器。

测试代码

js 复制代码
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());

app.all('*', (req, res, next) => {
    res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:5500');
    res.header('Access-Control-Allow-Headers', 'Authorization,Content-Type');
    res.header('Access-Control-Allow-Methods', 'GET,POST,PUT');
    next();
})
app.put('/preflight-test', (req, res) => {
    res.json({
        msg: 'preflight-test'
    })
})

app.listen(8080, () => {
    console.log('Server is running on PORT 8080.');
})

复杂请求测试

以下是用vscode插件Live Server启动源为http://127.0.0.1:5500的本地服务器打开的html测试文件

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        fetch('http://localhost:8080/preflight-test', {
            method: 'put'
        });
    </script>
</body>

</html>

可以看到,有两个一模一样的请求。但一个是OPTIONS请求,一个是PUT请求。

修改请求头

js 复制代码
fetch('http://localhost:8080/preflight-test', {
    method: 'PUT',
    headers: {
        'X-Custom-Header': 'test-value'
    }
});

可以发现,虽然源匹配上了,但是还是发生了cors的报错。请求头不对也会导致跨域失败问题。 即使配置了允许源(Origin),但如果请求头(Headers)不匹配,同样会导致跨域预检请求失败。 Origin 只是跨域访问的"第一道关卡"。通过了它,只意味着浏览器愿意把你的请求"派送"到服务器。而请求的"具体内容"(方法、头信息)是否被接受,还需要由服务器返回的 Allow-MethodsAllow-Headers 来决定。

CORS失败 ≠ 连接失败 。它特指浏览器在收到了服务器响应 后,根据响应头中的CORS规则进行校验,发现权限不足,从而主动阻止前端JavaScript代码访问响应结果。

因此,OriginMethodsHeaders 三者是且(AND) 的关系,必须全部满足,请求才能成功。

相关推荐
复苏季风2 小时前
Vue3 小白的疑惑:为什么用 const 定义的变量还能改?
前端·javascript·vue.js
Mintopia2 小时前
在 Next.js 中接入 Google Analytics 与 PostHog —— 一场“数据偷窥”的艺术演出
前端·javascript·next.js
遂心_2 小时前
React useState:20分钟彻底掌握这个让你"状态满满"的Hook
前端·javascript·react.js
月亮慢慢圆2 小时前
Web Animation API
前端
Mintopia2 小时前
AIGC驱动的Web界面设计:技术逻辑与用户体验平衡
前端·javascript·aigc
盏茶作酒292 小时前
浅拷贝和深拷贝
前端·javascript
在掘金801102 小时前
pm2 程序 windows开机启动管理设置
前端
徐_三岁2 小时前
深入理解 svh、lvh、dvh—— 移动端视口高度解决方案
前端·css
昔人'2 小时前
css`min()` 、`max()`、 `clamp()`
前端·css