Jsonp跨域
jsonp用一句话概括就是定义好callback(回调函数)并传送至服务端,服务端采用callback为函数名包裹json数据返回浏览器并执行。
flask服务器(127.0.0.1:5000)
接受回调函数名称并包裹数据返回
@app.route('/json')
def hello_world():
cb = request.args.get("callback")
return '{}("端口5000的消息")'.format(cb)
apache服务器(127.0.0.1:80)
script标签跨域加载端口5000的内容,定义回调函数名并传递给script标签指向的url
<script>
function callback(data){
alert(data)
}
</script>
<script src="http://127.0.0.1:5000/jsonp?callback=callback"></script>
CORS跨域
CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 ajax 只能同源使用的限制。CORS 需要浏览器和服务器同时支持才可以生效,对于开发者来说,CORS 通信与同源的 ajax 通信没有差别,代码完全一样。浏览器一旦发现 ajax 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
flask服务器(127.0.0.1:5000)
服务器配置CORS后可以接受ajax的跨域请求
from flask_cors import CORS
cors = CORS(app)
@app.route('/cors')
def cors_res():
return '端口5000的消息'
apache服务器(127.0.0.1:80)
<script>
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
alert(xhr.responseText);
}
}
xhr.open("GET","http://127.0.0.1:5000/cors",true);
xhr.send(null)
</script>
如果服务器没有配置cors,那么ajax请求会出错。
此时访问两个域响应为
flask服务器:
apache服务器:
可以看出此时请求的资源并没有响应Access-Control-Allow-Origin
字段所以请求失败。
代理服务器
flask(127.0.0.1:5000)
此时后端会代替前端向www.baidu.com
发起请求并把结果返回给前端
@app.route('/proxy')
def proxy():
res = requests.get("http://www.baidu.com")
return res.text
apache服务器(127.0.0.1:80)
<script>
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
alert(xhr.responseText);
}
}
xhr.open("GET","http://127.0.0.1:5000/proxy",true);
xhr.send(null)
</script>
倘若直接请求www.baidu.com
则会
以src的标签请求也会被同源策略拦截
Postmessage
window.postMessage() 方法可以安全地实现跨源通信。一个窗口可以获得对另一个窗口的引用(比如 targetWindow = window.opener),然后在窗口上调用 targetWindow.postMessage() 方法分发一个 MessageEvent 消息。接收消息的窗口可以根据需要自由处理此事件。
Window.postMessage有三个参数message、targetOrigin和可选的[transfer])
- message为发送到其他窗口的消息
- targetOrigin为接受消息的目标窗口
- transfer代表消息的所有权
而与之对应的message消息监听事件window.addEventListener(“message”, receiveMessage, false)
- data为收到的数据
- origin发送消息的域(协议://域名:端口)
- 发送消息的窗口引用
flask服务器(127.0.0.1:5000)
@app.route('/postmessage')
def postmessage():
return render_template('postmessage.html')
postmessage.html内容
监听当前页面收到的message并向页面顶层窗口发送消息
<body>
<p>Postmessage Test</p>
<script>
window.addEventListener("message", function (e){
console.log('端口5000收到消息 : ' + e.data);
top.postMessage("端口5000发送的消息",e.origin);
},false)
</script>
</body>
apache服务器(127.0.0.1:80)
包含iframe子窗口为跨域的页面,设置target为目标域,向目标域发送消息并弹出收到的回复。
<iframe id="chil" src="http://127.0.0.1:5000/postmessage"></iframe>
<script>
window.onload = function() {
let target = "http://127.0.0.1:5000"
window.frames[0].postMessage('端口80发送的消息',target);
console.log("端口80已经发出消息");
}
window.addEventListener('message', function(e) {
console.log('端口80接收到消息:', e.data);
});
</script>
window.name + iframe
window.name属性代表当前窗口的名字,且每个子窗口都有自己独立的name值。而且当前页面重新加载后窗口的名字不会改变.
此时页面跳转到百度的界面但是window.name还是加载bing时的hello
flask服务器(127.0.0.1:5000)
每次页面加载时把当前窗口的name值设置为端口5000的数据
@app.route('/name')
def name():
return '''
<script>
window.name = "端口5000携带的数据"
</script>
'''
apache服务器(127.0.0.1:80)
iframe标签包含端口5000的界面再将src设置为空或者与当前页面同源的url(否则无法操作iframe的内容),取出iframe窗口的name值完成跨域数据传输
<script>
let data = '';
const ifr = document.createElement('iframe');
ifr.src = "http://127.0.0.1:5000/name";
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function() {
ifr.onload = function() {
data = ifr.contentWindow.name;
alert('收到数据:' + data);
}
ifr.src = "about:blank;";
}
</script>
如果不改变ifr.src的值为同域的url则会爆出如下的错误
location.hash + iframe
flask服务器(127.0.0.1:5000)
@app.route('/hash')
def hash():
return render_template("hash.html")
<!-- hash.html: -->
<script>
switch (location.hash) {
case "#data":
callback();
}
function callback() {
try{
parent.location.hash = "端口5000的数据";
}catch (e) {
console.log("尝试修改父窗口hash值失败")
let ifr_proxy = document.createElement('iframe');
ifr_proxy.src = "http://127.0.0.1/proxy.html#" + "端口5000的数据";
document.body.append(ifr_proxy);
}
}
</script>
如果当前页面的hash值为data
则触发回调函数尝试更改父窗口的hash值传递数据,由于浏览器的安全策略,页面包含的子窗口并不能直接修改父窗口的hash值,所以我们利用一个与父页面同源的iframe当做代理,代替端口5000修改端口80的hash值。
apache服务器(127.0.0.1:80)
主页面
<script>
let ifr = document.createElement('iframe');
ifr.src = "http://127.0.0.1:5000/hash#data"
ifr.style.display = 'none';
document.body.append(ifr);
window.addEventListener('hashchange',function (e){
alert('从端口5000获得的数据是: ',location.hash.substring(1))
})
</script>
主页面创建跨源页面的iframe并且监听自身的hash值改变情况,如果本身的hash被改变则说明顺利收到跨源界面传来的信息。
<!--127.0.0.1/proxy.html 代理iframe-->
<script>
parent.parent.location.hash = self.location.hash.substring(1);
</script>
代理iframe界面用来做两个跨域页面修改hash的桥梁,整个数据传递的过程如下图所示
document.domain降域
这种跨域方式仅限于主域相同而子域不同的情况,还是创建两个html页面,分别对应sub.example.com
和example.com
,修改sub.example.com
的document属性为example.com
就可以达到同域直接操作数据的目的。
flask服务器(127.0.0.1:5000)
@app.route('/document')
def document():
return '''
<script>
document.domain = 'example.com';
window.data = '端口5000的数据';
</script>
'''
flask服务器(127.0.0.1:5050)
@app.route('/crossdomain')
def hello_world():
return render_template("cross.html")
<!-- cross.html-->
<script>
document.domain = 'example.com';
let ifr = document.createElement('iframe');
ifr.src = "http://sub.example.com/document";
ifr.style.display = "none";
document.body.append(ifr);
ifr.onload = function(){
let win = ifr.contentWindow;
alert(win.data);
}
</script>
未设置之前的因为同源策略的错误
首先更改host文件的映射C:\Windows\System32\drivers\etc\host
,增加
127.0.0.1 www.example.com
127.0.0.1 sub.example.com
这样每次访问example.com的页面就dns会自动解析为127.0.0.1,再配置nginx反向代理
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://127.0.0.1:5050/;
}
}
server {
listen 80;
server_name sub.example.com;
location / {
proxy_pass http://127.0.0.1:5000/;
}
}
监听端口80如果有来自域名www.example.com
的请求则转发到5050端口处理,如果来自sub.example.com
则转发到5000端口。
最后访问www.example.com(127.0.0.1:5050)可以跨域收到5000端口的数据
参考文章
https://www.zhihu.com/search?type=content&q=%E8%B7%A8%E5%9F%9F
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage
https://blog.csdn.net/u011897301/article/details/52679486