热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

js实现跨域(jsonp,iframe+window.name,iframe+window.domain,iframe+window.postMessage)

一、浏览器同源策略首先我们需要了解一下浏览器的同源策略,关于同源策略可以仔细看看知乎上的一个解释。传送门总之:同协议,domain(或ip),同端口视为同一个域,一个域内的脚本仅仅

一、浏览器同源策略

首先我们需要了解一下浏览器的同源策略,关于同源策略可以仔细看看知乎上的一个解释。传送门

总之:同协议,domain(或ip),同端口视为同一个域,一个域内的脚本仅仅具有本域内的权限,可以理解为本域脚本只能读写本域内的资源,而无法访问其它域的资源。这种安全限制称为同源策略。

( 现代浏览器在安全性和可用性之间选择了一个平衡点。在遵循同源策略的基础上,选择性地为同源策略"开放了后门"。 例如img script style等标签,都允许垮域引用资源。)

下表给出了相对 http://store.company.com/dir/page.html 同源检测的示例:

URL结果原因
http://store.company.com/dir2/other.html 成功  
http://store.company.com/dir/inner/another.html 成功  
https://store.company.com/secure.html 失败 协议不同
http://store.company.com:81/dir/etc.html 失败 端口不同(默认80)
http://news.company.com/dir/other.html 失败 主机名不同

 

 

 

 

 

 

由于浏览器同源策略的限制,让我们无法通过js直接在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架(iframe)中的数据。

二、jsonp实现跨域请求数据

在Javascript中,我们不能直接用ajax请求不同域上的数据。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。

本地测试利用PHP内置了的Web 服务器来模拟两个端口不同的两个域,现在在web_test目录下有9000和9001两个目录,分别进入两个目录执行

web_test/9000: php -S 127.0.0.1:9000
web_test/9001: php -S 127.0.0.1:9001

执行后:

技术分享 技术分享

这时候开启了两个本地不同端口的服务器,现在在两个目录下的文件就是在两个不同域。

 在9001目录下jsonp_test.html中

DOCTYPE html>
<html>
<head>
    <title>jsonp-testtitle>
head>
<body>
    <script type="text/Javascript">
        function callback_data (data) {
            console.log(data);
        }
    script>
    <script type="text/Javascript" src="http://127.0.0.1:9000/jsonp.php?callback=callback_data">script>
body>
html>

可以看到我们在向9000目录下的jsonp.php文件获取数据时,地址后面跟了一个callback参数(一般的就是用callback这个参数名,你也可以用其他的参数名代替)。

如果你要获取数据的页面是你不能控制的,那你只能根据它所提供的接口格式进行获取。

因为我们的type规定是当成是一个Javascript文件来引入的,所以php文件返回的应该是一个可执行的js文件。

 在9000目录下jsonp.php中

php
    $callback = $_GET[‘callback‘];  // 获取回调函数名
    $arr = array("name" => "alsy", "age" => "20"); // 要请求的数据
    echo $callback."(". json_encode($arr) .");"; // 输出
?>

页面输出就是这样的:

callback_data({"name":"alsy","age":"20"}); //执行url参数中指定的函数,同时把我们需要的json数据作为参数传入

 这样我们浏览器中输入http://127.0.0.1:9001/jsonp_test.html,控制台打印出:

技术分享

这样我们就获取到不同域中返回的数据了,同时jsonp的原理也就清楚了:

通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,同时把我们需要的json数据作为参数传入。所以jsonp是需要服务器端和客户端相互配合的。

知道jsonp跨域的原理后我们就可以用js动态生成script标签来进行跨域操作了,而不用特意的手动的书写那些script标签。比如jQuery封装的方法就能很方便的来进行jsonp操作了。

9001目录下的html中:

//$.getJSON()方法跨域请求
$.getJSON("http://127.0.0.1:9000/jsonp.php?callback=?", function(data){
    console.log(data);
});

原理是一样的,只不过我们不需要手动的插入script标签以及定义回掉函数。jQuery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。

从请求的url和响应的数据就可以很明显的看出来了:

技术分享

技术分享

这里的 jQuery214036133305518887937_1462698255551 就是一个临时代理函数。

$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。

另外jsonp是无法post数据的,尽管jQuery.getJSON(url, [data], [callback]); 提供data参数让你可以携带数据发起请求,但这样是以get方式传递的。比如:

9001目录下的html中:

//$.getJSON()方法
$.getJSON("http://127.0.0.1:9000/jsonp.php?callback=?", {u:‘abc‘, p: ‘123‘}, function(jsonData){
    console.log(jsonData);
});

技术分享

或者是调用$.ajax()方法指定type为post,它还是会转成get方式请求。

9001目录下的html中:

$.ajax({
type: ‘post‘,
url: "http://127.0.0.1:9000/jsonp_post.php",
crossDomain: true,
data: {u: ‘alsy‘, age: 20},
dataType: "jsonp",
success: function(r){
console.log(r);
}
});

技术分享

以get形式的话是可以携带少量数据,但是数据量一大就不行了。

网上找到一种方法,直接调用ajax方法,设置crossDomain(跨域): true,服务器响应头设置

    header(‘Access-Control-Allow-Origin: *‘);
    header(‘Access-Control-Allow-Methods: POST‘);
    header(‘Access-Control-Max-Age: 1000‘);

比如:

9001目录下的一个html文件:

$.ajax({
    type: ‘post‘,
    url: "http://127.0.0.1:9000/jsonp_post.php",
    crossDomain: true,
    data: {u: ‘alsy‘, age: 20},
    dataType: "json",
    success: function(r){
        console.log(r);
    }
});

9000目录下的php文件:

php
    header(‘Access-Control-Allow-Origin: *‘);
    header(‘Access-Control-Allow-Methods: POST‘);
    header(‘Access-Control-Max-Age: 1000‘);
    if($_POST){
        $arr = array(‘name‘ => $_POST[‘u‘], ‘age‘ => $_POST[‘age‘]);
        echo json_encode($arr);
    } else {
        echo json_encode([]);
    }
?>

浏览器显示:

技术分享

技术分享

 这样也是可以实现跨域post数据的,当然这种方式就不是jsonp的实现方式了。

三、iframe+window.domain 实现跨子域

 现在9001目录下有一个iframe-domain.html文件:

doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>127.0.0.1:9001 -- iframe-domaintitle>
head>
<body>
<h1>127.0.0.1:9001/iframe-domain.htmlh1>
<script>  
function test(){ var obj = document.getElementById("iframe").contentWindow; //获取window对象 console.log(obj); } script> <iframe id="iframe" src="http://127.0.0.1:9000/domain.html" onload="test()">iframe> body> html>

在这个页面中存在一个不同域的框架(iframe),而iframe载入的页面是和目标域在同一个域的,是能够向目标域发起ajax请求获取数据的。

那么就想能不能控制这个iframe让它去发起ajax请求,但在同源策略下,不同域的框架之间也是不能够进行js的交互。

虽然不同的框架之间(父子或同辈),是能够获取到彼此的window对象的,但是却不能获取到的window对象的属性和方法(html5中的postMessage方法是一个例外,还有些浏览器比如ie6也可以使用top、parent等少数几个属性),总之就是获取到了一个无用的window对象。

在这个时候,document.domain就派上用场了,我们只要两个域的页面的document.domain设置成一样了,这个例子中由于端口不同,两边的document.domain也要重新设置成"127.0.0.1",才能正常通信。

要注意的是,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,因为主域已经不相同了。

通过设置document.domain为相同,现在已经能够控制iframe载入的页面进行ajax操作了。

9001目录下的iframe-domain.html文件改为:

doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>127.0.0.1:9001 -- iframe-domaintitle>
head>
<body>
<h1>127.0.0.1:9001/iframe-domain.htmlh1>
<script>
    document.domain = 127.0.0.1; //设置domain
    function test(){
        var obj = document.getElementById("iframe").contentWindow;
        console.log(obj);
        obj.getData(http://127.0.0.1:9000/json_domain.php, {u: "alsy-domain", age: "20"}, function(r){
            console.log(  eval("("+ r +")") );
        });
    }
script>
<iframe id="iframe" src="http://127.0.0.1:9000/domain.html" onload="test()">iframe>
body>
html>

9000目录下有一个domain.html,和一个json_domain.php文件:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>domaintitle>
head>
<body>
<h1>127.0.0.1/9000/domain.htmlh1>
<script>
    window.onload = function(){
        document.domain = 127.0.0.1; //设置domain
        window.getData =  function(url, data, cb) {
            var xhr = null;
            if (window.XMLHttpRequest) {
                xhr = new XMLHttpRequest();
            } else {
                xhr = new ActiveXObject("Microsoft.XMLHTTP");
            }
            xhr.open(POST, url, true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    cb(xhr.responseText);
                }
            }
            xhr.send(data);
        }
    }
script>
body>
html>
php
    $str = file_get_contents(‘php://input‘);
    echo json_encode($str);
?>

浏览器打印:

技术分享

这样就可以实现跨域,当然你也可以动态创建这么一个iframe,获取完数据后再销毁。

四、iframe+window.name 实现跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限。(window.name的值只能是字符串的形式,这个字符串的大小最大能允许2M左右甚至更大的一个容量,具体取决于不同的浏览器,一般够用了。)

9001目录下的iframe-window.name.html:

doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>127.0.0.1:9001 -- iframe-window.nametitle>
head>
<body>
<h1>127.0.0.1:9001/iframe-window.name.htmlh1>
<script>
    function test(){
        var obj = document.getElementById("iframe");
        obj.onload = function(){
            var message = obj.contentWindow.name;
            console.log(message);
        }
        obj.src = "http://127.0.0.1:9001/a.html";
    }
script>
<iframe id="iframe" src="http://127.0.0.1:9000/window.name.html" onload="test()">iframe>
body>
html>

9000目录下的window.name.html:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>window.nametitle>
head>
<body>
<h1>127.0.0.1/9000/window.name.htmlh1>
<script>
    //todo
    window.name = "This is message!";
script>
body>
html>

浏览器输出:

技术分享

整个跨域的流程就是:现在9000/window.name.html中通过一些操作将数据存入window.name中了,而9001/iframe-window.name.html想要获取到window.name的值就需要依靠iframe作为中间代理,首先把iframe的src设置成http://127.0.0.1:9000/window.name.html,这样就相当于要获取iframe的window.name,而要想获取到iframe中的window.name,就需要把iframe的src设置成当前域的一个页面地址"http://127.0.0.1:9001/a.html",不然根据前面讲的同源策略,window.name.html是不能访问到iframe里的window.name属性的。

五、iframe+window.postMessage 实现跨域

html5炫酷的API之一:跨文档消息传输。高级浏览器Internet Explorer 8+, chrome,Firefox , Opera  和 Safari 都将支持这个功能。这个功能实现也非常简单主要包括接受信息的"message"事件和发送消息的"postMessage"方法。

发送消息的"postMessage"方法:

向外界窗口发送消息:

otherWindow.postMessage(message, targetOrigin);

otherWindow:  指目标窗口,也就是给哪个window发消息。

message: 要发送的消息,类型为 String、Object (IE8、9 不支持)

targetOrigin: 是限定消息接收范围,不限制请使用 ‘*‘

接受信息的"message"事件

var Onmessage= function (event) {
    var data = event.data;
    var origin = event.origin;
    //do someing
};
if (typeof window.addEventListener != ‘undefined‘) {
    window.addEventListener(‘message‘, onmessage, false);
} else if (typeof window.attachEvent != ‘undefined‘) {
    //for ie
    window.attachEvent(‘onmessage‘, onmessage);
}

回调函数第一个参数接收 event 对象,有三个常用属性:

  • data:  消息
  • origin:  消息来源地址
  • source:  源 DOMWindow 对象

 9000目录下的 postmessage.html:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>postmessagetitle>
head>
<body>
<h1>127.0.0.1/9000/postmessage.htmlh1>
<script>
    window.onload = function () {
        if (typeof window.postMessage === undefined) {
            alert("浏览器不支持postMessage!");
        } else {
            window.top.postMessage({u: "alsy"}, "http://127.0.0.1:9001");
        }
    }
script>
body>
html>

 9001目录下的 iframe-postmessage.html:

doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>127.0.0.1:9001 -- iframe-postmessagetitle>
head>
<body>
<h1>127.0.0.1:9001/iframe-postmessage.htmlh1>
<script>
    function test(){
        if (typeof window.postMessage === undefined) {
            alert("浏览器不支持postMessage!");
        } else {
            window.addEventListener("message", function(e){
                if (e.origin == "http://127.0.0.1:9000") { //只接收指定的源发来的消息
                    console.log(e.data);
                };
            }, false);
        }
    }
script>
<iframe id="iframe" src="http://127.0.0.1:9000/postmessage.html" onload="test()">iframe>
body>
html>

浏览器打印:

技术分享

 六、说明

参考自 http://www.cnblogs.com/2050/p/3191744.html

如有错误或不同观点请指出,共同交流。

完整代码:传送门

技术分享
技术分享
技术分享

js实现跨域(jsonp, iframe+window.name, iframe+window.domain, iframe+window.postMessage)


推荐阅读
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
  • position属性absolute与relative的区别和用法详解
    本文详细解读了CSS中的position属性absolute和relative的区别和用法。通过解释绝对定位和相对定位的含义,以及配合TOP、RIGHT、BOTTOM、LEFT进行定位的方式,说明了它们的特性和能够实现的效果。同时指出了在网页居中时使用Absolute可能会出错的原因,即以浏览器左上角为原始点进行定位,不会随着分辨率的变化而变化位置。最后总结了一些使用这两个属性的技巧。 ... [详细]
author-avatar
cecillalurw_689
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有