跨域中的 options 请求

跨域 CORS

但凡被浏览器识别为不同源,浏览器都会认为是跨域,默认是不允许的。

比如:试图在 http://127.0.0.1:4000 中,请求 http://127.0.0.1:3000 的资源会出现如下错误:

跨域错误

这也是前端 100% 在接口调试中会遇到的问题。

同源和跨域的判断规则

当前浏览器访问地址:http://domain/url

URL结果原因
http://domain/other同源地址不同
http://domain2跨域域名不同
http://domain:8080跨域端口不同
https://domain跨域协议不同

简单请求和复杂请求

相信都会在浏览器的 Network 中看到两个同样地址的请求,有没有想过这是为什么呢?这是因为在请求中,会分为 简单请求复杂请求

简单请求:满足如下条件的,将不会触发跨域检查:

  • 请求方法为:GETPOSTHEAD
  • 请求头:AcceptAccept-LanguageContent-LanguageContent-Type

其中 Content-Type 限定为 :text/plain、multipart/form-data、application/x-www-form-urlencoded

我们可以更改同源规则,看下如下示例:

http://127.0.0.1:4000/ 下,请求 http://127.0.0.1:3000 不同端口的地址

简单请求

域名不同,这已经跨域了。但由于请求方法为 GET,符合 简单请求,请求将正常工作。

复杂请求:不满足简单请求的都为复杂请求。在发送请求前,会使用 options 方法发起一个 预检请求(Preflight) 到服务器,以获知服务器是否允许该实际请求。

模拟一个跨域请求:

// 端口不同,content-type 也非限定值
axios.post(
  'http://127.0.0.1:3000/test/cors',
  {},
  {
    headers: {
      'content-type': 'application/json',
    },
  }
);

能看到在请求之前浏览器会事先发起一个 Preflight 预检请求

Preflight

这个 预检请求 的请求方法为 options,同时会包含 Access-Control-xxx 的请求头:

options请求信息

当然,此时服务端没有做跨域处理(示例使用 express 起的服务,预检请求默认响应 200),就会出现浏览器 CORS 的错误警告。

跨域错误

如何解决跨域

对于跨域,前端再熟悉不过,百度搜索能找到一堆解决方法,关键词不是 JSONP,或者添加些 Access-Control-XXX 响应头。

本篇将详细说下后一种方式,姑且称为:服务端解决方案。

为 options 添加响应头

express 举例,首先对 OPTIONS 方法的请求添加这些响应头,它将根据告诉浏览器根据这些属性进行跨域限制:

app.use(function (req, res, next) {
  if (req.method == 'OPTIONS') {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'content-type');
    res.status(200).end();
  }
});

如果你不对 预检接口 做正确的设置,那么后续一切都是徒劳。

打个比方:如果 Access-Control-Allow-Methods 只设置了 POST,如果客户端请求方法为 PUT,那么最终会出现跨域异常,并会指出 PUT 没有在预检请求中的 Access-Control-Allow-Methods 出现:

跨域方法错误

所以,以后读懂跨域异常对于正确的添加服务端响应信息非常重要。另外:GET、POST、HEAD 属于简单请求的方法,所以即使不在 Access-Control-Allow-Methods 定义也不碍事(如果不对请指出)

正式的跨域请求

随后对我们代码发出的请求额外添加跨域响应头(这需要和前面的预检接口一致)

if (req.method == 'OPTIONS') {
  //...
} else {
  // http://127.0.0.1:3000/test/cors
  res.setHeader('Access-Control-Allow-Origin', '*');
  next();
}

最后能看到我们等请求正常请求到了:

跨域请求

对于跨域请求头的说明

上例出现了我们经常见到的三个:Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers

参考 cors 库,另外还有其他用于预检请求的响应头:

头属性作用
Access-Control-Allow-Origin判断源地址(协议://域名:端口)
Access-Control-Allow-Methods限定方法(GET,HEAD,PUT,PATCH,POST,DELETE)
Access-Control-Allow-Headers限定请求头(content-type)
Access-Control-Max-Age预检请求的缓存时间(单位为秒,-1 不缓存)
Access-Control-Expose-Headers授权客户端能获取到的响应头
Access-Control-Request-Headers客户端生成的请求头
Access-Control-Allow-Credentials限定客户端可以携带敏感信息
Vary定义可变化的头,防止浏览器缓存

下面将对上面这些头做个说明。

Access-Control-Allow-Origin

预检请求正常请求 告知浏览器被允许的源。支持通配符“*”,但不支持以逗号“,”分割的多源填写方式。

如果尝试些多个域名,则会出现如下错误:

Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains multiple values 'aaa,bbb', but only one is allowed.

多源错误

另外,也不建议 Access-Control-Allow-Origin 以通配符方式定义,这样会增加安全隐患,最好以请求方的 origin 来赋值。

const origin = req.headers.origin;
res.setHeader('Access-Control-Allow-Origin', origin || '*');
// 因为会随着客户端请求的 Origin 变化,所以标识 Vary,让浏览器不要缓存
res.setHeader('Vary', 'Origin');

Access-Control-Allow-Methods

被允许的 Http 方法,按照需要填写,支持多个,例如: GET , HEAD , PUT , PATCH , POST , DELETE

由于判断 简单请求 之一的 HTTP 方法默认为 GETPOSTHEAD ,所以这些即使不在 Access-Control-Allow-Methods 约定,浏览器也是支持的。

比如:如果服务端定义 PUT 方法,而客户端发送的方法为 DELETE,则会出现如下错误:

res.setHeader('Access-Control-Allow-Methods', 'PUT');

Method DELETE is not allowed by Access-Control-Allow-Methods in preflight response.

方法错误

Access-Control-Allow-Headers

预检接口 告知客户端允许的请求头。

简单请求 约定的请求头默认支持: AcceptAccept-LanguageContent-LanguageContent-Typetext/plain、multipart/form-data、application/x-www-form-urlencoded

如果客户端的请求头不在定义范围内,则会报错:

Request header field abc is not allowed by Access-Control-Allow-Headers in preflight response.

请求头错误

需要将此头调整为:

res.setHeader('Access-Control-Allow-Headers', 'content-type, abc');

Access-Control-Max-Age

定义 预检接口 告知客户端允许的请求头可以缓存多久。

默认时间规则:

  • 在 Firefox 中,上限是 24 小时 (即 86400 秒)。
  • 在 Chromium v76 之前, 上限是 10 分钟(即 600 秒)。
  • 从 Chromium v76 开始,上限是 2 小时(即 7200 秒)。
  • Chromium 同时规定了一个默认值 5 秒。
  • 如果值为 -1,表示禁用缓存,则每次请求前都需要使用 OPTIONS 预检请求。

比如设置为 5 秒后,客户端在第一次会发送 预检接口 后,5 秒内将不再发送 预检接口

res.setHeader('Access-Control-Max-Age', '5');

Access-Control-Allow-Credentials

跨域的请求,默认浏览器不会将当前地址的 Cookies 信息传给服务器,以确保信息的安全性。如果有需要,服务端需要设置 Access-Control-Allow-Credentials 响应头,另外客户端也需要开启 withCredentials 配置。

// 客户端请求
axios.post(
  'http://127.0.0.1:3000/test/cors',
  {},
  {
    headers: {
      'content-type': 'application/json',
      abc: '123',
    },
    withCredentials: true,
  }
);
// 所有请求
res.setHeader('Access-Control-Allow-Credentials', 'true');

需要注意的是,Access-Control-Allow-Origin 不能设置通配符“*”方式,会出现如下错误:

不支持通配符

这个 Access-Control-Allow-Origin 必须是当前页面源的地址。

Access-Control-Expose-Headers

Access-Control-Allow-Credentials 类似,如果服务端有自定义设置的请求头,跨域的客户端请求在响应信息中是接收不到该请求头的。

axios
  .post(
    'http://127.0.0.1:3000/test/cors',
    {},
    {
      headers: {
        'content-type': 'application/json',
        abc: '123',
      },
      withCredentials: true,
    }
  )
  .then((data) => {
    console.log(data.headers.def); //undefined
  });

需要在服务端设置 Access-Control-Expose-Headers 响应头,并标记哪些头是客户端能获取到的:

res.setHeader('Access-Control-Expose-Headers', 'def');
res.setHeader('def', '123');

Access-Control-Request-Headers

我试了半天没找到 Access-Control-Request-Headers 的使用示例,其实它是根据当前请求的头拼接得到的。

如果客户端的请求头为:

{
  "content-type": "application/json",
  "abc": "123",
  "xyz": "123",
},

那么浏览器最后会在 预检接口 添加一个 Access-Control-Request-Headers 的头,其值为:abc,content-type,xyz。然后服务端再根据 Access-Control-Allow-Headers 告诉浏览器服务端的请求头支持说明,最后浏览器判断是否会有跨域错误。

另外,对于服务端也需要针对 Access-Control-Request-HeadersVary 处理:

res.setHeader('Vary', 'Origin' + ', ' + req.headers['access-control-request-headers']);

如此,对于跨域及其怎么处理头信息会有个基本的概念。希望在遇到类似问题能有章法的解决,而非胡乱尝试。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 当使用HTTPS进行跨域请求,浏览器会先发送一个OPTIONS请求,以确认服务器是否允许跨域请求。这个OPTIONS请求包含了一些请求头信息,例如Origin、Access-Control-Request-Method、Access-Control-Request-Headers等。服务器需要根据这些请求头信息,返回一个响应头信息,例如Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers等。只有当服务器返回的响应头信息包含了Access-Control-Allow-Origin,且值为请求的Origin,浏览器才会发送真正的跨域请求。 ### 回答2: 在进行跨域请求,浏览器会先发送一个OPTIONS请求,以确认对方服务器是否支持跨域请求,并确定哪些请求方式和头部信息可以被允许。这种请求被称为“预检请求(Preflight Request)”。 OPTIONS请求的HTTP方法是一种请求方式,它类似于GET、POST、PUT、DELETE等常见的HTTP请求方法,但它的目的不是获取资源,而是对服务器发起一个探测性请求,以获取服务器对跨域请求的支持情况,从而安全地执行跨域请求OPTIONS请求一般包含如下信息: 1.请求方式:OPTIONS 2.请求头部信息:包括请求的URL、协议版本、请求的Host、Origin(指请求方的域名或IP地址)、Access-Control-Request-Method、Access-Control-Request-Headers(指发送请求携带的自定义头部信息),这些数据将告诉对方服务器,请求方的支持是否满足对方服务器的要求。 3.响应头部信息:对方服务器从请求头部信息获取到了想要的信息之后,会回复一个响应头部信息,包括允许 CORS 的 Origin、支持的HTTP请求方法、支持的头部信息等等。 在进行跨域请求,浏览器会先发送一个OPTIONS请求,以确认对方服务器是否支持跨域请求,并确定哪些请求方式和头部信息可以被允许。这种请求被称为“预检请求(Preflight Request)”。 对于接收到OPTIONS请求的服务器,如果它允许跨域请求,就会在响应头加入Access-Control-Allow-Origin、Access-Control-Allow-Methods等字段来告诉浏览器可以进行跨域请求。如果不允许跨域请求,则响应头不会包含这些字段。 ### 回答3: 在进行跨域请求,浏览器会先发送一个options请求来询问服务器是否允许跨域请求,并携带自身的请求方法、请求头、请求域等信息。这样可以避免在真正发送请求发生意外的错误。 Options请求通过http请求的"Access-Control-Request-Method"和"Access-Control-Request-Headers"字段,告知服务器客户端想要采用的请求方式和请求头。如果服务器允许客户端使用这些方式,它会在返回的响应头添加"Access-Control-Allow-Methods"和"Access-Control-Allow-Headers"字段,指明允许的请求方式和请求头,同也可以指定允许跨域请求的域名。 此外,还可以通过"Access-Control-Max-Age"字段指定响应信息在客户端浏览器本地缓存的间,减少重复的Options请求。 如果服务器不允许客户端进行跨域请求,响应头会包含"Access-Control-Allow-Origin"字段并设置为"*",表示拒绝所有跨域请求。这,浏览器会收到一个403 Forbidden错误。 需要注意的是,在进行跨域请求请求前端也需要在请求添加Origin字段,告知服务器此请求的源地址。这样服务器才能知道这个请求是否来自允许跨域请求的源,并进行相应的处理。如果没有这个字段,服务器会认为这是两个不同的域名之间的普通请求,导致跨域请求失败。 因此,了解Options请求的原理和具体操作可以帮助我们在进行跨域请求更加顺利地处理跨域问题,提高开发效率和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值