本文主要探讨用于构建实时跨源通信的两个模块:跨文档消息通信(Cross Document Messaging)和XMLHttpRequestLevel2。通过这两个模块,我们可以构建不同域间进行安全通信的Web应用。
一、跨文档消息通信
出于安全方面的看的考虑,运行在同一浏览器中的框架、标签页、窗口间的通信一直多受到严格的限制。但是现实中还存在一些合理的让不同站点的内容能在浏览器内进行交互的需求,其中Mashup就是一个典型的例子,它是各种不同应用的结合体。为了满足上述需求,引入了一种新的功能:跨文档消息通信。其可以确保iframe、标签页、窗口间安全地进行跨源通信。
发送消息使用postMessage API,其示例代码如下:
1
|
chatFrame.contentWindow.postMessage(content,url); |
接受消息时,需要在页面中添加一个事件处理函数,当消息到达时,通过检查消息的来源来决定如何对这条消息如何处理,示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
window.addEventListener( "message" ,messageHandler, true );
function
messageHandler(e){
switch (e.origin){ //表示数据发送源
case
"friend.example.com" :
//处理消息
processMessage(e.data); //发送方实际传送的消息
break ;
default :
//其他消息来源
//消息被忽略。
}
} |
postMessage API提供了一种交互方式,使得不同源的iframe之间可以进行消息通信。
HTML5 通过引入源的感念对域安全进行了阐明和改进。源是网络上用来建立信任关系的地址的子集。源由规则(scheme)、主机(host)、端口(port)组成,例如由于scheme(https、http)不同,则源不同。
跨源通信通过 源来确定发送者,这就使得接收方可以忽略或者拒绝来自不可信源的消息。同时需要通过添加监听事件来接受消息,以避免被不可信应用程序的信息所干扰。但是在使用外来消息时,即便是可靠的数据源,也同样要谨慎,以防止内容注入。
在使用postMessage API时,需要遵循以下步骤:
1、检查浏览器是否支持
1
2
3
|
if ( typeof window.postMessage === undefined){
//浏览器不支持postMessage } |
2、发送消息
1
|
window.postMessage( "Hello" , "xx.example.com" );
|
第一个参数包含要发送的数据,第二个参数时消息传递的目的地。
如果要发送消息给iframe,则使用如下代码:
1
|
document.getElementById( "iframe" )[0].contentWindow.postMessage( "Hello" , "xx.example.com" );
|
3、监听消息事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
window.addEventListener( "message" ,messageHandler, true );
var
originWhiteList = [ "a.example.com" , "b.example.com" , "c.example.com" ];
function
messageHandler(e){
if (checkWhiteList(e.origin)){
processMessage(e.data); //发送方实际传送的消息
} else {
//忽略发送的消息
}
} function
checkWhiteList(origin){
for ( var
i = 0; i<originWhiteList.length; i++){
if (origin === originWhiteList[i]){
return
true ;
}
}
return
false ;
} |
二、XMLHttpRequestLevel2
XMLHttpRequestLevel2是XMLHttpRequest的改进版本,主要涉及:跨源XMLHttpRequess和进度事件(Progress events)。
XMLHttpRequest仅限于同源通信,XMLHttpRequestLevel2通过跨资源共享实现(Cross Origin Resource Sharing)跨源XMLHttpRequests。
在XMLHttpRequest中通过readystatechange事件来响应进度,但是其在某些浏览器中不被兼容。XMLHttpRequestLevel2用了一个有意义的名字Progress进度来命名进度事件。其进度事件的名称主要有loadstart、progress、abort、error、load、loadend。通过对程序属性设置回调函数,可以实现对这些事件的监听。
在使用XMLHttpRequestLevel2时,需要遵循以下步骤:
1、检查浏览器是否支持
1
2
3
4
|
var xhr = new XMLHttpRequest();
if ( typeof xhr.withXredentials === undefined){
//浏览器不支持XMLHttpRequest
} |
2、构建跨源请求
1
2
|
var crossOriginRequest = new XMLHttpRequest();
|
在请求过程中,务必确保能够监听到错误,以找出出错原因,解决问题。
3、使用进度事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
crossOriginRequest.onprogress = function (e){
var
total = e.total;
var
loaded = e.loaded;
if (e.lengthComputable){
//处理其他事情
}
} crossOriginRequest.upload..onprogress = function (e){
var
total = e.total;
var
loaded = e.loaded;
if (e.lengthComputable){
//处理其他事情
}
} |
三、postMessage API示例应用
以跨源聊天应用为例,来演示门户页面和聊天部件之间的交互。
1、创建postMessagePortal.html页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
<!DOCTYPE html> <head>
<meta http-equiv= "Content-Type"
content= "text/html; charset=UTF-8" >
<title>跨源通信-WebChat</title>
<link rel= "stylesheet"
href= "styles.css" >
</head>
<h1>跨源通信门户</h1>
<p><b>源</b>: http: //portal.example.com:8080</p>
状态 <input type= "text"
id= "statusText"
value= "Online" >
<button id= "sendButton" >更改状态</button>
<p>
使用postMessage发送一个状态,以更新包含在此页面中的widgetiframe。
</p>
<iframe id= "widget"
src= "http://chat.example.net:8080/communication/postMessageWidget.html" ></iframe>
<script>
var
notificationTimer = null ;
function
messageHandler(e){
if (e.origin == targetOrigin){
notify(e.data);
} else {
//忽略
}
}
function
sendString(s){
document.getElementById( "widget" ).contentWindow.postMessage(s,targetOrigin);
}
function
notify(message){
alert(message);
}
function
sendStatus(){
var
statusText = document.getElementById( "statusText" ).value;
sendString(statusText);
}
function
loadDemo(){
document.getElementById( "sendButton" ).addEventListener( "click" ,sendStatus, true );
sendStatus();
}
window.addEventListener( "load" ,loadDemo, true );
window.addEventListener( "message" ,messageHandler, true );
</script>
|
2、创建postMessageWidget.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<!DOCTYPE html> <head>
<meta http-equiv= "Content-Type"
content= "text/html; charset=UTF-8" >
<title>widget</title>
<link rel= "stylesheet"
href= "styles.css" >
</head>
<h1>Widget iframe</h1>
<p><b>源</b>: http: //chat.example.net:8080</p>
<p>通过门户设置状态为: <strong id= "status" ></strong> <p>
<div>
<input type= "text"
id= "messageText"
value= "Widget notification." >
<button id= "actionButton" >发送通知</button>
</div>
<script>
window.addEventListener( "load" ,loadDemo, true );
window.addEventListener( "message" ,messageHandler, true );
function
loadDemo(){
document.getElementById( "actionButton" ).addEventListener( "click" ,
function () {
var
messageText = document.getElementById( "messageText" ).value;
sendString(messageText);
}, true );
}
function
messageHandler(e) {
document.getElementById( "status" ).textContent = e.data;
} else
{
// ignore messages from other origins
}
}
function
sendString(s) {
window.top.postMessage(s, targetOrigin);
}
</script>
|
注意:第一、上述的两个页面需要部署到web服务器;第二、两个页面必须来自不同的域。如果要在本机部署,则需要更改hosts文件,增加:
1
2
|
127.0.0.1 portal.example.com 127.0.0.1 chat.example.net |
修改完成后,需要关闭浏览器,再次重新打开。
四、XMLHttpRequestLevel2示例应用
1、创建crossOriginUpload.html页面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
<!DOCTYPE html> <head> header(“Access-Control-Allow-Origin: *”); <meta http-equiv= "Content-Type"
content= "text/html; charset=UTF-8" >
<title>上传地理数据</title> <link rel= "stylesheet"
href= "styles.css" >
</head> <script> function
loadDemo() {
var
dataElement = document.getElementById( "geodata" );
dataElement.textContent = JSON.stringify(geoData).replace( "," , ", " , "g" );
var
xhr = new
XMLHttpRequest()
if
( typeof
xhr.withCredentials === undefined) {
document.getElementById( "support" ).innerHTML = "浏览器不支持跨源XMLHttpRequest" ;
} else
{
document.getElementById( "support" ).innerHTML = "浏览器支持跨源XMLHttpRequest" ;
}
function
setProgress(s) {
document.getElementById( "progress" ).innerHTML = s;
}
document.getElementById( "sendButton" ).addEventListener( "click" ,
function () {
xhr.upload.onprogress = function (e) {
var
ratio = e.loaded / e.total;
setProgress( "已上传"
+ ratio + "%" );
}
xhr.onprogress = function (e) {
var
ratio = e.loaded / e.total;
setProgress( "已下载"
+ ratio + "%" );
}
xhr.onload = function (e) {
setProgress( "完成" );
}
xhr.onerror = function (e) {
setProgress( "错误" );
}
xhr.open( "POST" , targetLocation, true );
geoDataString = dataElement.textContent;
xhr.send(geoDataString);
}, true );
}
window.addEventListener( "load" , loadDemo, true );
</script> <h1>XMLHttpRequest Level 2</h1> <p id= "support" ></p>
<h4>上传地理数据:</h4> <textarea id= "geodata" >
</textarea> </div> <button id= "sendButton" >上传</button>
<script> geoData = [[39.080018000000003, 39.112557000000002, 39.135261, 39.150458, 39.170653000000001, 39.190128000000001, 39.204510999999997, 39.226759000000001, 39.238483000000002, 39.228154000000004, 39.249400000000001, 39.249533, 39.225276999999998, 39.191253000000003, 39.167993000000003, 39.145685999999998, 39.121620999999998, 39.095761000000003, 39.080593, 39.053131999999998, 39.02619, 39.002929000000002, 38.982886000000001, 38.954034999999998, 38.944926000000002, 38.919960000000003, 38.925261999999996, 38.934922999999998, 38.949373000000001, 38.950133999999998, 38.952649000000001, 38.969692000000002, 38.988512999999998, 39.010652, 39.033088999999997, 39.053493000000003, 39.072752999999999], [-120.15724399999999, -120.15818299999999, -120.15600400000001, -120.14564599999999, -120.141285, -120.10889900000001, -120.09528500000002, -120.077596, -120.045428, -120.0119, -119.98897100000002, -119.95124099999998, -119.93270099999998, -119.927131, -119.92685999999999, -119.92636200000001, -119.92844600000001, -119.911036, -119.942834, -119.94413000000002, -119.94555200000001, -119.95411000000001, -119.941327, -119.94605900000001, -119.97527599999999, -119.99445, -120.028998, -120.066335, -120.07867300000001, -120.089985, -120.112227, -120.09790700000001, -120.10881000000001, -120.116692, -120.117847, -120.11727899999998, -120.14398199999999]]; </script> <p> <b>状态: </b> <span id= "progress" >准备</span>
</p> |
注意:部署web应用,运行crossOriginUpload.html页面时,可能会才出现如下的提示错误:
这是因为访问一页面的域与所请求的域非同源造成的。且浏览器是根据响应头的规则来确定这个域是否同源可以接收。
因此我们需要http://geodata.example.net:8080/upload在返回内容时,设置Header Access-Control-Allow-Origin,即:
Response.AddHeader("Access-Control-Allow-Origin","*") ;
浏览器在接收到服务器返回信息时,会检查响应头的Access-Control-Allow-Origin,它的值标识请求内容所允许的域。如果将服务器设置Access-Control-Allow-Origin为*,表明该返回信息允许所有源访问。如果设置为具体的域,如http://xx.com,就表明除了同源外,只允许域来自xx.com的访问。
本文链接:http://www.cnblogs.com/oooweb/p/html5-communication.html
via oschina