Open maicFir opened 2 years ago
跨域产生的原因首先是受浏览器的安全性设计影响,由于浏览器同源策略的设计,所以产生了跨域。
在项目中,我们常常遇到跨域的问题,虽然在你的项目里,脚手架已经 100%做好了本地代理、或者运维老铁在nginx中也已经给你做了接口代理,所以你遇到跨域的概率会少了很多,但是在传统的项目中,在那个jquery的横行时代,或者你接手了一个祖传项目时,跨域问题会是时有发生,本文只做笔者了解跨域的通用解决方案,希望在实际项目中对你有些思考和帮助。
nginx
jquery
正文开始...
非同源是无法彼此访问cookie,dom操作,ajax、localStorage、indexDB操作
cookie
dom操作
ajax
localStorage
indexDB
但是非同源可以访问以下一些属性
window.postMessage(); window.location window.frames window.parent ...
对于非同源网站跨域通信,我们可以采用以下几种方案
通过在url上放入#type,利用hashchange事件,获取父页面的相关hash数据
url
#type
hashchange
hash
// parent const type = 'list'; const originUrl = originUrl + '#' + type; const iframe = document.getElementById('iframe'); iframe.src = originUrl;
在子iframe中,我们可以监听hashchange事件
iframe
// iframe window.addEventListener('hashchange', (evt) => { console.log(evt); // 获取片段标识符的相关数据 });
子页面可以利用window.postMessage向父页面发送信息,父页面监听message接收子页面发送的消息,通常这种方式在iframe通信中用得做多,也是跨域通信的一种解决方案
window.postMessage
message
// parent window.addEventListener('message', (evt) => { const data = evt.data; console.log(data); });
// child const origin = '*'; // 这里一般是父页面的域名,但是也可以是* const data = { text: 'hello, world' }; window.postMessage(data, origin);
利用script标签,向服务端发送一个get请求,url上绑定一个callback=fn,这个fn通常是与后端约束好,fn是客户端执行的函数名。
script
get
callback=fn
fn
用一个简单的例子来解释下jsonp
jsonp
客户端示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>jsonp</title> </head> <body> <h3>jsonp</h3> <div id="app"></div> <script> var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script.src = 'http://192.168.31.40:8080/api?callback=jsonp'; document.body.appendChild(script); const renderHtml = ({ name, age }, dom) => { dom.innerHTML = `<div>我的名字 ${name},今年 ${age}岁了</div>`; }; function jsonp(data) { console.log(data); const app = document.getElementById('app'); const { name, age } = data; renderHtml({ name, age }, app); } </script> </body> </html>
服务端示例代码
// server.js const http = require('http'); const fs = require('fs'); const path = require('path'); const PORT = '8080'; const server = http.createServer((req, res) => { res.statusCode = 200; // console.log(req.url); res.setHeader('Content-Type', 'text/html'); fs.readFile(path.resolve(__dirname, '../', 'index.html'), (err, data) => { if (err) { res.end('404'); return; } res.end(data); }); const data = { name: 'Maic', age: Math.floor(Math.random() * 20) }; if (req.url.includes('/api')) { res.end(`jsonp(${JSON.stringify(data)})`); } }); server.listen(PORT, () => { console.log('server is start' + PORT); });
注意我们看到有这样的一段判断req.url.includes('/api'),然后我们res.end(jsonp(JSON.stringify(data))),返回的就是jsonp这个回调函数,把数据当形参传给前端,在客户端定义的jsonp方法就能获取对应的数据。
req.url.includes('/api')
res.end(
)
执行node server.js打开http://localhost:8080我们会发现
node server.js
http://localhost:8080
返回的数据就是fn({name: 'xx',age: 'xx'})
fn({name: 'xx',age: 'xx'})
我们看下请求头 浏览器会发送一个get请求,所携带的参数就是callback: jsonp,而我们在客户端确实是通过jsonp这个方法拿到对应的数据了。
callback: jsonp
所以我们可以知道jsonp实际上就是利用一个客户端发送的get请求携带一个后端服务的返回的回调函数,在客户端,我们定义这个回调函数就可以获取后端返回的形参数据了。
回调函数
从jsonp这种跨域通信来看,其实有也它的缺点和优点
1、它的安全性会有一定风险,因为依赖的结果就是那个回调函数的形参内容,如果被人劫持修改返回数据,那可能会造成安全性问题
2、仅支持get请求,不支持其它http请求方式,我们发现jsonp这种通信就是利用script标签请求了一个url,url上携带了一个可执行的回调函数,进而通过后端给回调函数传递数据的。
http
3、没有任何状态码,数据丢给客户端,如果有失败情况,不会有像http状态码一样
能解决跨域通信问题,兼容性比较好,不受同源策略的影响,对后端来说实现也简单。
在以前比较久远的项目中,你可能是直接使用jquery,jsonp就ok了,大概的代码就是长这样的
ok
$.ajax({ url: 'xxx', dataType: 'jsonp', jsonp: 'successCallback', success: (data) => { console.log(data); }, error: (err) => { console.log(err); } });
successCallback这个就是与服务端约束的回调函数,一般与服务端沟通一致就行,那么简单的jsonp就已经完成了,是不是感觉很简单呢?
successCallback
由于WebSocket不受同源策略的限制,因此WebSocket也是可以实现跨域通信的。这里就不举例子了,具体可以参考之前写的一篇浅谈 websocket这篇文章
WebSocket
这种方式是在服务端进行控制,允许任何资源请求。其实在浏览器端,即使跨域,还是会正常请求,只是请求非同源环境的后端服务,浏览器禁止请求访问,更多可以参考这篇文章cors
我们写个例子具体测试一下,在客户端加入这段代码
const send = document.getElementById('send'); send.onclick = function () { if (!window.fetch) { return; } fetch('http://localhost:8081/list.json') .then((res) => res.json()) .then((result) => { console.log(result); const contentDom = document.querySelector('.content'); renderHtml(result, contentDom); }); };
服务端代码,我们新建一个index2.js服务端代码,并执行node index2.js
index2.js
node index2.js
const http = require('http'); const PORT = '8081'; const server = http.createServer((req, res) => { res.statusCode = 200; // // console.log(req.url); res.setHeader('Content-Type', 'application/json'); if (req.url.includes('/list.json')) { res.end( JSON.stringify({ name: 'maic', age: Math.ceil(Math.random() * 20) }) ); } }); server.listen(PORT, () => { console.log('server is start' + PORT); });
我们注意到请求的端口是8081,打开8080页面
8081
8080
点击按钮,发送fetch请求,我们发现浏览器报了这样你常常看到的跨域信息as been blocked by CORS policy: No 'Access-Control-Allow-Origin' header...,因为跨域了
fetch
as been blocked by CORS policy: No 'Access-Control-Allow-Origin' header...
紧接着我们在服务端设置下Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: *
const http = require('http'); const PORT = '8081'; const server = http.createServer((req, res) => { res.statusCode = 200; // // console.log(req.url); res.setHeader('Content-Type', 'application/json'); res.setHeader('Access-Control-Allow-Origin', '*'); if (req.url.includes('/list.json')) { res.end( JSON.stringify({ name: 'maic', age: Math.ceil(Math.random() * 20) }) ); } }); server.listen(PORT, () => { console.log('server is start' + PORT); });
此时再次访问时,已经 ok 了 注意我们可以看下Response Headers
Response Headers
HTTP/1.1 200 OK Content-Type: application/json Access-Control-Allow-Origin: * Date: Sun, 01 May 2022 14:23:59 GMT Connection: keep-alive Content-Length: 24
Access-Control-Allow-Origin:*这是我们服务的设置响应请求头,设置允许所有域名请求。
Access-Control-Allow-Origin:*
因此cors跨域其实在服务端设置Access-Control-Allow-Origin: *也就完美的解决了跨域问题。
cors
Access-Control-Allow-Origin: *
跨域产生的原因,主要受同源策略的影响,非同源环境,无法相互访问cookie、localStorage、dom操作等
解决跨域的方案主要有片段标识符、iframe通信postMessage,jsonp、WebSocket、cors
片段标识符
iframe通信postMessage
用具体实际例子深入了解几种跨域模式,比如jsonp,实际上是利用script发送一个get请求,在get请求的参数中传入一个可执行的回调函数,服务根据请求,将返回一个前端可执行的回调函数,并且将数据当成该回调函数的形参,在前端定义该回调函数,从而获取调函数传入的数据。
用具体 🌰 看到服务端设置cors,主要是在后端接口返回响应头里设置Access-Control-Allow-Origin:*允许所有不同源网站访问,这种方法也是比较粗暴的解决跨域问题。
本文示例代码code example
在项目中,我们常常遇到跨域的问题,虽然在你的项目里,脚手架已经 100%做好了本地代理、或者运维老铁在
nginx
中也已经给你做了接口代理,所以你遇到跨域的概率会少了很多,但是在传统的项目中,在那个jquery
的横行时代,或者你接手了一个祖传项目时,跨域问题会是时有发生,本文只做笔者了解跨域的通用解决方案,希望在实际项目中对你有些思考和帮助。正文开始...
何为同源
非同源是无法彼此访问
cookie
,dom操作
,ajax
、localStorage
、indexDB
操作但是非同源可以访问以下一些属性
对于非同源网站跨域通信,我们可以采用以下几种方案
片段标识符
通过在
url
上放入#type
,利用hashchange
事件,获取父页面的相关hash
数据在子
iframe
中,我们可以监听hashchange
事件跨文档通信 window.postMessage
子页面可以利用
window.postMessage
向父页面发送信息,父页面监听message
接收子页面发送的消息,通常这种方式在iframe
通信中用得做多,也是跨域通信的一种解决方案jsonp
利用
script
标签,向服务端发送一个get
请求,url
上绑定一个callback=fn
,这个fn
通常是与后端约束好,fn
是客户端执行的函数名。用一个简单的例子来解释下
jsonp
客户端示例
服务端示例代码
注意我们看到有这样的一段判断
req.url.includes('/api')
,然后我们res.end(
jsonp(JSON.stringify(data)))
,返回的就是jsonp
这个回调函数,把数据当形参传给前端,在客户端定义的jsonp
方法就能获取对应的数据。执行
node server.js
打开http://localhost:8080
我们会发现返回的数据就是
fn({name: 'xx',age: 'xx'})
我们看下请求头 浏览器会发送一个
get
请求,所携带的参数就是callback: jsonp
,而我们在客户端确实是通过jsonp
这个方法拿到对应的数据了。所以我们可以知道
jsonp
实际上就是利用一个客户端发送的get
请求携带一个后端服务的返回的回调函数
,在客户端,我们定义这个回调函数
就可以获取后端返回的形参数据了。从
jsonp
这种跨域通信来看,其实有也它的缺点和优点1、它的安全性会有一定风险,因为依赖的结果就是那个回调函数的形参内容,如果被人劫持修改返回数据,那可能会造成安全性问题
2、仅支持
get
请求,不支持其它http
请求方式,我们发现jsonp
这种通信就是利用script
标签请求了一个url
,url
上携带了一个可执行的回调函数,进而通过后端给回调函数传递数据的。3、没有任何状态码,数据丢给客户端,如果有失败情况,不会有像
http
状态码一样能解决跨域通信问题,兼容性比较好,不受同源策略的影响,对后端来说实现也简单。
在以前比较久远的项目中,你可能是直接使用
jquery
,jsonp
就ok
了,大概的代码就是长这样的successCallback
这个就是与服务端约束的回调函数,一般与服务端沟通一致就行,那么简单的jsonp
就已经完成了,是不是感觉很简单呢?WebSocket
由于
WebSocket
不受同源策略的限制,因此WebSocket
也是可以实现跨域通信的。这里就不举例子了,具体可以参考之前写的一篇浅谈 websocket这篇文章CORS 跨域资源分享
这种方式是在服务端进行控制,允许任何资源请求。其实在浏览器端,即使跨域,还是会正常请求,只是请求非同源环境的后端服务,浏览器禁止请求访问,更多可以参考这篇文章cors
我们写个例子具体测试一下,在客户端加入这段代码
服务端代码,我们新建一个
index2.js
服务端代码,并执行node index2.js
我们注意到请求的端口是
8081
,打开8080
页面点击按钮,发送
fetch
请求,我们发现浏览器报了这样你常常看到的跨域信息as been blocked by CORS policy: No 'Access-Control-Allow-Origin' header...
,因为跨域了紧接着我们在服务端设置下
Access-Control-Allow-Origin: *
此时再次访问时,已经 ok 了 注意我们可以看下
Response Headers
Access-Control-Allow-Origin:*
这是我们服务的设置响应请求头,设置允许所有域名请求。因此
cors
跨域其实在服务端设置Access-Control-Allow-Origin: *
也就完美的解决了跨域问题。总结
跨域产生的原因,主要受同源策略的影响,非同源环境,无法相互访问
cookie
、localStorage
、dom操作
等解决跨域的方案主要有
片段标识符
、iframe通信postMessage
,jsonp
、WebSocket
、cors
用具体实际例子深入了解几种跨域模式,比如
jsonp
,实际上是利用script
发送一个get
请求,在get
请求的参数中传入一个可执行的回调函数,服务根据请求,将返回一个前端可执行的回调函数,并且将数据当成该回调函数的形参,在前端定义该回调函数,从而获取调函数传入的数据。用具体 🌰 看到服务端设置
cors
,主要是在后端接口返回响应头里设置Access-Control-Allow-Origin:*
允许所有不同源网站访问,这种方法也是比较粗暴的解决跨域问题。本文示例代码code example