跨域请求
同源策略与跨域请求
同源策略(Same Origin Policy),即 URL 由协议、域名、端口和路径组成,如果两个 URL 的协议、域名和端口相同,则表示他们同源。浏览器的同源策略,限制了来自不同源的 document
或脚本,对当前 document
读取或设置某些属性,即从一个域上加载的脚本不允许访问另外一个域的文档属性。比如一个恶意网站的页面通过 iframe 嵌入了银行的登录页面(二者不同源),如果没有同源限制,恶意网页上的 JavaScript 脚本就可以在用户登录银行的时候获取用户名和密码。同时,同源策略还能限制我们随意请求资源,避免大量的恶意请求。
常用的跨域请求方式分为客户端跨域、服务端跨域与 CORS,服务端跨域即使用类似代理的方式,把访问其它域的请求替换为本域的请求,本域的请求是服务器端的动态脚本负责转发实际的请求。目前主流的浏览器皆支持 CORS 协议,其会在进行以下几种典型的跨域请求时被触发:
- 不同的域名,譬如 example.com 的网页请求 api.com
- 不同的子域名,譬如 example.com 请求 api.example.com
- 不同的端口,譬如 example.com 请求 example.com:3001
- 不同的协议,譬如 https://example.com 请求 http://example.com
客户端跨域
表单 POST 跨域提交
JSONP
JSONP 是较为常用的一种跨域方式,不受到浏览器兼容性的限制,但是因为它只能以 GET 动词进行请求,这样就破坏了标准的 REST 风格,比较丑陋。JSONP 本质上是利用<script>
标签的跨域能力实现跨域数据的访问,请求动态生成的 JavaScript 脚本同时带一个 callback 函数名作为参数。其中 callback 函数本地文档的 JavaScript 函数,服务器端动态生成的脚本会产生数据,并在代码中以产生的数据为参数调用 callback 函数。当这段脚本加载到本地文档时,callback 函数就被调用。(1)浏览器端构造请求地址
function resolveJson(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 标签的请求地址为:请求资源的地址+获取函数的字段名+回调函数名称,这里的获取函数的字段名是需要和服务端提前约定好,譬如 jQuery 中默认的获取函数名就是callback
。而resolveJson
是我们默认注册的回调函数,注意,该函数名需要全局唯一,该函数接收服务端返回的数据作为参数,而函数内容就是对于该参数的处理。(2)服务端构造返回值在接受到浏览器端 script 的请求之后,从 url 的 query 的 callbackName 获取到回调函数的名字,例子中是resolveJson
。然后动态生成一段 javascript 片段去给这个函数传入参数执行这个函数。比如:
resolveJson({ name: "qiutc" });
(3)客户端以脚本方式执行服务端返回值服务端返回这个 script 之后,浏览器端获取到 script 资源,然后会立即执行这个 javascript,也就是上面那个片段。这样就能根据之前写好的回调函数处理这些数据了。
## postMessage
window.postMessage 是一个用于安全的使用跨源通信的方法。通常,不同页面上的脚本当且仅当执行它们的页面所处的位置使用相同的协议(通常都是 http)、相同的端口(http 默认使用 80 端口)和相同的主机(两个页面的 document.domain 的值相同)只在这种情况下被允许互相访问。而 window.postMessage 提供了一个受控的机制来安全地绕过这一限制。其函数原型如下:
windowObj.postMessage(message, targetOrigin);
windowObj
: 接受消息的 Window 对象。message
: 在最新的浏览器中可以是对象。targetOrigin
: 目标的源,*
表示任意。
调用 postMessage 方法的 window 对象是指要接收消息的那一个 window 对象,该方法的第一个参数 message 为要发送的消息,类型只能为字符串;第二个参数 targetOrigin 用来限定接收消息的那个 window 对象所在的域,如果不想限定域,可以使用通配符 *
。需要接收消息的 window 对象,可是通过监听自身的 message 事件来获取传过来的消息,消息内容储存在该事件对象的 data 属性中。上面所说的向其他 window 对象发送消息,其实就是指一个页面有几个框架的那种情况,因为每一个框架都有一个 window 对象。
//在主页面中获取子页面的句柄
var iframe = document.getElementById("iframe");
var iframeWindow = iframe.contentWindow;
//向子页面发送消息
iframeWindow.postMessage("I'm message from main page.");
//在子页面中监听获取消息
window.onmessage = function (e) {
e = e || event;
console.log(e.data);
};