1.前言
fetch是一个在js中非常好用的网络请求方法,可以当作一种浏览器原生支持的,xhr的简写升级版。使用时其mode参数可以进行对网络请求跨域时的设置,在研究fetch使用时,想到我们开发时经常会遇到跨域请求失败的情况:
以前只知道jsonp和cors等手段可以解决跨域问题,八股背了一大堆,其本质却不甚明了。所以今天想通过一些实验弄清楚:这个跨域失败究竟是如何失败?在进行到哪一步时失败的?
2.fetch基础
简单来说,fetch是一个网络请求的函数,在浏览器下是个全局函数,可以在console中直接用。其最大好处有两点:
- 写起来简单
- 返回结果时promise对象 比如我们在掘金首页console中运行如下命令,可以获得首页的html内容:
fetch("https://juejin.cn/")
.then((response) => response.text())
.then((data) => {
console.log(data);
});
若是使用传统的xhr形式的ajax写法:
const xhr = new XMLHttpRequest(),
method = "GET",
url = "https://juejin.cn/";
xhr.open(method, url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
var status = xhr.status;
if (status === 0 || (status >= 200 && status < 400)) {
console.log(xhr.responseText);
}
}
};
xhr.send();
况且这种回调式的写法还是不如promise来得爽利,此外fetch还处理流数据的结果,扩展了xhr的使用范围。
2.1 fetch参数
只放第一个参数url时fetch默认用GET方法,还可以配置器第二个参数对象:
fetch(url, optionObj)
具体第二个参数对象内容见下,星号为默认设置:
fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
通过第二个参数的设置,我们可以进行POST请求,设置请求头,请求体等等。
2.2 fetch结果
fetch函数的返回结果是一个promise对象,其中resolve出来的是一个Response对象,通过这个对象我们可以使用一些方法读取其中的数据,并返回一个promise:
response.text() //读文本
response.json() //读json
response.blob() // Blob 对象
response.formData() // FormData 表单对象。
response.arrayBuffer() // 得到二进制 ArrayBuffer 对象
也可以通过一些属性判断请求是否成功,方便进行后续行为
Response.ok //请求是否成功
Response.status //表示 HTTP 回应的状态码
Response.statusText //一个字符串,表示 HTTP 回应的状态信息
3.模拟跨域实验与fetch跨域的mode参数
3.1 模拟跨域
所谓跨域请求,就是浏览器页面与其所请求的页面不同源,即两个地址有协议、域名、端口号三者任一不同则均属跨域请求。 下面模拟一个跨域请求: 现在本地起一个简单的node服务器,以便我们控制服务端行为,代码如下:
var http = require("http");
var data = {
name: "link",
age: 121,
};
http
.createServer(function (request, response) {
// 不加Access-Control-Allow-Origin响应头就无法实现跨域,*表示所有域都可以请求
// response.setHeader("Access-Control-Allow-Origin", "*");
console.log(request.url);
response.write(JSON.stringify(data));
response.end();
})
.listen(8030, function () {
console.log("Server has stared...");
});
然后我们打开掘金首页并打开console,在其中运行网络请求指令就是跨域了,请求一下本地服务器地址:
fetch('http://127.0.0.1:8030/')
因为我们没有在服务端配置跨域,所以熟悉的跨域报错出现了:
看下网络日志,确实是跨域错误:
至此,我们构造了一个跨域情景模拟,在此环境中,我们研究fetch的mode参数设置的不同。
3.2 不同fetch mode参数下的跨域结果
mode参数设置了网络请求的跨域形式:
mode: 'cors', // no-cors, *cors, same-origin
下面分别研究三个参数下跨域情况:
3.2.1 same-origin
在掘金首页执行以下代码:
fetch('http://127.0.0.1:8030/', {mode: 'same-origin'})
该参数下执行严格的同源政策,非同源不发送请求:
也就是该请求在发送时就被阻止了,服务器端并未收到该请求。示意图如下
3.2.2 no-cors
no-cors很容易让人误解为不跨域也能请求,类似jsonp这种,但其实不然,no-cors是一种特殊的跨域请求模式。这种模式下服务端没有处理跨域的能力,但是我们仍发起跨域,不过这个请求类似于表单提交,我们并不打算得到什么结果,跨域了也不会报错,回来的响应是可以被忽略的。也就是说,请求是单向的,就是想发点东西到服务器端。
fetch('http://127.0.0.1:8030/', {mode: 'no-cors'})
没有报错
网络请求也正常
不过其返回的response对象的type为opaque,返回也是无内容的,也就是说并不能得到数据。不过服务器端是可以得到数据的:
fetch('http://127.0.0.1:8030/?name=link', {mode: 'no-cors'})
示意图如下:
3.2.3 cors
这个参数是默认参数,前文也可以,看到我们就是用这个命令来模拟最初的跨域失败情景的。具体内容与3.1类似,到但是我们加上一些参数再请求,看看服务端是否接收到数据:
fetch('http://127.0.0.1:8030/?name=link')
跨域失败:
服务端可以接收到数据:
过程示意图如下:
若我们在服务器端开启跨域配置,即
response.setHeader("Access-Control-Allow-Origin", "*")
把前文中的注释放出来即可。
那么跨域请求将会和普通同源请求一样顺畅,不需要我们在前端多做什么事情。
3.3 谁阻止了跨域?
在上述例子中,服务器端并未对发起请求的页面地址作任何分析,所以是否跨域,其实全都是浏览器自己来进行判断的。其过程大致如下:
- 判断是否跨域,跨域则在请求头加origin属性。
- 服务器返回数据。
- 浏览器判断服务器返回数据响应头中有无Access-Control-Allow-Origin,无或不匹配则为跨域。熟悉的报错就会如约而至。匹配成功则无事发生。 不过此处仅是简单请求,复杂请求时需要预检请求option,但其核心还是在服务端的配置。
4.倘若浏览器不禁止跨域
到这儿,又有了个新的疑问。浏览器是不是吃饱了撑的,整这么多跨域来恶心🤢我们开发者啊?这跨域能不能不要?
4.1 实验
别说,还真能不要跨域,但是得对浏览器入口快捷方式进行一定的改造。 改造完了之后:
运行跨域请求也是畅通无阻:
4.2 分析
那我们能不能教谷歌做事,让他把跨域限制取消呢?😁
答案是否定的,若没有同源限制,浏览器则是不安全的浏览器,用户怎么会敢使用随使暴雷的浏览器呢?至于其隐患的具体体现则是我们老生常谈的csrf问题。具体内容已超出本文范畴,详见超链接。
5.总结
- 跨域是浏览器做出的限制,为保证安全牺牲了一定自由度。
- cors的目的是为了将跨域逻辑后移,让服务器决定谁能跨域。(不过现实是很多后端懒得配代码,前端还是得用jsonp)
- fetch简化了操作,使用起来比较顺滑