服务器端跨域解决方案
网站越来越需要彼此协作。 例如,在线房屋租赁网站需要Google Maps支持以显示特定房屋的位置。 为了满足这样的需求,出现了各种混搭。 mashup是一个Web应用程序,它集成了来自不同提供程序的数据或组件,以使其更加有用或更具自定义性。 混搭或协作功能被视为Web 2.0的重要组成部分。
令人惊讶的是,将异步JavaScript和XML(Ajax)与混搭结合起来并不容易。 由于浏览器施加的安全限制,使页面上的不同小部件相互通信也很麻烦。 传统上,您可以通过在服务器端设置不可扩展的代理来解决此问题。 在本文中,了解有关客户端的其他一些解决方案,用于跨域通信和数据传输。
安全限制
相同的来源策略(SOP)阻止从一个来源加载的脚本从另一个来源获取或操纵文档中的属性或方法。 术语“ 起源”是域名,应用程序协议和运行脚本的文档端口的组合。 关于SOP概念可能会有误解; 这意味着不止站点A无法从站点B获取信息。您需要知道在SOP限制下可以做什么和不能做什么。
SOP的局限性
例如,来源A的网页可以:
- 从原点B获取脚本,CSS样式表或图像
- 包含指向原点B的页面的iframe /框架
- 使用HTML元素(例如
iframe
或img
的src
属性将一些信息发送到原点B
来源A的网页无法:
- 发起对源B的Ajax调用
- 在指向B页的iframe /框架中读取或操作内容
为什么会这样呢? 主要是保护用户的重要信息。 假设是:如果用户与一个提供程序进行交互,则他不希望将提交给该站点的任何信息泄露给其他站点。 这种限制限制了不同网站之间的合作,但可以保护用户免受潜在有害攻击的侵害。
有许多解决问题的方法。 例如,JSONP利用了以下事实:网页可以从任何来源动态加载脚本。 但是,JSONP有两个主要限制:它没有像Ajax调用那样的错误处理机制,并且脚本标记请求是Get method
,它具有长度限制。 (有关JSONP的更多信息,请参阅“ 相关主题”部分。)
以下各节讨论了跨域通信和数据传输的客户端解决方案。 每种解决方案都有优点和缺点; 应用程序方案在很大程度上影响您的选择。
跨子域解决方案
如果源A和源B共享相同的超级域,则很容易通过document.domain
属性的更改使两个文档相互访问。 document.domain
是HTML规范中的只读属性; 大多数现代浏览器都允许将其设置为超级域(而不是顶级域)。 例如,URL为www.myapp.com/index.html的文档可以将其自己的域设置为myapp.com
,而sample.myapp.com/index2.html中的另一个文档也可以将其自身的域设置为myapp.com
。 图1显示了document.domain
工作方式。
图1. document.domain
![通过将各自的域设置为相同的超级域来描述来自相同超级域的具有不同URL的文档](https://i-blog.csdnimg.cn/blog_migrate/acde60d8f8d5614438e8f946b846df30.png)
使用此跨子域解决方案,来自不同子域的源可以在同一超级域下进行通信,这不是SOP限制。 但是,严格来说,跨子域解决方案最适合Intranet应用程序。
URL.hash(片段ID)解决方案
URL由几部分组成,如下图2所示:
图2. URL的组成
![URL屏幕截图,其中的行分隔并指定了不同的URL组件(协议,主机,路径名,查询和哈希)](https://i-blog.csdnimg.cn/blog_migrate/8bbafd0f90a2f94e033e643a0fe64a71.png)
传统上,对URL的任何更改都会加载新的网页。 例外是哈希值的更改。 URL哈希的任何更改都不会导致页面刷新。 在部分刷新页面时,哈希已在许多Web 2.0网站中广泛用于为每个步骤添加书签。 在跨域通信中,哈希也是宝贵的资产。 来自不同来源的文档可以设置彼此的URL,包括哈希值,尽管在获取彼此的哈希值方面存在限制。 这些文档可以使用散列相互发送消息。 图3显示了一个示例。
图3.使用URL.hash(片段ID)进行通信
![通过修改彼此的哈希值并监视自身哈希值的变化来描述具有不同URL的文档的通信](https://i-blog.csdnimg.cn/blog_migrate/899166d258460acaece530f41d499e05.png)
在图3中,当A要向B发送消息时,它可以修改B的哈希值,如清单1所示:
清单1.通过url.hash发送消息
function sendMsg(originURL, msg){
var data = {from:originURL, msg:msg};
var src = originURL + “#” + dojo.toJson(data);
document.getElementById('domainB').src=src;
}
B中的函数将轮询其自身的哈希值,并找出A发送的内容。 它可以以相同的方式回复A。 如果A想要接收此消息,它还应该轮询自身的哈希值。
清单2.监视url.hash并从中接收消息
window.oldHash="";
checkMessage = function(){
var newHash = window.location.hash;
if(newHash.length > 1){
newHash = newHash.substring(1,newHash.length);
if(newHash != oldHash){
oldHash = newHash;
var msgs = dojo.fromJson(newHash);
var origin = msgs.from;
var msg = msgs.msg;
sendMessage(origin, "Hello document A");
}
}
}
window.setInterval(checkMessage, 1000);
sendMessage = function(target, msg){
var hash = "msg="+ msg;
parent.location.href= target + “#” + hash;
}
与JSONP一样,此方法也有长度限制,但可以更好地处理错误。 某些特殊字符(例如问号(?))是URL中的保留字符,应首先进行编码。
清单3.通过url.hash发送包含特殊字符的消息
function sendMsg(originURL, msg){
…
var src = originURL + “#” + encodeURI (dojo.toJson(data));
…
}
并且,在接收时,您应该先对其进行解码。
清单4.接收包含特殊字符的消息
function checkMsg(){
…
var msgs = decodeURI(dojo.fromJson(newHash));
…
}
跨碎片技术
由于哈希已经在许多网站中用于其他用途,因此对于那些网站而言,将URL.hash(fragment id)技术用于跨域通信和数据传输将变得很复杂。 跨帧消息传递有点类似于片段ID消息传递。 图4显示了交叉碎片技术的工作原理。
图4.跨碎片技术
![通过动态创建指向目标文档所在域中的代理的iframe来通信具有不同URL的文档的描述,该iframe将获取请求/数据并通过调用目标文档中的相应功能进行响应](https://i-blog.csdnimg.cn/blog_migrate/bb8f1b115848d3982beb4a86812d674e.png)
在上图中,当A要与iframe B通信时,它将首先在其自身中创建一个iframe。 此iframe指向与B相同的域中的“代理”C。它将在代理的URL中包括参数或数据以及B的帧标识符。
清单5.以跨碎片技术发送消息
function sendMsg(msg){
var frame = document.createElement(“iframe”);
var baseProxy = “http://www.otherapp.com/proxy.html”;
var request = {frameName:’otherApp’,data:msg};
frame.src = baseProxy+”#”+encodeURI (dojo.toJson(request));
frame.style.display=”none”;
document.body.appendChild(frame);
}
当C加载时,它从A获取请求和数据,并在B中调用相应的方法。由于B和C在同一个域中,因此它们可以通过另一个窗口的处理程序直接调用另一个方法。 A可以成功向B发送消息,而B可以以相同的方式响应。
清单6.跨碎片技术接收消息
window.onLoad = function(){
var hash = window.location.hash;
if(hash && hash.length>1){
var request = hash.substring(1,hash.length);
var obj = dojo.fromJson(decodeURI (request));
var data = obj.data;
//process data
parent.frames[obj.frameName].getData(…);// getData in a function defined in B
}
}
OpenAjax实现
OpenAjax提供了受管集线器模块,以基于片段ID和跨框架技术支持跨域通信和数据传输。 托管集线器模块包括管理器端和客户端。 托管中心包含一个消息中心,用于存储消息。 每个想要与其他人进行通信的小部件都将在其自身中设置一个集线器客户端,并且还将设置一个相应的容器以与其连接。 容器将代表客户端与托管集线器进行交互。 客户端可以使用订阅/发布机制发送和接收消息。 OpenAjax的基本工作流程如图5所示。
图5. OpenAjax的主要工作流程
![描述OpenAjax实现跨域通信的主要工作流程](https://i-blog.csdnimg.cn/blog_migrate/ac717812be8c47c44390df676566e104.png)
Window.name解决方案
Window.name
是跨域通信和数据传输的棘手解决方案。 传统上, window.name
的用法如下。
- 使用
window.frames[windowName]
获取子窗口。 - 将其设置为链接元素中的目标属性。
尽管window.name
具有显着的特征,使其适合于不同来源的文档之间的“桥梁”,但它并不是一个经常使用的属性。 无论加载什么页面, window.name
的值都保持不变。 在SOP限制下如何使用? 图6说明window.name
如何协助跨域通信。
图6. window.name和跨域通信
![通过使用存储服务器响应的window.name以及从客户端回滚并检查window.name值的描述来从其他域获取资源](https://i-blog.csdnimg.cn/blog_migrate/bd07d6a19b95ec1cba68c703aa1591d5.png)
当页面A要获取其他来源的资源或网络服务时,它可以在其自身中添加新的隐藏iframe B,以外部资源或服务为目标。 服务器将以HTML文件响应,从而将window.name
属性设置为数据。 由于A和iframe B现在不在同一个域中,因此A仍无法获得B的name属性。在B获得数据之后,应将其导航回与A处于同一个域的任何页面以创建name属性A可以访问A。在A获得数据之后,B可以随时被销毁。
使用dojox.io.windowName进行跨域通信
Dojo提供了对基于window.name
的跨域通信的支持。 唯一的API是dojox.io.windowName.send(method, args)
,类似于dojo.xhrGet/dojo.xhrPost
。 该方法可以是GET
或POST
,并且args
与dojo.xhr
的args
相似。 例如,您可以在客户端发送跨域请求,如清单7所示:
清单7.通过window.name发送消息
var args = {
url: "http://www.sample.com/testServlet?windowName=true",
load: function(data){
alert("You've got the data from server " + data);
},
error: function(error){
alert("Error occurred: " + error);
}
}
dojox.io.windowName.send("GET",args);
您可以dojox.io.windowName
使用dojo.xhr
一样使用dojox.io.windowName
。 对于服务器端,建议如果希望通过windowname
传输访问资源或服务,请检查请求中的windowName
参数。 如果请求中包含此类参数,则服务器应使用HTML文档进行响应,该HTML文档window.name
其window.name
设置为需要传递给客户端的数据。 清单8中的代码显示了一个示例。
清单8.对window.name消息技术的后端支持
testServlet.java:
protected void doGet(HttpServletRequest request,HttpServletResponse response){
//process request
String returnData = ...;
String isWindowNameReq = request.getParameter(“windowName”);
if(null !=isWindowNameReq && Boolean.parseBoolean(isWindowNameReq)){
returnData = getCrossDomainStr(returnData);
}
response.getOutputStream().print(returnData);
}
private String getCrossDomainStr(String data){
StringBuffer returnStr = new StringBuffer();
returnStr.append("<html><head><script type=\"text/javascript\">window.name='");
returnStr.append(data);
returnStr.append("'</script></head><body></body></html>");
return returnStr.toString();
}
将框架导航回原始域中的任何页面时,应确保该页面存在于域中。 如果该页面不存在,Internet Explorer将出现问题。 在Firefox中,您可以简单地使用blank.html
。 在Dojo中,您可以使用dojo.dojoBlankHtmlUrl
属性指定要回滚的页面。 默认情况下,它在Dojo库下设置为dojo / resources / blank.html。
使用window.name
传输的数据量比使用url.hash
传输的数据量url.hash
。 大多数现代浏览器都基于window.name
支持16M +数据传输。
HTML5的新功能
在HTML5规范草案中,新方法window.postMessage(message, targetOrigin)
用于安全的跨域通信。 调用它时,将调度一个消息事件,并且,如果窗口正在侦听该消息事件,则它可以从事件中获取消息和消息的来源。 图7显示了一个示例。
图7.与HTML5的跨域通信
![通过HTML5中的本机函数(window.postMessage)和消息事件侦听器来描述跨域通信](https://i-blog.csdnimg.cn/blog_migrate/c3e5e414e0cf91ce106f271bf8230dd4.png)
在图7中,当iframe希望从另一个来源通知父窗口它已成功加载时,它可以通过window.postMessage
发送消息。 同时,它将监视反馈消息,如清单9所示:
清单9.通过HTML5新方法发送消息
http://www.otherapp.com/index.html
function postMessage(msg){
var targetWindow = parent.window;
targetWindow.postMessage(msg,"*");
}
function handleReceive(msg){
var object = dojo.fromJson(msg);
if(object.status == “ok”){
//continue to do other things
……
}else{
//retry sending msg
……
}
}
window.addEventListener("message", handleReceive, false);
window.onLoad = function(){
postMessage("already loaded");
}
父文档将监听消息事件。 消息到达时,首先检查它是否来自www.otherapp.com
,然后返回确认消息。
清单10.通过HTML5新方法接收消息
http://www.myapp.com/index.html
function handleReceive(event){
if(event.origin != "http://www.otherapp.com")
return;
//process data
……
var otherAppFrame = document.getElementById(“otherApp”)
otherAppFrame.postMessage(“{status:’ok’}”,”http://www.otherapp.com”);
}
window.addEventListener("message", handleReceive, false);
Lising 10中的示例代码可以在Firefox 3 +,Internet Explorer 8,Google Chrome 2,Opera 9+和Safari 4中运行。它促进了不同来源文档之间的通信。 而且,如果您自己的文档不想接收来自其他文档的任何消息,请不要添加消息事件侦听器并忽略所有消息。
翻译自: https://www.ibm.com/developerworks/web/library/wa-crossdomaincomm/index.html
服务器端跨域解决方案