对于web开发来讲,由于浏览器的同源策略,我们需要经常使用一些hack的方法去跨域获取资源,但是hack的方法总归是hack。直到W3C出了一个标准-CORS-”跨域资源共享”(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
首先来说 CORS 需要浏览器和服务端同时支持的,对于兼容性来说主要是ie10+,其它现代浏览器都是支持的。
http://caniuse.com/#feat=cors
使用 CORS 跨域的时候其实和普通的 ajax 过程是一样的,只是浏览器在发现这是一个跨域请求的时候会自动帮我们处理一些事,比如验证等等,所以说只要服务端提供支持,前端是不需要做额外的事情的。
CORS 的请求分两种,这也是浏览器为了安全做的一些处理,不同情况下浏览器执行的操作也是不一样的,主要分为两种请求,当然这一切我们是不需要做额外处理的,浏览器会自动处理的。
只要同时满足以下两大条件,就属于简单请求。
1) 请求方法是以下三种方法中的一个:HEADGETPOST2)HTTP的头信息不超出以下几种字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
对于简单的跨域请求,浏览器会自动在请求的头信息加上 Origin 字段,表示本次请求来自哪个源(协议 + 域名 + 端口),服务端会获取到这个值,然后判断是否同意这次请求并返回。
// 请求GET /cors HTTP/1.1Origin: http://api.qiutc.meHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...
如果服务端许可本次请求,就会在返回的头信息多出几个字段:
// 返回Access-Control-Allow-Origin: http://api.qiutc.meAccess-Control-Allow-Credentials: trueAccess-Control-Expose-Headers: InfoContent-Type: text/html; charset=utf-8
这三个带有 Access-Control 开头的字段分别表示:
当然我们为了防止接口被乱调用,需要限制源,对于不允许的源,服务端还是会返回一个正常的HTTP回应,但是不会带上 Access-Control-Allow-Origin 字段,浏览器发现这个跨域请求的返回头信息没有该字段,就会抛出一个错误,会被 XMLHttpRequest 的 onerror 回调捕获到。
这种错误无法通过 HTTP 状态码判断,因为回应的状态码有可能是200
出了简单请求以外的CORS请求。非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
预检请求的发送请求:
OPTIONS /cors HTTP/1.1Origin: http://api.qiutc.meAccess-Control-Request-Method: PUTAccess-Control-Request-Headers: X-Custom-HeaderHost: api.qiutc.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...
“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,”预检”请求的头信息包括两个特殊字段。
Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。
预检请求的返回:
HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:15:39 GMTServer: Apache/2.0.61 (Unix)Access-Control-Allow-Origin: http://api.qiutc.meAccess-Control-Allow-Methods: GET, POST, PUTAccess-Control-Allow-Headers: X-Custom-HeaderContent-Type: text/html; charset=utf-8Content-Encoding: gzipContent-Length: 0Keep-Alive: timeout=2, max=100Connection: Keep-AliveContent-Type: text/plain
Access-Control-Allow-Methods必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。
参考: 《跨域资源共享 CORS 详解》 http://www.ruanyifeng.com/blog/2016/04/cors.html
)
阮大神的文章,复制粘贴了不少。
jsonp = json + padding
其实对于常用性来说,jsonp应该是使用最经常的一种跨域方式了,他不受浏览器兼容性的限制。但是他也有他的局限性,只能发送 GET 请求,需要服务端和前端规定好,写法丑陋。
它的原理在于浏览器请求 script 资源不受同源策略限制,并且请求到 script 资源后立即执行。
主要做法是这样的:
在浏览器端:
首先全局注册一个callback回调函数,记住这个函数名字(比如:resolveJson),这个函数接受一个参数,参数是期望的到的服务端返回数据,函数的具体内容是处理这个数据。
然后动态生成一个 script 标签,src 为:请求资源的地址+获取函数的字段名+回调函数名称,这里的获取函数的字段名是要和服务端约定好的,是为了让服务端拿到回调函数名称。(如: www.qiute.com?callbackName=resolveJson )。
function resolveJosn(result) { console.log(result.name);}var jsonpScript= document.createElement("script");jsonpScript.type = "text/javascript";jsonpScript.src = "http://www.qiute.com?callbackName=resolveJson";document.getElementsByTagName("head")[0].appendChild(jsonpScript);
服务端
在接受到浏览器端 script 的请求之后,从url的query的callbackName获取到回调函数的名字,例子中是 resolveJson 。
然后动态生成一段javascript片段去给这个函数传入参数执行这个函数。比如:
resolveJson({name: 'qiutc'});
执行服务端返回这个 script 之后,浏览器端获取到 script 资源,然后会立即执行这个 javascript,也就是上面那个片段。这样就能根据之前写好的回调函数处理这些数据了。
在一些第三方库往往都会封装jsonp的操作,比如 jQuery 的 $.getJSON 。
一个页面框架(iframe/frame)之间(父子或同辈),是能够获取到彼此的window对象的,但是这个 window 不能拿到方法和属性(尼玛这有什么用,甩脸)。
// 当前页面域名 http://blog.qiutc.me/a.html<script>function onLoad() { var iframe =document.getElementById('iframe'); var iframeWindow = iframe.contentWindow; // 这里可以获取 iframe 里面 window 对象,但是几乎没用 var doc = iframeWindow.document; // 获取不到}</script><iframe src="http://www.qiutc.me/b.html" onload="onLoad()"</iframe>
这个时候, document.domain 就可以派上用场了,我们只要把 http://blog.qiutc.me/a.html 和 http://www.qiutc.me/b.html 这两个页面的 document.domain 都设成相同的域名就可以了。
前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致。
但要注意的是, document.domain 的设置是有限制的,我们只能把 document.domain 设置成自身或更高一级的父域,且主域必须相同。例如: a.b.example.com 中某个文档的 document.domain 可以设成 a.b.example.com 、 b.example.com 、 example.com 中的任意一个,但是不可以设成 c.a.b.example.com ,因为这是当前域的子域,也不可以设成 baidu.com ,因为主域已经不相同了。
这样我们就可以通过js访问到iframe中的各种属性和对象了。
// 主页面:http://blog.qiutc.me/a.html<script>document.domain = 'qiutc.me';function onLoad() { var iframe =document.getElementById('iframe'); var iframeWindow = iframe.contentWindow; // 这里可以获取 iframe 里面 window 对象并且能得到方法和属性 var doc = iframeWindow.document; // 获取到}</script><iframe src="http://www.qiutc.me/b.html" onload="onLoad()"</iframe>
// iframe 里面的页面<script>document.domain = 'qiutc.me';</script>
window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个 window.name 的,每个页面对 window.name 都有读写的权限, window.name 是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
比如有一个 www.qiutc.me/a.html 页面,需要通过a.html页面里的js来获取另一个位于不同域上的页面 www.qiutc.com/data.html 里的数据。
data.html页面里的代码很简单,就是给当前的window.name设置一个a.html页面想要得到的数据值。data.html里的代码:
<script>window.name = '我是被期望得到的数据';</script>
那么在 a.html 页面中,我们怎么把 data.html 页面载入进来呢?显然我们不能直接在 a.html 页面中通过改变 window.location 来载入data.html页面(这简直扯蛋)因为我们想要即使 a.html 页面不跳转也能得到 data.html 里的数据。
答案就是在 a.html 页面中使用一个隐藏的 iframe 来充当一个中间人角色,由 iframe 去获取 data.html 的数据,然后 a.html 再去得到 iframe 获取到的数据。
充当中间人的 iframe 想要获取到 data.html 的通过 window.name 设置的数据,只需要把这个 iframe 的src设为 www.qiutc.com/data.html 就行了。然后 a.html 想要得到 iframe 所获取到的数据,也就是想要得到 iframe 的 window.name 的值,还必须把这个 iframe 的 src 设成跟 a.html 页面同一个域才行,不然根据前面讲的同源策略, a.html 是不能访问到 iframe 里 的window.name 属性的。这就是整个跨域过程。
// a.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title> <script> function getData() { var iframe =document.getElementById('iframe'); iframe.onload = function() { var data = iframe.contentWindow.name; // 得到 } iframe.src = 'b.html'; // 这里b和a同源 } </script></head><body> <iframe src="http://www.qiutc.com/data.html" style="display:none" onload="getData()"</iframe></body></html>
window.postMessage(message, targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源。兼容性:
http://caniuse.com/#search=postMessage
调用postMessage方法的window对象是指要接收消息的那一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 * 。
需要接收消息的window对象,可是通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。
上面所说的向其他window对象发送消息,其实就是指一个页面有几个框架的那种情况,因为每一个框架都有一个window对象。在讨论第种方法的时候,我们说过,不同域的框架间是可以获取到对方的window对象的,虽然没什么用,但是有一个方法是可用的- window.postMessage 。下面看一个简单的示例,有两个页面:
// 主页面 blog.qiutc.com<script>function onLoad() { var iframe =document.getElementById('iframe'); var iframeWindow = iframe.contentWindow; iframeWindow.postMessage("I'm message from main page.");}</script><iframe src="http://www.qiutc.me/b.html" onload="onLoad()"</iframe>
// b 页面<script>window.onmessage = function(e) { e = e || event; console.log(e.data);}</script>
一种用 CSS 跨域传输文本的方案。
优点:相比 JSONP 更为安全,不需要执行跨站脚本。
缺点:没有 JSONP 适配广,CSST 依赖支持 CSS3 的浏览器。
原理:通过读取 CSS3 content 属性获取传送内容。
具体可以参考: CSST (CSS Text Transformation)
flash 嘛,这个东西注定灭亡,不想说了。。。