第五章 Communication API
本章我们将探讨用于构建实时(meal-time)跨源(cross-origin)通信的两个重要模块:跨文档消息通信(Cross Document Messaging)和XML HttpRequest Level2。通过他们,我们可以构建引入注目的Web应用。作为HTML5应用新的通信手段,这两个构建块可以让不同域间的Web应用安全地进行通信。
接下来,我们会探讨XML HttpRequest Level2–XML HttpRequest的改进版,介绍XML HttpRequest在哪些方面得到了改进,并会特别介绍如何用XML HttpRequest发起跨源请求以及如何使用新的进度事件(progress event)。
5.1 跨文档消息通信
出于安全方面的考虑,运行在同一浏览器中的框架、标签页、窗口间的通信一直都受到了严格的限制。例如,在浏览器内部共享信息对某些站点可能比较方便,但是同时也增加了受到恶意攻击的可能性。如果浏览器允许程序访问加载到其他框架和标签的内容,某些网站就能够利用脚本来窃取其他网站的某些信息。浏览器厂商合理地限制了这类访问,当尝试检索或修改从其他源加载的内容时,浏览器会抛出安全异常,并阻止相应的操作。
然而,现实中存在一些合理的让不同站点的内容能在浏览器内进行交互的需求。Mashup 就是最典型的一个例子。它是各种不同应用的结合体,把来自不同站点的地图、聊天、新闻等应用全部整合到一起形成一个新的元应用(meta-application) 。在这种情形下,如果浏览器内部能提供直接的通信机制,就能更好地组织这些应用。
为了满足上述需求,浏览器厂商和标准制定机构一致同意引人一种新功能:跨文档消息通信。跨文档消息通信可以确保iframe、标签页、窗口间安全地进行跨源通信。它把postMessag API定义为发送消息的标准方式。利用postMessage发送消息非常简单,代码如下所示:
chaFrame.contentWindow,postMessage(‘Hello,world’,’http://www.example.com/’);
接收消息时仅需在页面中增加一个事件处理函数。当某个消息到达时,通过检查消息的来源来决定是否对这条消息进行处理。代码清单5-1描述了一个可以把消息传递给messageHandler()函数的事件*********。
代码清单5-1 消息事件的*********
window.addEventListener(“message”,messageHandler,true); Function messageHandler(e){ Switch(e.origin){ Case“friend.example.com”; //处理消息 processMessage(e.data); Break; Default: //消息来源无法识别 //消息被忽略 } }
消息事件是一个拥有data (数据)和origin (源)属性的DOM事件。data 属性是发送方传递的实际消息,而origin属性是发送来源。有了origin属性,接收方能轻易地忽略掉来自不 可信源的消息;根据可信源的列表能方便地判断来源是否可靠。
如图5-1所示.,postMessage API 提供了一种交互方式,使得不同源的iframe (源为http://chat.example.net 网站的聊天部件)可以与其父页面(源为http://portal.example.com,包含这里提及的聊天部件)进行通信。
图5-1 iframe与其父页面间的postMessage通信
这个示例中。聊天部件被包含在一个iframe中,因此不能直接访问父窗口。当聊天部件接收到聊天消息时,它可以用postMessage向父页面发送一个消息,以便父页面提醒聊天部件的用户接收到了新消息。同理,父页面也能将用户的状态发送给聊天部件。父页面和部件通过把彼此的源加到可信源的白名单中,就都能收到来自对方的信息。
在postMessage之前,iframe 间的通信有时会通过直接写脚本来实现。通过执行一个页面中的脚本来尝试操纵另一个文件。这种方式可能会因为安全限制而被禁止。因而postMessage 取代了直接编程访问,它提供了Javascript 环境中的异步通信机制。如图5-2所示,假如没有postMessage。 跨源通信将导致安全错误,为防止跨站点(cross-site) 脚本攻击,浏览器会强制引发这种安全错误。
图5-2 Firefox和Firebug 早期版本中的跨站点脚本错误
postMessage API不仅可以胜任同源文档间的通信,而且在浏览器不允许非同源通信的情况下,postMessage API也很有用,鉴于它的一致性和易用性,在同源文档间通信时也推荐使用postHessage。在JavaScript 环境的通信中始终应使用postMessage API,例如使用HTML5 Web Workers 通信时。
5.1.1 理解源安全
HTML5 通过引入源(origin)的概念对域安全进行了阐明和改进。源是在网络上用来建立信任关系的地址的子集,源由规则(schemee). 主机(host) 、端口(port)组成。例如,由于规则不同(https与bttp) ,所以https://www.example.com页面与http://www.example.com页面的源是不同的。源的概念中不考虑路径,如http://www.example.com/index.html与http://www.example.com/page2.html有相同的源,因为他们只是路径不同,其他完全相同。
HTML5 定义了源的序列化。源在API 和协议中以字符串的形式出现。这对于使用XMLHttpRequest进行跨源HTTP请求是非常重要的,对于WebSocket也一样。
跨源通信通过源来确定发送者,这就使得接收方可以忽略或者拒绝来自不可信源的消息。同时,各种应用必须加入事件*********以接收消息,从而避免被不可信应用程序的信息所干扰。
postMessage 的安全规则确保了消息不会被传递到非预期的源页宙页面中。当发送消息时, 由发送方指定接收方的源。如果发送方用来调用postMessage的窗口不具有特定的源(例如用户跳转到了其他站点),浏览器就不会传送消息。
类似地,接收信息的时候,发送方的源也被作为消息的一部分。 为避免伪造,消息源由浏览器提供。接收方可以决定处理哪些消息,以及忽略哪些消息。我们可以保留一份白名单.告诉浏览器仅仅处理可信源的消息。
谨慎对待外部输入
“在处理跨源通信的消息时,一定要验证每个消息的源。此外,处理消息中的数据时也应该谨慎。即使消息来自信源,也应该像对待外部输入时一样仔细,下面是两个关于内容注入的示例,一个是可能带来麻烦的方法,而另一个则是较为安全的方法。
//危险:e.data会被当成标记! element.innerHTML = e.data; //相对安全 element.textContent = e.data;最好永远不要对来自第三方的字符串求值,再者,要避免使用eva1方法处理应用内部的字符串。可以通过window.JSON 或者json.org 解析器使用JSON.。JSON 是一种数据格式,它能在JavaScript 命安全使用,而且json.org解析器设计得非常严谨。”
—–Frank
5.1.2 跨文档消息通信的浏览器支持情况
在本书编写的时候。HTML5 的跨文档消息通信已经被很多浏览器支持了,如表5-1 所示。
浏览器 | 支持情况 |
Chrome | 2.0及以上版本支持 |
firefox | 3.0及以上版本支持 |
Internet explore | 8.0及以上版本支持 |
opera | 9.6及以上版本支持 |
safari | 及以上版本支持 |
在使用前,我们还是应该先检测浏览器是否支持HTML5 的跨文档消息通信。本章后面的”浏览器支持情况检测”部分将演示如何编写代码来检查浏览器的支持性。
5.1.3 使用postMessage API
本节将更详细地介绍HTML5 postMessage API的使用。
1、浏览器支持情况检测
在调用postMessage前,应该首先检测浏览器是否支持它。下面就是一种检测是否支持postMessage的方法:
if(typeof window.postMessage === undefined){ //该浏览器不支持postMessage }
2、发送消息
通过调用目标页面window对象中的postMessage() 函数可发送消息,代码如下:
window.postMessage(“Hello,world”,”portal.example.com”);
第一个参数包含要发送的数据,第二个参数是捎息传送的目的地。要发送消息绘iframe,可以在相应iftame 的contentW1ndow中调用postMessage,代码如下:
document.getElementsByTagName(“iframe”)[0].content; window.postMeSSAGE(“Hello,world”,”chat.example.net”);
3、监听消息事件
脚本可以通过监听window对象中的事件来接收信息。在事件监听函数中,接收方可以决定接收或者忽略消息,见代码清单5-2。
代码清单5-2 监听消息事件并通过白名单鉴定源
var originWhiteList = ["portal.wxamplw.com","games.example.com","www.example.com"]; function checkWhiteList(origin){ for(var i = 0; i<originWhiteList.length;i++){ if (origin === originWhiteList[i]){ return true; } } return false; } function messageHandler(e){ if(checkWhiteList(e.origin)){ processMessage(e.data); }else{ //忽略来未知源的消息 } } window.addEventListener("message",messageHandler,true);
提示:HTML5定义的MessageEvent接口也是lITML5 WebSockets和HTML5 Web Workers的一部分..HTML5的通信功能中用于接收消息的API与MessageEvent接口是一致的。其他通信类API,如EventSource API和Web Workers,也都使用MessageEvent接口来传递消息。
5.1.4 使用postMessage API 创建应用
现在,我们将创建前面提到过的包含跨源聊天部件的门户应用。可以利用跨文档消息通信来实现门户页面和聊天部件之间的交互,如图5-3 所示。
图5-3 门户页面和跨源的聊天部件 iframe
这个示例演示了如何在门户页面中以iframe方式嵌入第三方的部件。示例中使用了一个来自chat.example.net 的部件。门户页面和部件通过postMessage 来通信。iframe 中的聊天部件通过父页面标题内容的闪烁来通知用户。这是后台接收事件的应用中常见的UI 技术。不过,由于部件不在父页面中,而是被隔离在一个来自不同源的iframe 中,所以直接改变标题会引发安全冲突。因此。部件使用posMessage 请求父页面修改标题。
示例中,门户页面还发送消息到iframe 以通知部件:用户改变了其状态。以这样的方式使用postMessage可以实现门户与部件之间的协作。由于信息发送时会检查目标源,接收时会检查事件源,所以数据不会意外泄露或被伪造。
提示:示例中的柳天部件没有与在线聊天系统连接,发送通知是通过用户单击”SendNotification”按钮来驱动的。在线聊天应用可以通过WebSockets来实现,这将在第6章中介绍。
为了便于演示,我们编写了两个简单的HT’ML页面;:postMessagePortal.html和postMessage-Widget.html。下面将分步演示建立门户页面和聊天部件页面的要点。示例代码位于code/cornmu-nication文件夹下。
1、创建门户页面
首先,添加来自不同源的iframe 以包含聊天部件:
<ifrme is=”widget” src=”http://chat.example.net:9999/postMessageWidget.html”></iframe>
接下来,添加事件*********messageHandler 监听来自聊天部件的消息事件。从下面的示例代码中可以看到,部件会请求门户去通知用户—一闪烁标题。为了确保消息的来源是聊天部件,我们会验证消息源;如果消息不是来源于http://chat.example.net:9999.,门户页面会直接忽略它。
var targetOrigin = "http://chat.example.net:9999"; function messageHandler(e){ if(e.origin ---- targetOrigin){ notify(e.data); }else{ //忽略来自其他源的消息 } }
最后,添加一个与聊天部件通信的函数。它用postMessage 发送一个状态,更新门户页面中的部件iframe。对于在线的聊天应用,可以用这种方式来更新用户状态(在线、离开等)。
function sendString(s){ document.getElementById(“widget”).contentWindow.postMessage(s,targetOrigin); }
2、创建聊天部件页面
首先,添加事件*********messageHandler 监听来自门户页面的消息事件。如下面的示例代码所示,聊天部件监听发过来的状态变更消息。为了确保消息来源于门户页面,我们会验证消息源,如果不是来源于http://portal.example.com:9999 ,部件将直接忽略它。
var targetOrigin = "http://portal.example.com:9999"; function messageHandler(e) { if (e.origin === "http://portal.example.com:9999") { document.getElementById("status").textContent = e.data; } else { // ignore messages from other origins } }
其次,编写用于与门户页面通信的函数 。在接收到新消息时,部件将请求门户页面通知用户,并且用postMssage() 函数向门户页面发送消息,代码如下:
function sendString(s) { window.top.postMessage(s, targetOrigin); }
3、完整代码
代码清单5-3 是postMessagePortal.thml文件的完整代码。
代码清单 5-3 postMessagePortal.thml的内容
<!DOCTYPE html> <title>Portal [http://portal.example.com:9999]</title> <link rel="stylesheet" href="styles.css"> <style> iframe { height: 400px; width: 800px; } </style> <link rel="icon" href="http://apress.com/favicon.ico"> <script> var defaultTitle = "Portal [http://portal.example.com:9999]"; var notificationTimer = null; var targetOrigin = "http://chat.example.net:9999"; function messageHandler(e) { if (e.origin == targetOrigin) { notify(e.data); } else { // ignore messages from other origins } } function sendString(s) { document.getElementById("widget").contentWindow.postMessage(s, targetOrigin); } function notify(message) { stopBlinking(); blinkTitle(message, defaultTitle); } function stopBlinking() { if (notificationTimer !== null) { clearTimeout(notificationTimer); } document.title = defaultTitle; } function blinkTitle(m1, m2) { document.title = m1; notificationTimer = setTimeout(blinkTitle, 1000, m2, m1) } function sendStatus() { var statusText = document.getElementById("statusText").value; sendString(statusText); } function loadDemo() { document.getElementById("sendButton").addEventListener("click", sendStatus, true); document.getElementById("stopButton").addEventListener("click", stopBlinking, true); sendStatus(); } window.addEventListener("load", loadDemo, true); window.addEventListener("message", messageHandler, true); </script> <h1>Cross-Origin Portal</h1> <p><b>Origin</b>: http://portal.example.com:9999</p> Status <input type="text" id="statusText" value="Online"> <button id="sendButton">Change Status</button> <p> This uses postMessage to send a status update to the widget iframe contained in the portal page. </p> <iframe id="widget" src="http://chat.example.net:9999/postMessageWidget.html"></iframe> <p> <button id="stopButton">Stop Blinking Title</button> </p>
代码清单5-4 是postMessageWidget.html页面的完整代码。
代码清单5-4postMessageWidget.html的内容
<!DOCTYPE html> <title>widget</title> <link rel="stylesheet" href="styles.css"> <script> var targetOrigin = "http://portal.example.com:9999"; function messageHandler(e) { if (e.origin === "http://portal.example.com:9999") { document.getElementById("status").textContent = e.data; } else { // ignore messages from other origins } } function sendString(s) { window.top.postMessage(s, targetOrigin); } function loadDemo() { document.getElementById("actionButton").addEventListener("click", function() { var messageText = document.getElementById("messageText").value; sendString(messageText); }, true); } window.addEventListener("load", loadDemo, true); window.addEventListener("message", messageHandler, true); </script> <h1>Widget iframe</h1> <p><b>Origin</b>: http://chat.example.net:9999</p> <p>Status set to: <strong id="status"></strong> by containing portal.<p> <div> <input type="text" id="messageText" value="Widget notification."> <button id="actionButton">Send Notification</button> </div> <p> This will ask the portal to notify the user. The portal does this by flashing the title. If the message comes from an origin other than http://chat.example.net:9999, the portal page will ignore it. </p>
4、部署应用
示例应用的运行依赖于两个先决条件:首先,页面需要部署在Web服务器上:其次,两个页面必须来自不同的源。如果可以访问位于不同域的多个Web 服务器(如两台Apache HTTP服务器) ,那么将示例文件部署到服务器之后,示例就能顺利运行了。另外,如果要在本机部署运行示例应用,需要使用Python的SimpleHTTPServerWeb 服务器,其安装步骤如下。
(1) 更新Windows 系统的hosts 文件(位于C:\Windows\system32\drivers\hosts) 或Linux系统的hosts文件(位于/etc/hosts),稍加两条指向localhost (IP 地址为127.0.0. 1) 的记录,如下所示:
127.0.0.1 chat.example.net 127.0.0.1 portal.example.net
提示:bosts文件修改完成后,必须立启浏览器以确保该DNS记录生效。
(2)安装Python,其中包括轻量级SimpleHTTPSverWeb 服务器。
(3)打开示例文件(postMessageParent.html 和postMessageWidget.html) 所在自录。
(4)按如下方式启动Web 服务器?。
Python -m SimpleHTTPServer 9999
(5)打开浏览器,输入http ://portal .example.com:9999/postMessagePortal.html。现在应该就可以看到图5-3 所示的页面了。
5.2 XMLHttpRequest Level 2
XMLHttpRequest API 使得Ajax技术的实现成为了可能。现在市面上有许多关于XMLHttpRequest和Ajax的图书。更多关于XMLHttpRequest 编程的知识,建议阅读由John Resìg 捕写的Pro JavaScript Techniques (Apress, 2006) 。
作为XMLHttpRequest的改进版。XMLHttpRequest Level 2 在功能上有了很大的改进。在本章中, 我们将介绍XMLHttpRequest Level2 的这些改进,主要集中在以下两个方面:
- 跨源XMLHttpReques;
- 进度事件(Progress events)。
5.2.1 跨源XMLHttpRequest
过去, XMLHttpRequest仅限于同源通信。XMLHttpRequest Level2 通过CORS(Cross Origin Resource Sharing,跨源资源共享)实现了跨源XMLHttpRequest。 其中源的概念曾在5.1节中讨论过。
跨源HTTP请求包括一个Origin头部。它为服务器提供HTTP请求的源信息。头部由浏览器保护,不能被应用程序代码更改。从本质上讲,它与跨文档消息通信中消息事件的origin属性作用相同.。Origin 头部不同于早先的Referer [sic]头部,因为后者中的Referer是一个包括了路径的完整URL。由于路径可能包含敏感信息,为了保护用户隐私,浏览器并不一定会发送Referer,而浏览器在任何必要的时候都会发送Origin头部。
使用跨源XMLHttpRequest可以构建基于非同源服务的Web应用程序。例如,如果Web应用程序使用了一个源的静态文本和另一个源的Ajax 服务,那么它可以借助跨样XMLHttpRequest请求实现在两个源之间的通信,如果没有跨源XMLHttpRequest则只能进行同源通信,而且部署方式也会受到限制。也许不得不将Web应用程序部署在一个单独域中或者再为其建立一个子域。
如图5-4所示,通过跨源XMLHttpRequest可以从客户端整合来自不同源的内容。如果目标服务器允许,可以使用用户证书访问受保护的内容,进而让用户直接访问个人的数据。反之,如果通过服务器端对不同源进行整合,则所有内容都要穿过一个服务器端的基础层,因而可能会形成瓶颈。
图5-4 客户端整合与服务器端整合的差异
CORS规范要求,对一些敏感行为一一如申请证书的请求或除了GET和POST以外的OPTIONS预检(preflight) 请求,必须由浏览器发送给服务器,以确定这种行为能否被支持和允许,这意味着成功通信的背后或许需要由具备CORS处理能力的服务器来支持。代码清单5-5 和代码清单5-6展示了托管在www.example.com的页面与www.example.net服务之间用于跨源交换的HITP 头部。
代码清单5-5 请求的头部示例
POST /main HTTP/1.1 Host: www.example.net User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3) Gecko/20090910 Ubuntu/9.04 (jaunty) Shiretoko/3.5.3 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://www.example.com/ Origin: http://www.example.com Pragma: no-cache Cache-Control: no-cache Content-Length: 0
代码清单5-6 相应的头部示例
HTTP/1.1 201 Created Transfer-Encoding: chunked Server: Kaazing Gateway Date: Mon, 02 Nov 2009 06:55:08 GMT Content-Type: text/plain Access-Control-Allow-Origin: http://www.example.com Access-Control-Allow-Credentials: true
5.2.2 进度事件
新版XMLHttpReques中最重要的API 改造之一是增加了对进度的响应。在XMLHttpReques之前的版本中,仅有readystatechange 一个事件能够被用来响应进度。更糟糕的是,浏览器对该事件的实现并不兼容,如在Internet Explorer中永远都不会触发readyStàte3?。此外,readyState 的更改事件缺乏与上传进程通信的方法。在这种情况下,想要实现上传进度条是一件相当复杂的事情,而且还要牵扯到服务器端的编程开发。
XMLHttpReques Leve12用了一个有意义的名字Progress进度来命名进度事件。表5-2 是新的进度事件名称。通过为事件处理程序属性设置回调函数。
进度事件的名称 |
loadstart |
progress |
abort |
error |
load |
loadend |
5.2.3 HTML5 XMLHttpRequest Level 2浏览器支持情况
如表所示,在编写本文的时候,HTML5 XMLHttpRequest已经在许多浏览器得到支持。
浏览器 | 支持情况 |
Chrome | 2.0及以上版本 |
Firefox | 3.5及以上版本 |
Internet Explorer | 不支持 |
Opera | 不支持 |
Safari | 4.0及以上版本 |
Due to the varying levels of support, it is a good idea to first test if HTML5 XMLHttpRequest issupported, before you use these elements. The section “Checking for Browser Support” later in thischapter will show you how you can programmatically check for browser support.
5.2.4 使用 XMLHttpRequest API
In this section, we’ll explore the use of the XMLHttpRequest in more detail. For the sake of illustration,we’ve created a simple HTML page—crossOriginUpload.html. The sample code for the followingexamples is located in the code/communication folder.
1、检测浏览器支持
Before you try to use XMLHttpRequest Level 2 functionality—such as cross-origin support—it is a good idea to check if it is supported. You can do this by checking whether the new withCredentials property is available on an XMLHttpRequest object as shown in Listing 5-7.
Listing 5-7. Checking if cross-origin support is available in XMLHttpRequest
var xhr = new XMLHttpRequest() if (typeof xhr.withCredentials === undefined) { document.getElementById("support").innerHTML = "Your browser <strong>does not</strong> support cross-origin XMLHttpRequest"; } else { document.getElementById("support").innerHTML = "Your browser <strong>does</strong>support cross-origin XMLHttpRequest"; }
2、构建跨源请求
为了构建跨源XMLHttpRequest,首先呀创建一个新的XMLHttpRequest对象,代码如下:
var crossOriginRequest = new XMLHttpRequest();
接下来。通过制定不同源的地址来构造跨源XMLHttpRequest,代码如下:
crossOriginRequest.open(“GET”,”http://www.example.net/stock feed”,true);
在请求过程中务必确保能够监听到错误,请求不成功有很多原因,如网络故障、访问被拒、目标服务器缺乏对CORS支持等。
3、使用进度事件
在表示请求和响应的不同阶段方面, XMLHttpRequest Leve12不再使用数值型状态表示法,而是提供了命名进度事件。为事件处理程序属性设置相应的回调函数后,就可以对这些事件进行监听了。
代码清单5-8 显示了如何用回调函数来处理进度事件。进度摹件使用了多个文本域,分别记录待发送数据的总量、已发送数据的总量,以及用于标识数据总量是否已知的布尔值(流式HTTP中可能无法获知数据总量)。XMLHttpRequest。upload调度事件也用到了这些文本域。
代码清单5-8 使用onprogress事件
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){ //利用进度信息做些事情 } }
5.2.5 创建XMLHttpRequest应用
在这个示例中,我们要将赛事位置坐标上传到非同源的Web 服务器端,并使用新的进度事件监控包括上传进度在内的HTTP请求的状态,示倒运行效果如图5-5所示。
图5-5 上传位置数据的Web应用
为方便说明,我们创建了HTML文件crossOriginUpload.html。下面的步骤揭示了图5-5 所示的跨源上传页面的重要组成部分。示例代码位于code/communication文件夹下。
首先,创建一个新的xmlhhttprequest对象:
var xhr = xmlhhttprequest();
接下来,检查浏览器是否支持跨源xmlhhttprequest,代码如下:
if (typeof xhr.withcredaentials === undefined){ document.getelementbyid("support"),innerhtml = "your browser <strong>doesnot</strong>support cross-origin xmlhttpreqest"; }else{ document.getelementbyid("support"),innerhtml = "your browser <strong>does<strong/>support cross-origin xmlhttprequest"; }
然后,设置回调函数以处理进度事件,并计算上传和下载的完成率。
xhr.upload.onprogress = function(e){ var ratio = e.loaded/e.total; setprogress(ratio + "% uploaded"); } xhr.onload = function(e){ setprogress("finished"); } xhr.onerror = function(e){ setprogress("error"); }
最后,打开请求并发送包含编码后的地理位置数据的字符串。由于目标位置的url与当前页面不同源,所以这是一个跨源请求。
var targetlocation = "http://geodata.example.net:9999/upload"; xhr.open("post",targetlocation,true); geodatastring = dataelement.textconent; xhr.send(geodatastring);
完整代码
代码清单5-9 是crossOriginUpload.html文件的完整代码。
代码清单5-9 是crossOriginUpload.html文件
<!DOCTYPE html> <title>Upload Geolocation Data</title> <link rel="stylesheet" href="styles.css"> <link rel="icon" href="http://apress.com/favicon.ico"> <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 = "Your browser <strong>does not</strong> support cross-origin XMLHttpRequest"; } else { document.getElementById("support").innerHTML = "Your browser <strong>does</strong> support cross-origin XMLHttpRequest"; } var targetLocation = "http://geodata.example.net:9999/upload"; 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 + "% uploaded"); } xhr.onprogress = function(e) { var ratio = e.loaded / e.total; setProgress(ratio + "% downloaded"); } xhr.onload = function(e) { setProgress("finished"); } xhr.onerror = function(e) { setProgress("error"); } 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>Geolocation Data to upload:</h4> <textarea id="geodata"> </textarea> </div> <button id="sendButton">Upload</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>Status: </b> <span id="progress">ready</span> </p>
部署应用
示例代码依赖于两个前提条件: 首先,页面不能同域,其次,目标页面必须由能够解析CORS头部的Web服务器来提供。本章的示例代码中包括支持CORS 的Python脚本,它可以处理传入的跨源的XMLHttpRequests。你可以在本机按以下步骤进行配置。
(1)更新主机文件(Windows中的C:\Windows\system32\drivers\etc\hosts,Unit/Linux中的/etc/hosts文件),添加如下两个指向localhost(IP地址为127.0.0.1)的项:
127.0.0.1.geodata.example.net 127.0.0.1.portal.example.com
提示:hosts文件修改完成后,必须重启浏览器以确保该DNS记录生效。
如果在学习之前的示例时没有安装Python,则需要安装包括了轻量级SimpleHTTPServer Web服务器的Python环境。
(3)打开包含示例文件(crossOriginUpload.html )和COR系统服务器Python脚本(CORSServer.py)的目录。
(4)运行Python脚本:
Python CORSServer.py9999
打开浏览器。输入http://portal.example.com:9999/crossOriginUploaad.html后即可看到图5-5所示的页面。
5.3 进阶功能
尽管有些技巧我们在示例中用不上,但这并不妨碍它们在多种类型的HTML5Web应用中发挥作用。本节,我们就来介绍一些简单实用的进阶功能。
5.3.1 结构化的数据
早期版本的postMessage仅支持字符串。.后来的版本支持JavaScript对象、canvas imageData和文件等其他数据类型。由于不同浏览器对规范支持的差异。对不同的对象类型的支持情况也不同。
在一些浏览器中,对借由postMessage发送的JavaScript对象的限制同对JSON数据的限制是相同的。具体来讲,可能不允许循环数据结构,如循环链表。
5.3.2 Framebusting
framebusting技术可以用来保证某些内容不被加载到iframe中。应用程序首先检测所在的窗口是否为最外层的窗口(window.top),若不是则跳脱包含他的框架,代码如下所示:
if(window ! = window.top){ Window.top.location = location: }
不过,你可能会希望借助iframe 导人一些确定的合作网站页面来充实自身的内容,这里有一种解决方案,即使用postMessage但实现iframe 与其父页面间的握手通信,如代码清单5- 10 所示。
代码清单5-10 在iframe中使用postMessage实现互信页面握手通信
var framebustTimer; var timeout = 3000; //超时时间设为3s if(window ! == window.top){ framebustTimer = setTimeout( function(){ window.top.location = location; },timeout); } window.addEventListener("message",function(e){ Switch(e.origin){ Case trustedFramer: clearTimeout(framebustTimer); Break; } ),true);
5.4 小结
本掌主要介绍了如何使用跨文档消息通信和XMLHttpRequest Level2 创建引人注目的应用,这些应用能够安全地进行跨源通信。
首先,我们讨论了postMessage和源安全的概念,这是HTML5 Communication规范的两个关键元素。接下来。我们演示了如何利用postMessage API 实现iframe、标签页和窗口间通信。
随后,我们讨论了XMLHttpRequest Leve12, 作为XMLHttpRequest的升级版,它对
XMLHttpRequest进行了多方面的改进。而最为重要的是对于readystatechange事件方面的改进。然后。我们介绍了如何使用XMLHttpRequest创建跨源请求,以及如何使用新的进度事件。
最后,我们用一些实倒结束了本掌的讲解。下一章,我们将介绍HTML5 WebSockets,基于它能够轻松高效地将实时数据流推送给Web应用。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论