经常使用跨域方法总结

经常使用跨域方法总结

为何要跨域?

由于浏览器的一种安全机制——同源策略的限制,致使不能直接获取不一样源的资源,因此要跨域。javascript

同源策略限制了从同一个源加载的文档或脚本如何与来自另外一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

限制之一是不能经过ajax的方法去请求不一样源中的文档。
第二个限制是浏览器中不一样域的框架(iframe)之间是不能进行js的交互操做的。html

那么什么才叫“同源”呢?前端

  • 协议相同
  • 域名相同
  • 端口号相同

clipboard.png

图来自MDN,参见最后Reference.java

下面介绍经常使用的几种跨域方法。node

jsonp 跨域

原理:git

  • 利用了<script>标签不受浏览器同源限制的影响,
  • 由于借用<script>发起的请求,因此会把请求到的内容看成js代码来解析执行。

缺点:只能发送get请求
下面是一个简单的例子github

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
function callback (msg) {
  console.log('json:', msg)
}
</script>
<script src="http://localhost:8080?cb=callback ">
</script>
</body>
</html>

如上,定义了一个全局的callback 函数,而后利用<script>来发起get请求,注意把函数名称callback 一同发送给服务端,为何呢,接着往下看服务端代码ajax

//nodeJs
var http = require('http');
var url = require('url');

/**
 * 客户端请求的函数
 * @param req
 * @param res
 */
function onRequest (req, res) {
  console.log("获取到的请求参数的路径:" + req.url);

  //获得键值对
  var arg = url.parse(req.url, true).query;
  //打印键值对中的值
  console.log(arg.cb);

  res.writeHead(200);
  res.write(arg.cb + '("我今天要用jsonp来跨域获取数据")');
  res.end();
}

http.createServer(onRequest).listen(8080);

服务端简单解析cb参数(咱们传的函数名称),而后返回一段字符串callback("我今天要用jsonp来跨域获取数据")
浏览器会收到响应以下:chrome

clipboard.png

clipboard.png

发现什么了吗?响应内容callback("我今天要用jsonp来跨域获取数据")会被看成js代码来执行,正好调用了咱们以前定义的callback函数。
由此,咱们成功的利用jsonp经过跨域获取到了想要的数据。json

document.domain跨域(子域名不一样的框架之间)

开头咱们说到不一样源的框架之间是不能进行js交互操做的,实际上是能够获取window对象,但不能获取window的属性。
原理:

  • document.domain的值是能够设置的,但仅限于设置为自身或是更高一级的父级域名(主域名相同)。

那么主域名相同,子域名不一样的框架之间跨域获取数据的思路就来了,咱们把它们的document.domain都设置成主域名不就完事了?
好比有一个页面a.google.com/1.html
这里参考了其余文章的代码。出处见本文最下方

<iframe id = "iframe" src="http://b.google.com/2.html" onload = "test()"></iframe>
<script type="text/javascript">
    document.domain = 'google.com';//设置成主域
    function test(){
        alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象
    }
</script>

另外一个页面b.google.com/2.html只需设置主域名为google.com,两个框架间就能够进行交互啦。

<script type="text/javascript">
document.domain = 'google.com';//设置成主域
</script>

location.hash跨域(不一样源的框架之间)

原理:

  • hash字段(常常用于锚点定位)不属于http协议的部分,请求不会携带hash信息,因此改变不会从新请求资源(可是会产生新的浏览器历史记录,许多前端路由也是借用这个原理实现的)
  • 父窗口能够对iframe进行URL读写,iframe也能够读写父窗口的URL(不一样源的话,IE、Chrome不容许修改parent.location.hash的值,但咱们仍有处理方式)

思路:

  • 若是是父窗口向子窗口跨域传递数据,直接修改子窗口url的hash就能够了,比较简单这里就不贴代码了。
  • 子窗口向父亲窗口跨域传递数据就须要多加一个代理窗口(由于IE,Chrome),这个代理窗口和父亲窗口同源就能够了,在这个代理窗口改变父亲窗口的hash,父亲窗口监听hashchange就能够了。

代码以下:
父亲窗口页面
http://localhost:63342/test-field/cross-origin/local-test/hash-parent.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<iframe src="http://blank121.github.io/test-field/cross-origin/hash.html">
</iframe>
<script>
console.log('old hash:', location.hash)
window.addEventListener('hashchange', function (ev) {
  console.log('new hash:', location.hash)
})
</script>
</body>
</html>

子窗口页面
http://blank121.github.io/tes...

try{
  parent.location.hash='今天我要用hash跨域'
  //chrome ie 直接修改parent.location.hash报错
}catch (e){
  var iframe = document.createElement('iframe')
  iframe.src = 'https://localhost:63342/test-field/cross-origin/local-test/hash-proxy.html'+'#今天我要用hash跨域'
  document.body.appendChild(iframe);
}

代理窗口页面
http://localhost:63342/test-field/cross-origin/local-test/hash-proxy.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
console.log('proxy',location.hash)
parent.parent.location.hash = location.hash
</script>
</body>
</html>

如上,在子窗口页面内修改了代理窗口的hash值,代理窗口又修改了父亲窗口的hash值,父亲窗口监听hashchange就能够获取到不一样源的子窗口传来的数据啦。
控制台结果以下:

clipboard.png

window.name跨域(不一样源框架间)

原理:

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的全部的页面都是共享一个window.name的,每一个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的全部页面中的,并不会因新页面的载入而进行重置。

利用一个窗口的生命周期内,载入不一样页面window.name不变的特性
贴代码前首先去作个有趣的实验
在必应的首页设置一下window.name

clipboard.png

而后输入location.href = 'http://www.baidu.com',跳转到百度后再看一下name

clipboard.png

666,window.name果真没骗我,没有变化。

接下来先贴代码
父亲窗口:
http://localhost:63342/test-field/cross-origin/local-test/name-parent.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<iframe id="frame" src="https://blank121.github.io/test-field/cross-origin/name.html"></iframe>
<script>
var iframe = document.getElementById('frame')
var loaded = false
iframe.onload = function () {
  if (!loaded) {
    loaded = true
    iframe.src = 'http://localhost:63342/test-field/cross-origin/local-test/name-proxy.html'
  } else {
    console.log(iframe.contentWindow.name)
  }
}
</script>
</body>
</html>

子窗口:
https://blank121.github.io/te...

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>window.name跨域</title>
</head>
<body>
<script>
window.name='今天,我要用window.name来实现跨域'
</script>
</body>
</html>

代理窗口(啥也没作。主要是要和父亲窗口同源来传递name)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
</body>
</html>

代码如上,主要思路就是利用window.name不变性,子窗口设置了name以后,来加载一个和父亲窗口同源的代理窗口,以此来获取name(注意子窗口和代理窗口是同一个iframe,加载不一样页面而已,因此window.name不变)

clipboard.png

postMessage跨域

首先了解一下postMessage
otherWindow.postMessage(message, targetOrigin);
otherWindow:指目标窗口,也就是给哪一个window发消息,是 window.frames 属性的成员或者由 window.open 方法建立的窗口
message: 是要发送的消息,类型为 String、Object (IE八、9 不支持)
targetOrigin: 是限定消息接收范围,不限制请使用 *

postMessage是HTML5新特性,跨域简直太方便了,就是兼容性要注意一下。

发送方页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<iframe id="frame" src="https://blank121.github.io/test-field/cross-origin/receive.html">
</iframe>
<script>
var iframe = document.getElementById('frame')
iframe.onload = function () {
    iframe.contentWindow.postMessage("我今天就是要用postMessage跨域","https://blank121.github.io")
}
</script>
</body>
</html>

接收方页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
function receiveMessage(event)
{
  var p = document.createElement('p')
  p.innerText = event.data
  document.body.appendChild(p)
}
window.addEventListener("message", receiveMessage, false);
</script>
</body>
</html>

接收方监听一下message事件,就能够接收到信息啦。

clipboard.png

WebSocket跨域

WebSocket 是一种在客户端与服务器之间保持TCP长链接的网络协议,这样它们就能够随时进行信息交换(双工通信)。
虽然任何客户端或服务器上的应用均可以使用WebSocket,但原则上仍是指浏览器与服务器之间使用。经过WebSocket,服务器能够直接向客户端发送数据,而无须客户端周期性的请求服务器,以动态更新数据内容。

WebSocket 很是强大,笔者在这方面也是小白级别的,之后有时间会详细研究学习。

跨域代码以下
页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
</body>
</html>
<script>
// Create WebSocket connection.
const socket = new WebSocket('ws://127.0.0.1:8080');

// Connection opened
socket.addEventListener('open', function (event) {
  socket.send('Hello Server!');
});

// Listen for messages
socket.addEventListener('message', function (event) {
  console.log('Message from server', event.data);
});
</script>

服务端nodeJs代码:

var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(message) {
    console.log('received: %s', message);
     ws.send("hello"+message);
  });
});

结果如图:
浏览器端:

clipboard.png

服务端:

clipboard.png

完美实现了跨域。

CORS(cross origin resource sharing 最经常使用)

老生常谈的CORS,优秀的文章已经很是多了,你们能够搜一下,很是重要,有机会我会专门写一篇文章来学习总结,在此就再也不详述了

最后

不得不说,这些方法仍是比较巧妙的,在此写下一篇文章来总结一下,感受本身面对跨域丝绝不慌啦。

Reference

前端跨域整理
正确面对跨域,别慌
浏览器的同源策略

相关文章
相关标签/搜索