为什么一个axios请求会发起两次(详解)?

4,479 阅读8分钟

一直都有这样的疑问,为什么有的项目中明明这个请求我只写了一次,但是在控制台中会出现两遍,今天仔细看了下相关内容,做了如下记录:

问题:

如下图所示:在项目中只写了一次的请求,在实际netWork中发送了两次,第一次为不带参数的请求方式为options的请求,第二次为我们自己定义的带了参数的请求方式

1.png

2.png

1. 因为vue是没有提供ajax请求功能,所以需要使用vue-resource、axios等插件实现ajax请求

2. axios本质上是javascript的ajax封装,所以在使用axios发送请求时会被同源策略限制

什么是同源策略:

1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。

最初,它的含义是指,A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”。所谓“同源”指的是”三个相同“。

即:协议相同 域名相同 端口相同

如:www.example.com/dir/page.ht…

协议是http://, 域名是 www.example.com ,端口是80(默认端口可以省略)

目前,如果非同源,共有三种行为受到限制:(如下所示:)

(1) 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB。

(2) 无法接触非同源网页的 DOM。

(3) 无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)

另外,通过 JavaScript 脚本可以拿到其他窗口的window对象。如果是非同源的网页,目前允许一个窗口可以接触其他网页的window对象的九个属性和四个方法。(自查)

同源策略的目的是为了保证用户信息的安全,防止恶意的网站窃取数据。

那么,当我们发起http请求的时候,由于同源策略会导致跨域的问题,接下来就讲讲跨域:

跨域场景有哪些呢:

3.png

关于上图,针对我自己不太理解的进行下解释:

4.png

关于这一条,举个例子,比如说现在有两个页面,一个是用域名访问的( http://www.domain.com ),
用这个域名寻址(DNS服务器)到的ip地址为192.168.4.12,另一个页面是直接用这个ip地址访问的页面
(http://192.168.4.12 ),这两个页面任然不能通讯

5.png

很多小伙伴对域名可能也是一知半解的,跟我一样,我找到了一张比较清晰的图,如下:

6.png

.com 顶级域名(一级域名)

zzvips.com 一级域名

www .zzvips .com 二级域名

bingo:简单点的记忆方法就是有几个点就是几级域名

跨域解决方案:

同源政策规定,AJAX 请求只能发给同源的网址,否则就报错。

除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。

JSONP

WebSocket

CORS

JSONP:

JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务端改
造非常小。


它的基本思想是,网页通过添加一个<script>元素(script/link/img标签的属性不受同源政策限制),
向服务器请求 JSON 数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字
的回调函数里传回来。

WebSocket

WebSocket 是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同
源政策,只要服务器支持,就可以通过它进行跨源通信。

CORS

CORS 是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX
请求的根本解决方法。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同
源使用的限制。相比 JSONP 只能发GET请求,CORS 允许任何类型的请求。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能(IE浏览器不能低于IE10)。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

看到这里的小伙伴应该就知道了我们在为什么一个请求会发起两次了,正是因为后端开启了cors跨域资源共享

CORS两种请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

简单请求

若请求满足所有下述条件,则该请求可视为“简单请求”:

1. 使用下列方法之一:

GET

HEAD(与GET类似,但是HEAD并不返回消息体,响应可以被缓存,一般用于检查资源的有效性、检查超链接的有效性、 检查网页是否被串改、多用于自动搜索机器人获取网页的标志信息,获取rss种子信息,或者传递安全认证信息等)

POST

2. 除了被用户代理自动设置的首部字段(例如 Connection ,User-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:

Accept

Accept-Language

Content-Language

Content-Type (需要注意额外的限制)

DPR

Downlink

Save-Data

Viewport-Width

Width 其实总结就是:无自定义的header,并且HTTP头部信息不超过以上几种字段

3. Content-Type 的值仅限于下列三者之一:

text/plain

multipart/form-data

application/x-www-form-urlencoded

3. 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。

4.请求中没有使用 ReadableStream 对象。

例如:

站点 foo.example 的网页应用想要访问 bar.other 的资源 那么浏览器请求头上就会带上Origin(Origin:协议+域名+端口号)这个字段,用于标识来源,服务器会根据这个值来决定是否允许其跨域。 如果服务器允许跨域,则需要在相应头中携带如下信息:

1. Access-Control-Allow-Origin:http: //foo.example(或是 * ) 

2. Access-Control-Allow-Credentials:true

3. Content-type:text/html; charset=utf-8

Access-Control-Allow-Origin:允许哪个域名进行跨域,是一个具体的域名或者 * ( * 代表任何域名)

Access-Control-Allow-Credentials:是否允许携带cookie,默认情况下CORS不会携带cookie,除非这个值是true

想要操作cookie需要满足3个条件:

  1. 服务器响应头中需要携带Access-Control-Allow-Credentials并且值为true

  2. 在浏览器发起ajax请求时需要在请求头上指定withCredentials:true

  3. 响应头中的Access-Control-Allow-Origin一定不能为*,必须是指定的地址

withCredentials:

是一个Boolean类型,它指示了是否该使用类似cookies,authorization headers(头部授权)或者TLS客户端证书这一类资格证书来创建一个跨站点访问控制(cross-site Access-Control)请求。在同一个站点下使用withCredentials属性是无效的。

这个指示也会被用做响应中cookies 被忽视的标示。默认值是false

如果在发送来自其他域的XMLHttpRequest请求之前,未设置withCredentials 为true,那么就不能为它自己的域设置cookie值。而通过设置withCredentials 为true获得的第三方cookies,将会依旧享受同源策略,因此不能被通过document.cookie或者从头部相应请求的脚本等访问。

实例:

var xhr = new XMLHttpRequest();

xhr.open('GET', 'http://example.com/', true);

xhr.withCredentials = true;

xhr.send(null);

8.png

非简单请求

不符合一上条件的为复杂请求, 如果为复杂请求,则会发起一个请求方式为options的预请求,如下所示:

9.png 浏览器通过Origin字段先询问服务器当前网页所在的域名是否在服务器的许可名单内,以及可以使用哪些Http动词和头信息字段

只有通过了预检请求浏览器才会发出正式的携带相关参数的XMLHttpRequest请求,否则就报错

一个“预检”请求样板:

OPTIONS  /cors HTTP/1.1
Origin:  http://liudan.handou.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.leyou.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

与简单请求相比,除了Origin以外,多了两个头:

Access-Control-Request-Headers:额外会用到的请求头信息

Access-Control-Request-Method:请求方式 ,如PUT

预检请求的响应头:(服务器收到的预检请求后,如果允许跨域会发出响应):

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2021 01:15:39 GMT
Server: Apache/2.0.61(Unix)
Access-Control-Allow-Origin: http://liudan.handou.com
Access-Control-Allow-Credentials:true
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers:X-Custom-Header
Access-Control-Max-Age: 172800
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length:0
Keep-Alive:timeout=2,max=100
Connection:keep-alive
Content-Type:text/plain

在响应头中除了Access-Control-Allow-Origin和Access-Control-Allow-Credentials外,又额外多出了3个返回值:

Access-Control-Allow-Methods:允许访问的方式

Access-Control-Allow-Headers:允许携带的请求头

Access-Control-Max-Age:本次许可的有效市场,单位:s,过期之前的ajax就无需再次进行预检了

如果浏览器得到了上述响应,就认定为可以跨域,后续就跟简单请求一样处理,以下为复杂请求的具体图示:

10.png