【web】http请求中的 OPTIONS 详解 & 跨域

1. 导读

有过跨域请求的同学们应该发现过一个http请求有时会请求2次的时候,今天就给大家说说这个http请求的OPTIONS 方法是如何产生以及作用是啥。

2. 解释

互联网上的各个节点之间本来都是连通的,但是有些节点,比如我们的个人电脑连接另外一些节点(比如服务器)的时候,总是通过浏览器。这样,浏览器作为一个中间人,就有机会管理一些连接,就好像高速路上的收费站检查进出的车辆。

这个类比还有一个可以借鉴的地方:就像收费站会根据一些章程文件来检查进出车辆,浏览器也根据一些技术约定来管理进出的连接。这些技术约定由 W3C,WHATWG 等组织制定。

我们今天要谈的跨域请求,Cross Origin Resource Sharing ( CORS ),就是浏览器执行的一种技术约定,一种 Protocol。

CORS(Cross-Origin Resource Sharing)跨域资源共享,这个协议 定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。

注意:CORS是仅指http请求涉及的跨域问题,跨域一般包含2个场景:

  • http请求(向另一个ip发送get等请求)
  • 页面(例如通过iframe加载另一个ip的页面或通过src引用另一个ip的css、js等文件)2种场景!

那么具体来说,到底什么是“跨域请求”呢?

首先,“域”(Origin)是指浏览器地址栏中显示的主机地址。“跨域”,就是网页程序想要请求的地址和地址栏中的“域”不同。合起来就是,一个网页程序想要去别人家的地址[1]索要资源。

这个概念基本上只在涉及浏览器的网页应用中会出现。在浏览器之外,“域”的概念(浏览器地址栏中显示的主机地址)都没有了,更谈不上跨域了。但这个概念也是重要的,因为目前主流的浏览器,不论是电脑上的,还是手机上的,都支持这个约定;也就是说,不论是哪家的浏览器,只要在他们的地盘活动,这个约定是必须要考虑的。

只要协议、域名、端口有任何一个不同,都被当作是不同的域。

如果违反这个 Protocol 中的约定,会有什么后果么?

有一些 request 会收不到 response,因为 response 被浏览器拦截了,内容不告诉你
另外一些请求会根本发不出去,因为浏览器不允许发出那样的 request。

接下来,我们就来讨论,哪些情况会导致收不到 response,哪些情况会导致 request 失败。

2.1 览器将CORS请求分为两类:简单请求和非简单请求

2.1.1 Simple Requests简单请求

满足以下条件属于简单请求:

  • 请求方式只能是GET、POST、HEAD
  • HTTP请求头限制这几种字段(Accept、Accept-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width)
  • Content-Type:只能取application/x-www-form-urlencoded、multipart/form-data、text/plain,只能是特定的3个
  • 请求中的任意XMLHttpRequestUpload对象均没有注册任何事件监听器;XMLHttpRequestUpload对象可以使用
  • 请求中没有使用ReadableStream对象

一旦一个 request 是 simple request,那么,尽管这个请求是跨域的,它也会被浏览器直接放行。但是,在 response 返回的时候,浏览器并不会把 response 直接交给你,而是去检查这个 response 的 headers:

  • 有没有 Access-Control-Allow-Origin
  • 这个 header 的 value 包含 request 发出的地址(也就是“域”)。

如果两个条件都满足, response 会被返回给发出请求的程序;如果没有这个 header 或者 value 不对, response 就会被拦截下来,因为在浏览器看来,这个 response 不属于你(因为服务器没有明确允许你这个“域”来请求它)。如果你使用的是 chrome 浏览器,在 response 被拦截下来的时候,console 中会显示一个类似于下面的错误信息:

repeat this in your console

尽管发出request的程序无法得到 response,但是这个请求实际上是被发出了的,而且服务器也会完整的处理这个request。 可以想见,如果被请求的服务器支持被跨域请求,那么它一定会想办法在 response 中加上Access-Controll-Allow-Origin这个 header,并且附上合适的值。

什么是合适的值呢?
在 Request 的headers中会有一个 Origin,只要Access-Controll-Allow-Origin包含这个 Origin 就可以了(如果是 wildcard *,那么就等于包含所有的 Origin)。

2.1.1.1 演示简单请求被拒绝

我们通过nodejs,本地启动2个web服务,利用express插件提供静态文件,模拟类似完整的web服务。

步骤1 :编写代码

新建2个启动js脚本,分别命令为express_8881.js和express_8883.js:
在这里插入图片描述
public放置静态页面:
在这里插入图片描述

express_8881.js:

var express = require('express');
var app = express();
//提供静态文件服务
app.use('/public', express.static('public'));

//通过路由,提供一个get的查询服务
app.get('/', function (req, res) {
   // res.set("Access-Control-Allow-Origin","*");
   res.send('Hello World 8881');
})


 //监听8881,并打印可访问地址
var server = app.listen(8881, function () {
 
  var host = server.address().address
  var port = server.address().port
   //显示的有可能是ipv6的格式
  console.log("应用实例,访问地址为 http://%s:%s", host, port)
 
})

express_8883.js,除了端口配置,与前者几乎一样:

var express = require('express');
 var app = express();

//提供静态文件服务
app.use('/public', express.static('public'));
//通过路由,提供一个get的查询服务 
app.get('/', function (req, res) {   
   res.send('Hello World 8883');
})
 
 //监听8883,并打印可访问地址
var server = app.listen(8883, function () {
 
  var host = server.address().address
  var port = server.address().port
 
  //显示的有可能是ipv6的格式
  console.log("应用实例,访问地址为 http://%s:%s", host, port)
 
})

index8881.html,仅提供文本显示:

<html>
    <body>
            Wellecome to 8881 !
    </body>
</html>

index8883.html,稍复杂,里面有个按钮,会触发向8881发起一个跨域的请求:

<html>

    <script>
        function send (){
        var httpRequest = new XMLHttpRequest();//第一步:建立所需的对象
                httpRequest.open('GET', 'http://127.0.0.1:8881/', true);//第二步:设置url               
                
                // 获取数据后的处理程序                
                httpRequest.onreadystatechange = function () {     };
                
                 httpRequest.send();//第三步:发送请求 
        }

    </script>

    <body>
    Wellecome to 8083 !
<br>
        <button onclick="send()">send request to 8881</button>

    </body>
</html>

步骤2 :启动web 8881服务:

D:\test\nodejs-workspace>node express_8881.js
应用实例,访问地址为 http://:::8881

显示的是ipv6的地址,实际是127.0.0.1
在这里插入图片描述

访问http://127.0.0.1:8881/ ,证明直接访问时(非跨域)可以提供一个正常的http请求:
在这里插入图片描述

步骤3 :启动web 8883服务:

D:\test\nodejs-workspace>node express_8883.js
应用实例,访问地址为 http://:::8883

在这里插入图片描述
访问http://127.0.0.1:8883/public/index8883.html,我们访问的是一个页面,该页面提供一个按钮,会触发跨域请求
在这里插入图片描述

步骤4 :触发跨域请求:

点击http://127.0.0.1:8883/public/index8883.html页面的 send request to 8881按钮
分析:
在这里插入图片描述
在这里插入图片描述

  • 没有Access-Control-Allow-Origin项,说明是不支持跨域的
  • origin是request headers中的信息,服务端正是根据这个信息拒绝请求的

console打印:

Access to XMLHttpRequest at 'http://127.0.0.1:8881/' from origin 'http://127.0.0.1:8883' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
index8883.html:7 GET http://127.0.0.1:8881/ net::ERR_FAILED

在这里插入图片描述

2.1.2 Preflighted Requests 复杂请求

进行跨域的请求只会被分为两类,第一类就是 Simple Requests,其他的都是 Preflighted Requests。所以,只要一个请求,不满足上面一小节中对 simple requests 的要求,就是 preflighted requests 了。也就是说,一个请求只要满足下面几个条件中的任意一个就可以被划归此类了:

看完了 preflighted requests 的入选条件,我们再来从字面和行为上理解一下这种跨域请求。preflight 的中文意思是起飞前的,而 preflighted 的意译可能是:被在起飞前搞了一番的(有点蹩脚,哈哈)。这种跨域请求的实际行为也确实包含 preflight (起飞前)的部分:在一个请求被发出之前,浏览器会先发一个 OPTIONS 请求到目标“域”的服务器上,这个提前发出的请求,被称为 preflight request。

一般的,使用过程中,在跨域的情况下,设置的Content-Type为application/json,所以出现了非简单请求,需要后台配合将options预请求过滤处理即可。

讲完了 preflight request 这个最重要的概念,我们可以比较方便的梳理这种跨域请求的流程:

  • 浏览器发送 preflight request(那个 OPTIONS 请求[2])
  • 浏览器收到 preflight response(也就是刚刚那个 request 的返回)
  • 浏览器根据 preflight response 中的 Access-Control-Allow-Origin, Access-Control-Allow-Headers以及其他Access-Control-*类的headers 中的 value 来判断网页程序真正要发出的 request 是否符合要求
  • 如果这个 request 符合要求,request 被发出,网页程序可以收到正常的 response(如果不出网络通讯上的意外);如果这个 request 被判定为不符合要求,这个 request 干脆就不会被发出。
2.1.2.1 options请求未通过例子

步骤1 :修改代码,构造复杂请求:

下面我们基于 “2.1.1.1 演示简单请求被拒绝”来改造,只要添加一行代码,把简单请求改造为复杂请求即可:

httpRequest.setRequestHeader('Content-Type','text/html')

由简单请求的定义得知 Content-Type:只能取application/x-www-form-urlencoded、multipart/form-data、text/plain,只能是特定的3个,这里我们是’text/html’,不在特定的3个内,因此是复杂请求。
不过需要注意的是,一般get请求不需要设置Content-Type,只有post才有必要设置!我们这里为了简单演示,举了一个不标准的例子。get和post请求具体可以参见《js发送get 、post请求的方法简介》

在index8883.html,增加上述代码:

 <script>
        function send (){
        var httpRequest = new XMLHttpRequest();
                httpRequest.open('GET', 'http://127.0.0.1:8881/', true);               
                httpRequest.setRequestHeader('Content-Type','text/html')  //修改默认的Content-Type,变为复杂代码
                          
                httpRequest.onreadystatechange = function () {     };
                
                
                httpRequest.send();
        }

    </script>

都不用重启服务,刷新http://127.0.0.1:8883/public/index8883.html页面即可。
步骤2 :触发跨域请求:
在这里插入图片描述
从上图能看到实际有2条请求,其中一条正是options请求:

  • options请求,请求本身是成功的
    虽然请求自身是成功的,但服务端的返回信息,没有Access-Control-Allow-Origin,说明是不支持跨域的,这也是目标请求失败的原因。
  • 目标请求显示失败,其实压根没发送

console打印:

Access to XMLHttpRequest at 'http://127.0.0.1:8881/' from origin 'http://127.0.0.1:8883' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

与简单请求稍有不同,控制台打印的信息包含关键词显示请求的类型是preflight request类型

在这里插入图片描述

2.1.3 支持跨域请求

只需要在被访问的服务器设置响应头时,添加响应的跨域项即可:

2.1.3.1 对于简单请求

基于 “2.1.1.1 演示简单请求被拒绝”来改造

直接在express_8881.js加上“res.set("Access-Control-Allow-Origin","*");”:

//通过路由,提供一个get的查询服务
app.get('/', function (req, res) {
   // 支持保证跨域
   res.set("Access-Control-Allow-Origin","*");
   res.send('Hello World 8881');
})

需要重启web服务!

简单请求效果:
在这里插入图片描述

2.1.3.2 对于非简单请求(带options请求)

更标准的写法可参见《nodesjs express 统一(全局)设置Respose Headers & 跨域》

对于nodejs express框架来说,由于采用了路由,我们之前的请求是仅响应get方法,因此,对于option等价于无路由,永远返回的是不支持跨域,因此我们需要针对options写个稍复杂的。

基于 “2.1.2.1 options请求未通过例子”来改造

修改express_8881.js:

app.use('/public', express.static('public'));

//在aap.use后面新增一个基础路由,由于是all,因此options也会进去
app.all('*', function(req, res, next) {
   res.header("Access-Control-Allow-Origin","*");
   res.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type");
   res.header("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
   next();
});

在aap.use后面新增一个基础路由,由于是all,因此options也会进去;类似java中的switch中的default,如果某个get请求过来,仍然会匹配get请求的,没有的话,会走all

在这里插入图片描述


参考:
《http请求中的 OPTIONS 详解》

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 不完全正确。虽然OPTIONS请求通常在跨域请求使用,但它不仅限于跨域请求。OPTIONS请求可以用于任何类型的请求,包括同源请求。 OPTIONS请求通常用于跨域请求,因为浏览器在执行跨域请求时会先发送OPTIONS请求以获取服务器支持的请求方法和头信息等。如果服务器支持该请求,则浏览器会发送实际请求,否则请求将失败。这是由于浏览器的同源策略,旨在防止恶意网站获取用户敏感信息或执行不良操作。 ### 回答2: 不是的,OPTIONS请求并不仅仅在跨域时才会使用。OPTIONS请求是根据HTTP协议规定的一种方法,用于获取服务器所支持的HTTP方法列表以及其他与请求的资源相关的信息。它可以用于同域和跨域请求。 在同域请求,当一个客户端发送一个带有OPTIONS方法的请求时,服务器会返回关于该资源所支持的各种请求方法(例如GET、POST等)以及其他信息(例如允许访问该资源的域名列表、请求头部等)。这些信息可以帮助客户端了解服务器所支持的操作,并适当地进行后续操作。 在跨域请求,由于浏览器的同源策略限制,跨域请求需要进行预检(preflight),浏览器会先发送一个OPTIONS请求到目标服务器,以确认是否允许实际请求,例如是否允许通过Ajax请求跨域资源。服务器在收到OPTIONS请求后,会根据请求头部的信息进行验证,然后返回允许的请求方法和其他信息给浏览器,浏览器根据返回的结果来判断是否发送实际请求。 因此,OPTIONS请求不仅局限于跨域请求,它在同域和跨域场景下都有使用的价值。 ### 回答3: options请求并不只会在跨域时才出现。 Options请求是HTTP协议的一种请求方法,用于获取关于服务器支持的请求方法、请求头等信息。它的作用是让客户端可以在发送实际请求之前,了解服务器对于跨域请求的支持情况。 一般情况下,当浏览器发送跨域的GET、POST等请求时,会先发送一个OPTIONS请求到目标服务器,以获取服务器是否允许该跨域请求的相关信息。服务器会在返回的响应头指定所允许的请求方法,请求头信息等内容。只有当服务器返回的OPTIONS响应允许浏览器发送实际请求的方法和头信息时,浏览器才会发送具体的GET、POST等请求。 但是,并非所有场景下都需要发送OPTIONS请求。在同源访问情况下,即域名、协议和端口号都完全一致的情况下,浏览器会直接发送实际的请求,而不会发送OPTIONS请求。 因此,虽然OPTIONS请求在跨域场景下非常常见,但并不是所有的跨域请求都会触发OPTIONS请求。同源访问下的请求是不会触发OPTIONS请求的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值