跨域技术(上下完整版)

跨域技术(上)

跨域技术(上)包括 同源策略及其限制什么是跨域图像PingJSONPCORSWebSocket 等跨域技术。php

(一)同源策略及其限制

同源策略

点击查看浏览器同源策略官方又臭又长说法html

同源策略限制内容

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点获取
  • AJAX 请求的发送

(二)什么是跨域

所谓同源是指:协议,域名,端口都相同,就是同源;前端

协议,域名,端口号有一个不一样就是跨域nginx

跨域并非请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。 git

可能嵌入跨源的资源的标签

<script src="..."></script> | 标签嵌入跨域脚本。github

<link rel="stylesheet" href="..."> | 标签嵌入CSS。web

<img> | 嵌入图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG,...ajax

<video><audio> | 嵌入多媒体资源。json

<object>, <embed><applet> 的插件。segmentfault

<frame><iframe> | 载入的任何资源。站点可使用X-Frame-Options消息头来阻止这种形式的跨域交互。

@font-face 引入的字体 | 一些浏览器容许跨域字体(cross-origin fonts),一些须要同源字体(same-origin fonts)。

background: url(); 背景连接 | 嵌入的资源可能存在跨域

可能存在跨源的场景

  • 资源跳转: A连接跳转、重定向、表单提交
  • 资源嵌入: <link><script><img><frame>等dom标签
  • 脚本请求: ajax请求、dom和js对象的跨域操做等

(三)几种跨域技术的讨论

图像Ping

1.原理

  • 咱们知道,一个网页能够从任何网页中加载图像,不用担忧跨域不跨域。这也是在线广告跟踪浏览量的主要方式。
  • 能够动态地建立图像,使用它们的onloadonerror 事件处理程序来肯定是否接收到了响应。
  • 动态建立图像常常用于图像 Ping。图像 Ping 是与服务器进行简单、单向的跨域通讯的一种方式。

2.具体步骤

请求的数据是经过查询字符串形式发送的,而响应能够是任意内容,但一般是像素图或 204 响应。经过图像 Ping,浏览器得不到任何具体的数据,但经过侦听 load 和 error 事件,它能知道响应是何时接收到的。

let img = new Image(); //建立了一个 Image 的实例
// 将 onload 和 onerror 事件处理程序指定为同一个函数
img.onload = img.onerror = function(){ 
    alert("Done!"); 
}; 
// 请求中发送了一个 name 参数
img.src = "http://www.example.com/test?name=Nicholas";
复制代码

3.应用场景

图像 Ping 最经常使用于跟踪用户点击页面或动态广告曝光次数。

4.图像 Ping 的缺点

  • 一是只能发送 GET 请求,
  • 二是没法访问服务器的响应文本。所以,图像 Ping 只能用于浏览器与服务器间的单向通讯。

JSONP(JSON with padding 填充式JSON)

1.JSONP的做用、组成

  • JSONP可以让网页从别的地址(跨域的地址)那获取资料,即跨域读取数据
  • JSONP 由两部分组成:回调函数和数据。
    • 回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字通常是在请求中指定的。
    • 而数据就是传入回调函数中的JSON数据。

2.JSONP实现跨域访问的原理及优化

2.1 <script>标签的做用

  • 在同一界面中能够定义多个<script>标签,同一个界面中多个<script>标签中的数据能够相互访问
  • 能够经过<script>的src属性导入其它资源,经过src属性导入其它资源的本质就是将资源拷贝到<script>标签中
  • <script>的src属性不只能导入本地资源, 还能导入远程资源
  • 因为<script>的src属性没有同源限制, 因此能够经过<script>的src属性来请求跨域数据

2.2 JSONP原理

JSONP 是经过动态<script>元素来使用的,使用时能够为 src 属性指定一个跨域 URL。这里的<script>元素与<img>元素相似,都有能力不受限制地从其余域加载资源。由于 JSONP 是有效的 JavaScript代码,因此在请求完成后,即在 JSONP 响应加载到页面中之后,就会当即执行。

2.3 JSONP优化

2.3.1 为何要优化(存在的问题)?

  • 经过JSONP来获取跨域的数据,通常状况下服务器返回的都不会是一个变量,而是一个函数的调用
  • 当前服务器返回的函数调用名称若是是固定的,服务器返回函数叫什么名称,咱们本地就必须定义一个叫什么名称的函数
  • 因为<script>标签默认是同步,前面的<script>标签没有加载完数据,后面的<script>标签就不会被执行, 因此请求数据的<script>标签必须放到后面

2.3.2 解决方案:

  • 经过URL参数的方式来动态指定函数名称
  • 经过JS动态建立<script>标签,由于JS动态建立的<script>标签默认就是异步的, 不用等到前面的标签加载完就能够执行后面的<script>标签

2.3.3 实现方式

原生实现

<script>
    //jsonp回调方法,必定要写在jsonp请求前面
    function testjson(txt){
    	alert(txt);
    }
</script>

<script>
    //动态建立<script>标签
    let oScript = document.createElement("script");
    oScript.src = "http://127.0.0.1:80/jQuery/Ajax/20-jsonp.php?cb=test";
    document.body.appendChild(oScript);
</script>
复制代码

jQuery方式实现

jQuery中的Ajax能够设置为请求跨域的数据

  • dataType: "jsonp" | 设置请求方式为jsonp,告诉jQuery须要请求跨域的数据。
  • jsonp: "cb" | 告诉jQuery服务器在获取回调函数名称的时候须要用什么key来获取
  • jsonpCallback: "handleCallback" | 自定义回调函数名,告诉jQuery服务器在获取回调函数名称的时候回调函数的名称是什么
<script>
    $.ajax({
        url: "http://127.0.0.1:80/jQuery/Ajax/22-jsonp.php",
        data:{
            "name": "ghk",
            "age": 18
        },
        dataType: "jsonp",   // 设置请求方式为jsonp
        jsonp: "cb",  
        jsonpCallback: "handleCallback", // 自定义回调函数名
        success: function (msg) {
            console.log(msg);
        }
    });
</script>
复制代码

3.JSONP的优缺点

3.1 优势

与图像 Ping 相比,它的优势在于可以直接访问响应文本,支持在浏览器与服务器之间双向通讯。

3.2 缺点

  • JSONP 是从其余域中加载代码执行。若是其余域不安全,极可能会在响应中夹带一些恶意代码,而此时除了彻底放弃 JSONP 调用以外,没有办法追究。
  • 不能注册success、error等事件的监听,因此要肯定 JSONP 请求是否失败并不容易
  • JSONP 只支持get请求(JSONP的实现原理就是建立一个script标签,再把须要请求的api地址放到src里. 因此只能是get请求.)

CORS (Cross-origin resource sharing 跨域资源共享)

1.CORS是什么及原理

谁应该读这篇文章? 说实话,每一个人。

一句话总结:CORS是跨域的根本解决方法(处理跨域问题的标准作法),由浏览器自动完成。

CORS是一个W3C标准,它容许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS背后的基本思想:使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,仍是应该失败。

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

所以,实现CORS通讯的关键是服务器。只要服务器实现了CORS接口,就能够跨源通讯。

2.CORS的两种请求

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

只要同时知足如下两大条件,就属于简单请求。

(1) 请求方法是如下三种方法之一:

  • HEAD
  • GET
  • POST

(2) HTTP的头信息不超出如下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不一样时知足上面两个条件,就属于非简单请求。

浏览器对这两种请求的处理,是不同的。(阮老师这里有对这两种请求的详细解说)

3.实现

普通跨域请求:服务端设置Access-Control-Allow-Origin便可,前端无须设置;若要带cookie请求,先后端都须要设置。

在响应头上添加 Access-Control-Allow-Origin 属性,指定同源策略的地址。同源策略默认地址是网页的自己。只要浏览器检测到响应头带上了CORS,而且容许的源包括了本网站,那么就不会拦截请求响应。

原生实现

let xhr = new XMLHttpRequest();
xhr.withCredentials = true;// 前端设置是否带cookie
...
复制代码

jQuery Ajax

$.ajax({
    ...
    xhrFields: {
        withCredentials: true,  // 前端设置是否带cookie
    },
    crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
    ...
});
复制代码

4.CORS优缺点

CORS要求浏览器(>IE10)和服务器的同时支持,是跨域的根本解决方法,由浏览器自动完成。 优势在于功能更增强大支持各类HTTP Method,缺点是兼容性不如JSONP。

WebSocket

同源策略对 WebSocket 不适用,所以能够经过它打开到任何站点的链接。

1.WebSocket的由来

为了解决服务器向浏览器端及时推送消息的需求,提出了一些替代的、变通的解决方案,如iframe、AJAX,基本思路是浏览器端及时提交请求,服务器收到请求后进行阻塞,并保持链接,等待有消息须要发送时,及时向浏览器端进行响应推送。 然而,这些技术都是基于HTTP协议实现。从根本上讲,HTTP是半双工的协议,也就是说,在同一时刻信息只能单向传输。浏览器端向服务器发送请求(单向),而后服务器响应请求(单向)。这样,服务器只能被动的接收到请求信息后,再向浏览器端响应请求,而没法主动向客户端推送信息。

然而,随着人们对信息的实时性要求愈来愈高(如在线订票系统、即时通讯系统和股票交易系统等),当服务器数据发生变化的时候,须要主动、实时地向浏览器端发送消息,将最新的数据或事件通知给用户。显然,HTTP做为半双工的通讯协议并不能高效地支持上述需求功能的实现。咱们须要一种高效节能的全双工通讯机制来保证数据的实时传输。所以,WebSocket应运而生。

2.WebSocket的优势和应用场景

2.1 WebSocket的优势

WebSocket做为一种高效的全双工通讯机制,有以下优势

  • WebSocket经过一个单一的套接字在Web上进行操做,以最小的开销高效地提供了Web链接。相较于常常须要使用推送实时数据到浏览器端的轮询、长轮询及长链接来讲,这就极大地减小了没必要要的网络流量
  • 实现了浏览器与服务器的双向通道服务器和浏览器端能够主动的发送数据给对方
  • WebSocket减小了延迟,由于一旦创建起WebSocket链接,服务器能够在消息可用时发送它们,不用频繁建立TCP请求及销毁请求,减小网络带宽资源的占用,同时也节省服务器资源。
  • WebSocket减小了网络带宽资源的占用,由于无需频繁建立及销毁
  • WebSocket节省了服务器资源。由于一旦创建起WebSocket链接,服务器能够在后台数据更新时及时推送消息给客户端。

2.2 应用场景

经过WebSocket实现的浏览器后台传输技术,能够应用于如多媒体聊天、实时状态监控、股票行情、基于位置的应用等须要实时刷新的应用场景。

3.WebSocket协议通讯机制

WebSocket协议是独立的、基于TCP的协议。其本质是先经过HTTP/HTTPS协议进行握手后建立一个用于交换数据的TCP链接,此后服务器端与浏览器端经过此TCP链接进行实时通讯。

图所示为WebSocket的通讯模式:

浏览器端首先向服务器端发起一个HTTP请求。这个请求包含了一些附加头信息,是一个升级的HTTP请求。服务器端解析这些附加头信息后,产生应答信息返回给浏览器端。这样,浏览器端和服务器端之间的WebSocket链接就创建起来了。双方能够经过这个链接通道自由地传输数据,直到浏览器端或服务器端主动关闭该链接。

4.WebSocket协议通讯实现的相关技术

4.1 WebSocket构造函数

4.1.1 客户端要和服务器端创建WebSocket链接,只需使用构造函数创建一个WebSocket对象,而且为这个对象提供链接端口的URL地址便可。

let  ws = new  WebSocket("ws://127.0.0.1:8080/WebSocket/WebSocket")
复制代码

4.1.2 注意点

  • URL地址的字符串须要以“ws”或“wss”(加密通讯时使用)做为开头。若是URL地址有语法错误,那么构造函数将会抛出异常。

  • 必须给 WebSocket 构造函数传入绝对 URL。同源策略对 Web Sockets 不适用,所以能够经过它打开到任何站点的链接。至因而否会与某个域中的页面通讯,则彻底取决于服务器。

4.2 WebSocket的readyState属性

readyState 属性返回实例对象的当前状态,共有四种。

状态 含义
WebSocket.OPENING (0) 正在创建链接。
WebSocket.OPEN (1) 已经创建链接。
WebSocket.CLOSING (2) 正在关闭链接。
WebSocket.CLOSE (3) 已经关闭链接。

4.3 WebSocket事件

  • open 事件(WebSocket链接创建后触发该事件)
    一旦服务器响应了客户端的WebSocket链接请求,open事件就会被触发。而要监听open事件,只须要为其添加回调函数。

    ws.onopen = function(){
        setConnected(true));
    };
    复制代码
  • message 事件(当客户端接收到服务器的数据时触发该事件)
    当客户端接收到服务器发送的消息后,message事件触发,而且客户端可调用相应函数对所接收的消息进行处理。

    ws.onmessage= function(e){
        console.log("接收:"+e.data);
    }
    复制代码
  • error 事件(当通讯过程当中发生错误时触发该事件)
    error事件会在WebSocket链接出现故障时触发,用来处理异常事件。

    ws.onerror = function(e){
        console.log("WebSocket Error: ", e);
    };
    复制代码
  • close 事件(当链接关闭时触发该事件)。
    若是接收到error事件,能够预期很快会触发close事件。close事件在WebSocket链接关闭时触发,处理程序以下所示:

    ws.onclose = function(e){
        setConnected(false));
    };
    复制代码

    一旦链接关闭,客户端和服务器再也不接收或者发送消息。

4.4 WebSocket方法

WebSocket对象有两个方法即send()和close(),分别用来发送数据和关闭链接。

  • send() 方法
    WebSocket在客户端和服务器之间创建链接后,就能够调用send()方法来传输数据,语法格式以下所示:
    ws.send("message");
    复制代码
  • close() 方法
    使用close()方法,能够关闭WebSocket链接,语法格式以下所示:
    ws.close(code, reason);
    复制代码
    能够在close()方法传递两个可选参数:code(数字型的状态代码)和reason(一个文本字符串),用来讲明关闭链接的缘由。

5.WebSocket小结

有没有发现好用的东西、牛逼的东西总得要有点小缺陷才行,例如兼容问题,否则开发就没啥意思了(不是

JavaScript 中建立了 WebSocket 以后,会有一个 HTTP 请求发送到浏览器以发起链接。在取得服务器响应后,创建的链接会使用 HTTP 升级从 HTTP 协议交换为 Web Socket 协议。

也就是说,使用标准的 HTTP 服务器没法实现 WebSocket ,只有支持这种协议的专门服务器才能正常工做

目前支持WebSocket的服务器有不少,包括了Jetty七、Netty、mod pyWebSocket、Nodejs和Tomcat7等。在双向通道创建后,服务器端一样采用事件驱动的架构模式,监听事件、并触发相应方法函数来向客户端推送数据。


本文参考书籍:

《JavaScript高级程序设计》

本文参考连接:

浏览器的同源策略 MDN

HTTP访问控制(CORS) MDN

跨域资源共享 CORS 详解 阮一峰

WebSocket 教程 阮一峰

不要再问我跨域的问题了



跨域技术(下)

跨域技术(下)包括 iframe标签的使用document.domainwindow.namelocation.hashpostMessage等跨域技术。

iframe标签

域名地址的组成

iframe 标签

iframe是一种HTML标记,它会建立包含另一个文档的内联框架,经过iframe框架能够在当前页面中显示其余页面的信息。

将iframe的src属性设置为对另一个页面的链接请求,并在当前页面中经过JavaScript动态更新iframe的内容,就能够将服务器端的数据响应到客户端且而不会出现主页面一片空白,等待刷新的现象。

而且,仅刷新iframe框架而不是主页面,也减小了页面刷新的内容,这在必定程度上提升了页面刷新速度。

iframe 标签的使用

1.用iframe嵌套页面时,若是父页面要获取子页面里面的内容,可使用 contentWindow 或者contentDocument

子页面获取父页面使用window.parent

//父窗口
<iframe id="father" src="iframe.html"></iframe>
<script>
    console.log(iframe.contentWindow); //获取子页面里面的内容
    console.log(iframe.contentDocument);
</script>

//子窗口
<div id="son">内容</div>
<script>
  console.log(window.parent); //获取父页面内容
</script>
复制代码

2.页面间通讯,获取子页面的内容、cookie等

//父窗口
<iframe id="father" src="iframe.html"></iframe>
<script>
  let iframe = document.getElementById("father");
  iframe.onload = function(){
    let doc = iframe.contentWindow.document;
    console.log(doc.getElementById('son').innerHTML);//'内容'
    console.log(document.cookie);//'name=match'
  }
</script>

//子窗口
<div id="son">内容</div>
<script>
  document.cookie = 'name=match';
</script>
复制代码

document.domain + iframe(主域相同的跨域)

这种方式只适合主域名相同,子域名不一样的iframe跨域

经过修改document的domain属性,咱们能够在域和子域或者不一样的子域之间通讯。同域策略认为域和子域隶属于不一样的域,好比www.a.com和sub.a.com是不一样的域,这时,咱们没法在www.a.com下的页面中调用sub.a.com中定义的JavaScript方法。可是当咱们把它们document的domain属性都修改成a.com,浏览器就会认为它们处于同一个域下,那么咱们就能够互相调用对方的method来通讯了。

document.domain实现跨域核心

1.主域相同而二级域名不一样的状况下,两个文件分别加上 document.domain = "example.com";而后经过 a.html 文件建立一个 iframe,去控制 iframe 的 window,从而进行交互,这种方法只能解决主域相同而二级域名不一样的状况。

2.注意

使用document.domain容许子域安全访问其父域时,您须要设置document域在父域和子域中具备相同的值。这是必要的,即便这样作只是将父域设置回其原始值。不然可能会致使权限错误。这里都是a.com。

// 在www.a.com/a.html中:
<script>
    document.domain = 'a.com';
    let ifr = document.createElement('iframe');
    ifr.src =  'http://www.script.a.com/b.html';  
    ifr.display = none;
    document.body.appendChild(ifr);
    ifr.onload = function(){ 
        let doc = ifr.contentDocument || ifr.contentWindow.document;  
        ifr.onload = null;
};
</script>
复制代码
// 在www.script.a.com/b.html中:
<script>
    document.domain ='a.com';
    window.data = '传送的数据:1111';
</script>
复制代码

window.name + iframe

window.name 属性用于获取/设置窗口的名称。该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的全部的页面都是共享一个window.name的,每一个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的全部页面中的。window.name属性的神奇之处在于name值在不一样的页面(甚至不一样域名)加载后依旧存在(若是没修改则值不会变化),而且能够支持很是长的 name 值(2MB)。

当该window的location变化,而后从新加载,它的name属性能够依然保持不变。

在控制台输入:

页面跳转到百度时,name属性值不变

window.name实现跨域核心

能够将跨域请求用以下步骤解决:

  • 在 a.github.io/a.html 中建立 iframe 指向 b.github.io/b.html (页面会将自身的 window.name 附在 iframe 上)
  • 给 a.github.io/a.html 添加监听 iframe 的 onload 事件,在该事件中将 iframe 的 src 设置为本地域的代理文件(代理文件和a.html处于同一域下,能够相互通讯),同时能够传出 iframe 的 name 值
  • 获取数据后销毁 iframe,释放内存,同时也保证了安全

window.name 的优点在于巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操做。

location.hash + iframe

location.hash就是锚点值,又称为片断标识符(fragment identifier),指的是URL的#号后面的部分。

若是只是改变片断标识符,页面不会从新刷新 ,因此能够利用hash值来进行数据的传递,固然数据量是有限的。

location.hash实现跨域核心

1.假设 github.io 域名下 a.html 和 mouyuming.eu 域名下 b.html 存在跨域请求,那么利用 location.hash 的一个解决方案以下:

  • a.html 页面中建立一个隐藏的 iframe, src 指向 b.html,其中 src 中能够经过 hash 传入参数给 b.html
  • b.html 页面在处理完传入的 hash 后经过修改 a.html 的 hash 值达到将数据传送给 a.html 的目的
  • a.html 页面添加一个定时器,每隔必定时间判断自身的 location.hash 是否变化,以此响应处理

以上步骤中须要注意第二点:如何在 iframe 页面中修改 父亲页面的 hash 值。因为在 IE 和 Chrome 下,两个不一样域的页面是不容许 parent.location.hash 这样赋值的,因此对于这种状况,咱们须要在父亲页面域名下添加另外一个页面来实现跨域请求,具体以下:

  • 假设 a.html 中 iframe 引入了 b.html, 数据须要在这两个页面之间传递,且 c.html 是一个与 a.html 同源的页面
  • a.html 经过 iframe 将数据经过 hash 传给 b.html
  • b.html 经过 iframe 将数据经过 hash 传给 c.html
  • c.html 经过 parent.parent.location.hash 设置 a.html 的 hash 达到传递数据的目的

2.location.bash 方法的优缺点

优势在于能够解决域名彻底不一样的跨域请求,而且能够实现双向通信

而缺点则包括如下几点:

  • 利用这种方法传递的数据量受到 url 大小的限制,传递数据类型有限
  • 因为数据直接暴露在 url 中则存在安全问题
  • 若浏览器不支持 onhashchange 事件,则须要经过轮训来获知 url 的变化
  • 有些浏览器会在 hash 变化时产生历史记录,所以可能影响用户体验

window.postMessage

HTML5 为了跨域问题,引入了一个全新的 API:跨文档通讯 API(Cross-document messaging)。这个 API 为 window 对象新增了一个 window.postMessage 方法,容许跨窗口通讯,不论这两个窗口是否同源。

详细方法见MDN

图所示为postMessage的兼容性:

1.语法

  • 格式:otherWindow.postMessage(message, targetOrigin, [transfer]);

  • 参数:
    otherWindow | 其余窗口的一个引用,好比iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。

    message | 将要发送到其余 window的数据;是具体的信息内容。

    targetOrigin | 接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也能够设为*,表示不限制域名,向全部窗口发送。

若是你明确的知道消息应该发送到哪一个窗口,那么请始终提供一个有确切值的targetOrigin,而不是*。不提供确切的目标将致使数据泄露到任何对数据感兴趣的恶意站点。

2.父窗口和子窗口均可以经过message事件,监听对方的消息。

message 的属性有:

data | 从其余 window 中传递过来的对象。(消息内容)

origin | 调用 postMessage 时消息发送方窗口的 origin。(消息发向的网址)

source | 对发送消息的窗口对象的引用。(发送消息的窗口)

//发送页面
<body>
<iframe id="myIframe" src="http://localhost:3000/user/reg"></iframe>
<input type="button" value="点我" onclick="test()">
<script>
   function test() {
      let frm = document.getElementById("myIframe");
      frm.contentWindow.postMessage("跨域请求信息","http://localhost:3000");
    }
</script>
</body>

// 接收页面
<script>
    //接收信息页面 http://localhost:3000/parent.html
    window.addEventListener("message",function (e) {
       console.log(e.data);
    },false);
</script>
复制代码

其余方式跨域

NodeJS中间件代理跨域、nginx反向代理中设置、flash等第三方插件等

等我会了这几个再填这三种方式的坑

本文参考连接

iFrame跨域的方式

跨域资源共享的10种方式

前端跨域的几种方式(超详细,值得收藏)

window.postMessage MDN

相关文章
相关标签/搜索