您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
js中各种跨域问题实战小结
 
作者:艺璇 来源:博客园 发布于 2015-03-04
  2789  次浏览      14
 

什么是跨域?为什么要实现跨域呢?

这是因为JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。也就是说只能访问同一个域中的资源。我觉得这就有必要了解下javascript中的同源策略是怎么回事了:javascript的同源策略 。这里更加细致详细的总结了为什么要跨域:javascript跨域之 什么是跨域?为什么跨域?

于是当我们想某些特定的功能的时候,实现合理的跨域请求就显得比较重要了。我努力通过自己动手,自己模拟环境来切实的尝试跨域是怎么回事。

javascript中的原生Ajax对象XMLHTTPRequest

先看原生ajax的实现代码:

var xhr = createXHR();//for IE封装一下浏览器不同的ajax对象
xhr.onload = function(event){ //确保接收到适当的响应
if((xhr.status >=200&&xhr.status<300)||xhr.status == 304){
alert(xhr.responseText);
}else{
alert('error:'+xhr.status);
}
}
xhr.open('get','**.php',true);//open方法的参数open(get/post,url,是否发送异步请求)
xhr.send(null);//open只是启动一个请求,并未发送,调用send()才会发送请求

我们知道想上面这样就可以发送ajax请求啦,得到的响应数据会自动填充xhr对象的属性,我们就可以去访问啦。

但是,就是有上面说到的限制,只能想同一个域中使用相同端口和协议的URL发送请求。然后就是解决跨域的问题了。

好在有个东西叫CORS(跨源资源共享),它背后的思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。

实现方法:

1.发送get/post请求时,给它添加一个额外的Origin头部,其中包含请求页面的源信息(协议,域名和端口),以便服务器根据这个头部信息来决定是否响应,像这样的:

Origin:http://www.nczonline.net

2.如果服务器认为这个请求可以接受,就在头部发回相同的源信息:

Access-Control-Allow-Origin:http://www.nczonline.net

如果没有这个头部或者源信息不匹配,那么服务器就会驳回请求,正常的情况下浏览器会处理请求。

深入研究CORS:HTTP access control (CORS)

另外,说到HTTP头部相关,我觉得也有必要了解下通常的Request Headers和Response Headers通常都有哪些参数,它们会告诉我们很多信息的:

之所以想到了解,是因为记得看过这篇文章,小胡子利用了响应头部的Date来实现了个倒计时:利用XMLHttpRequest响应头的Date实现倒计时

JSONP(双向)

1、什么是JSONP:

jsonp(json with padding),jsonp与json看起来差不多,只不过jsonp是被包含在函数调用中的json。jsonp由两部分组成,一部分是回调函数,一部分是数据。回调函数就是当响应到来时应该在页面中调用的函数,数据就是传入回调函数中的json数据。他的优点是:第一,他能直接访问响应文本;第二,jsonp支持在浏览器与服务器之间的双向通信。但同时也存在缺点:它无法保证加载的来自其他域的代码是安全的,还有就是无法判断jsonp的请求是否失败。

2、使用方法:

动态创建<script>节点:

function handleResponse(response){
         alert("你的ip地址:"+response.ip+",ip地址所在的城市:"+response.city+",所在省份:"+response.region_name);

      }

      var script = document.createElement('script');
      script.src = 'http://freegeoip.net/json/?callback=handleResponse';/*http://freegeoip.net是一个jsonp地理定位服务*/ 
document.body.insertBefore(script,document.body.firstChild);

但是如何判断script节点加载完毕是个问题:ie只能通过script的readystatechange属性,其他浏览器支持script的load事件。

如果用了jquery的话,就不用写上面那些代码了,jquery做了很多工作,调用$.ajax()方法的时候,如果想使用jsonp这种方法,封装参数即可了。

图像ping(单向)

1、什么是图像ping:

图像ping是与服务器进行简单、单向的跨域通信的一种方式,请求的数据是通过查询字符串的形式发送的,而相应可以是任意内容,但通常是像素图或204相应(No Content)。 图像ping有两个主要缺点:首先就是只能发送get请求,其次就是无法访问服务器的响应文本。

2、使用方法:

var img = new Image();
img.onload = img.onerror = function(){
alert("done!");
};
img.src = "https://raw.githubusercontent.com/zhangmengxue/Todo-List/master/me.jpg";
document.body.insertBefore(img,document.body.firstChild);

然后页面上就可以显示我放在我的github上某个地方的照片啦。

与<img>类似的可以跨域内嵌资源的还有:

(1)<script src=""></script>标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。上面jsonp也用到了呢。

(2) <link src="">标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的Content-Type消息头。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari (跳至CVE-2010-0051)部分 和 Opera。

(3)<video> 和 <audio>嵌入多媒体资源。

(4)<object>, <embed> 和 <applet>的插件。

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

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

document.domain+iframe实现跨域

首先要知道,只是在页面上通过<iframe>载入了资源,是不能与他交互的,像我的博客右边的那个About中的那个Github的Fllow一样的,只能去访问。那我们想要实现交互的话,对于主域相同而子域不同的情景,就可以借助于document.domain来实现。

www.one.com上的a.html:

document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://script.a.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument || ifr.contentWindow.document;
// 在这里操纵b.html
alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
};

w3w.one.com上的b.html:

document.domain = 'a.com';

这种方式就可以实现a页面和b页面交互了。适用于{www.kuqin.com, kuqin.com, script.kuqin.com, css.kuqin.com}这样的域名下的页面间,这里默认他们使用相同的协议和端口。

备注:某一页面的domain默认等于window.location.hostname。主域名是不带www的域名,例如a.com,主域名前面带前缀的通常都为二级域名或多级域名,例如www.a.com其实是二级域名。 domain只能设置为主域名,不可以在b.a.com中将domain设置为c.a.com。

同时在修改document.domain的时候也需要注意一些问题,和可能产生的影响:修改document.domain可能产生的影响

这部分参考了这里:js中跨域问题总结document.domain+iframe部分

这里说到了document.domain,那么我也就想到了document的其他属性,放这一起看下:

(1)document.referrer 简单来说,一般情况下浏览器请求A时发送的Header中Referer是什么,那么拿到A页面后document.referre的值就是什么.我们可以通过访问document的这个属性来知道我们的页面是从哪里来的。通常还能看到一些参数。

(2)document.links 它能得到页面中所有<a>和<area>标签中的href属性值。

(3)document.compatMode 如果得到的值是CSS1Compat那么是标准模式,BackCompat则是混杂模式

(4)document.readyState 文档加载属性 值为complete即加载完全 loading则正在加载。

上面只举例列了一些,其实还有很多很好用的,可以自行查阅手册:document的属性和方法

利用iframe和location.hash实现跨域

想必有很多人像我之前一样,或许只知道上面文中所说的那几种方法。所以,我刚了解到可以用iframe和location.hash来实现跨域的时候,我会想,为什么他们可以实现。iframe是什么,有什么特性,location.hash是什么又有什么特性。

准备:

>>1.MDN上说的<iframe>:HTML 的<iframe>(HTML inline框架元素)是一个嵌套的浏览上下文,他可以有效地嵌入另一个HTML页面到当前页面。在HTML 4.01中,一个文件可能包含一个头和一个身体或头部和一个框架集,而不是一个身体和一个框架集。然而,一个< iframe >可以被应用在正常的文件体。每个浏览上下文有它自己的会话历史和活动文档。浏览上下文包含嵌入的内容叫做父浏览上下文。顶级的浏览上下文(没有父)是典型的浏览器窗口。

了解到了<iframe>,我觉得另外两个相似的标签<frame>和<frameset>可以一起拿过来看下啦,这有篇文章放在一起总结了下:Frameset,Frame和Iframe

>>2.window.location:打开chrome的console,一起来试一下,很快就懂啦。我们从最基本的开始:

2.1 可以跳转到另一个页面:打开百度搜索的主页,然后在console中输入下面代码:

window.location = "http://www.cnblogs.com/skylar/";

看,是不是跳转到我的博客主页啦。

2.2 强制从服务器重新加载页面:既然来到了我的博客主页,就先别走,继续输入下面代码:

window.location.reload(true);

可以看到页面被重新加载了。

2.3 然后可以点击进去比如js中各种跨域问题实战小结(一)这篇文章,console输入:

location.hash = '#comments';

看看页面是不是跳到了最下面评论的位置。

其实还有两个特性:比如说alert当前url属性,通过修改window.location.search属性向服务器发送一个字符串,这你可以移步这里自行脑补:MDN window.location 。

这里有用的是下面这个例子:location.hash理解demo

这里可以查看代码:https://github.com/zhangmengxue,因为我们就是要利用location.hash的特性来加上iframe来实现跨域,看完了这个demo,我们就可以开始实现跨域了。(请用chrome打开,还有些需要总结的兼容性问题)

实现:

我为了模拟两个不同的域,在SAE上面创建了两个wordpress应用,如果你之前也不懂SAE是什么的话,这个小简介:新浪SAE 会让你大致了解到SAE是做什么的,如何开始使用它。我的两个应用:

看到url了吧,是不同域喔,环境模拟好了,我可以开始尝试跨域了。

在[http://1.daeskylar.sinaapp.com/]下有两个文件 a.html和c.html;

在[http://1.skylarisdae.sinaapp.com/]下有文件 b.html

我想要在a.html中访问我在b.html中模拟的数据Helloworld!

好呀,那现在点击 有a.html和c.html的这个域 访问我在这个域上的a.html文件,它应该会告诉你他是a.html页面,然后他会访问到了我放在b.html中的HelloWorld!

跨域这样就实现了喔,那具体是怎样实现的呢?

原理:

a.html想和b.html通信(在a.html中动态创建一个b.html的iframe来发送请求);

但是由于“同源策略”的限制他们无法进行交流(b.html无法返回数据),于是就找个中间人:与a.html同域的c.html;

b.html将数据传给c.html(b.html中创建c.html的iframe),由于c.html和a.html同源,于是可通过c.html将返回的数据传回给a.html,从而达到跨域的效果。

三个页面之间传递参数用的是location.hash,改变hash并不会导致页面刷新(这点很重要)。

a.html---①--->b.html----②--->c.html---③--->a.html——>a.html便获得到了b.html中的数据

①通过iframe的location.hash传参数给b.html ;

②通过iframe的location.hash传参数给c.html ;

③同域传给a.html。

代码:

a.html:

<script type="text/javascript">
    alert('我是a页面');
function sendRequest(){
  //动态创建个iframe
  var ifr = document.createElement('iframe');
  ifr.style.display = 'none';
  //跨域发送请求给b.html,参数是#sayHello
  ifr.src = 'http://1.skylarisdae.sinaapp.com/b.html#sayHello';
  document.body.appendChild(ifr);
}
function checkHash(){
  var data = location.hash?location.hash.substring(1):'';
  if(data){
    //这里处理返回值
    alert(data);
    location.hash = '';
  }
}
setInterval(checkHash,1000);
window.onload = sendRequest;
</script>

c.html:

1 <!doctype html>
2 <html>
3 <head>
4 <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
5 <title>localhost:c.html</title>
6
7 <style type="text/css">
8
9 </style>
10 </head>
11
12 <body>
13 <script type="text/javascript">
14 //因为c.html和a.html属于同一个域,所以可以通过改变其location.hash的值, 可以通过parent.parent获取a.html的window对象
15 parent.parent.location.hash = self.location.hash.substring(1);
16 </script>
17 </body>
18 </html>

b.html:

<!doctype html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<title>另某个域的:b.html</title>

<style type="text/css">

</style>
</head>

<body>
<script type="text/javascript">
function checkHash(){
var data = '';
//模拟一个简单的参数处理操作
switch(location.hash){
case '#sayHello':
data = 'HelloWorld';
break;
case '#sayHi':
data = 'HiWorld';
break;
default : break;
}
data && callBack('#'+data);
}
function callBack(hash){
var proxy = document.createElement('iframe');
proxy.style.display = 'none';
proxy.src = 'http://1.daeskylar.sinaapp.com/c.html'+hash;
document.body.appendChild(proxy);
}
window.onload = checkHash;
</script>
</body>
</html>

利用window.name实现跨域

实现:

我还是利用上面我在sae上面创建的应用:

在[http://1.daeskylar.sinaapp.com/]下有两个文件 wantdata.html和proxy.html;

在[http://1.skylarisdae.sinaapp.com/]下有文件 data.html。

我想要在wantdata.html中访问到我在data.html中存在window.name中的数据,是个字符串,就叫‘我拿到数据啦!’。

访问这里:http://1.daeskylar.sinaapp.com/wantdata.html 应该可以看到alert出的数据喔。

原理:

MDN上的window.name是这样说的:

窗口的名称,主要用于设置超链接和表格对象。Windows不需要有名字。

它也被用于提供跨域通信的一些框架(例如,sessionvars和Dojo的DojoX。IO。windowname)为JSONP更安全的替代方案。

window.name 的美妙之处在于:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

这篇文章讲解的非常详细,很值得阅读:使用window.name解决跨域问题

实现起来基本步骤如下:

1.创建一个iframe,把其src指向目标页面(提供web service的页面,该目标页面会把数据附加到这个iframe的window.name上,大小一般为2M,IE和firefox下可以大至32M左右;数据格式可以自定义,如json字符串);

2.监听iframe的onload事件,在此事件中立即设置这个iframe的src指向本地域的某个页面,由本地域的这个页面读取iframe的window.name。

3.获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)。

总结起来即:iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

代码:

wantdata.html

<!doctype html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<title>window.name跨域demo</title>

<style type="text/css">

</style>
</head>

<body>
<script type="text/javascript">
var state = 0,
iframe = document.createElement('iframe'),
loadfn = function() {
if (state === 1) {
var data = iframe.contentWindow.name; // 读取数据
alert(data); //弹出'拿到数据啦!'
//下面立即销毁iframe,释放内存,也保证安全
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
} else if (state === 0) {
state = 1;
iframe.contentWindow.location = "http://1.daeskylar.sinaapp.com/proxy.html"; // 设置的代理文件
}
};
iframe.src = 'http://1.skylarisdae.sinaapp.com/data.html';
if (iframe.attachEvent) {
iframe.attachEvent('onload', loadfn);
} else {
iframe.onload = loadfn;
}
document.body.appendChild(iframe);
</script>
</body>
</html>

data.html

<!doctype html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<title>data页</title>

<style type="text/css">

</style>
</head>

<body>
<script type="text/javascript">
window.name = '拿到跨域的数据啦!'; // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
// 数据格式可以自定义,如json、字符串
</script>
</body>
</html>

proxy.html

<!doctype html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<title>window.name跨域demo</title>

<style type="text/css">

</style>
</head>

<body>
<span>这里是代理空文件</span>
<script type="text/javascript">

</script>
</body>
</html>

   
2789 次浏览       14

</script>

相关文章 相关文档 相关课程



深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]

Android手机开发(一)
理解Javascript
非典型ajax实践
彻底的Ajax
javascript 使用Cookies
使用 jQuery 简化 Ajax 开发
更多...   


Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术


某航空公司IT部 JavaScript实践
某电视软件 HTML5和JavaScript
中航信 JavaScript高级应用开发
大庆油田 web界面Ajax开发技术
和利时 使用AJAX进行WEB应用开发
更多...