现代的 web 应用程序都是基于各种 Ajax 相关概念。Ajax 技术的使用导致 web 页面上交互式或动态接口的增加。Ajax 革命始于这样的观念,即 web 应用程序可以在后台从服务器异步检索数据,web 页面与服务器之间的交互不仅限于获取页面的时刻。web 页面的概念又延伸到长效的 web 应用程序,用户与应用程序后台持续通信,与其交互。 这种持续通信能实现的例子有:
- 发送和接受信息
- 点对点输入验证(例如,密码强度)
- 根据规则和服务器上进行的分析自动完成用户输入
为了执行客户端-服务器交互相关任务,应用程序要需要一个优化的通信层,能为每个通信任务提供合适的通信机制。
在本文中,了解这些需要在构造通信层时考虑的问题,并探索应在其中建立的不同机制。
以前,web 页面和服务器之间的通信很不规范。就用些不同的 HTML 元素,而这些元素原本不是这么用的。这些元素允许这么用(或者说是滥用),其关键原因在于它们原本是用于从服务器上获取文件的。然后,浏览器负责根据元素类型解释文件。这些元素是:
- 获取图像文件
- 获取 JavaScript™ 文件
- 显示,也可能是获取 HTML 文件
img
script
iframe
这些元素原本用作网站的标记。然而,通过使用 JavaScript 做一些 DOM 操作,可以动态方式注入元素,并在页面生命周期中与服务器交互。接下来的三节将描述如何使用这些元素。
用后不管:滥用 <img> 元素
img
元素的主要用法是从服务器获取一个图像并显示。为了获取图像,浏览器创建一个 GET 请求,其中的 URL 是元素的 src
属性值。值不限于浏览器的 “同源策略”;因此图像的 URL不 限于显示图像的域。
能做和不能做的
可以使用 img
元素来执行任何 URL 的 GET 调用。然后,逻辑上,可以调用不同服务器的服务。
尽管如此,浏览器认为 GET 调用的响应是一个图像文件并按此处理(显示在屏幕上)。如果响应实际不是图像文件,就不能将 img 元素加入 DOM 树,因为 GET 请求在 src
属性设置时创建(不管 img 元素是否添加到页面中)。
可以将 img
元素主要用于 “用后不管” 类型的服务,这种服务中服务器返回的响应不管客户端。这适合于遵守 “发生在服务器,留在服务器” 规则的服务。
清单 1 显示了如何执行用后不管的调用。
请 1. 调用用后不管的服务
<script type=”text/javascript”> function fireAndForgetService(targetUrl){ // first we create an img object var imgNode = document.createElement(“img”); // then we set its src attribute to the url of the service we’d like to invoke // when the next code line is executed the browser creates a GET request // and sends it to targetURL imgNode.src = targetUrl; } // calling the function with any url – cross site scripting is possible here fireAndForgetService(“http://www.theTargetUrl.com/doSomething?param1Name:param1Value”); </script>
获取和执行:使用 <script> 元素
前面提到的关于 img
元素的所有情况对 script
元素同样适用。该通信机制同样不受 “同源策略” 限制。
<img
> 与 <script
> 元素的不同之处在于,当使用 script
元素时,浏览器希望接收到可执行的 JavaScript 代码。这是一个非常强大的机制,不应轻易使用。获取的脚本被调用,能访问页面可访问的一切内容,包括 cookies、DOM、历史等等。
能做和不能做的
应当在浏览器运行代码和获取的脚本之间定义某种协议。如果从自己域中获取脚本,其代码很可能熟悉应用程序代码的各种函数、工具和限制。协议可 基本上由这两个相同应用程序的组件集成。服务器能接收部分代码,这些代码作为请求的一部分,能在浏览器中执行各种操作,例如处理国际化限制和其他用户指定 的调整。
当从其他服务器获取代码,会更有趣。这种情况下(如果该域是可信的),获取的代码不熟悉应用程序结构,因此获取的只有静态代码。要在应用程序中处理 这些数据,需要将获取的内容与应用程序代码集成。可以使用 JSONP 概念,它基本上指出获取的代码是一个 JSON 对象,其中包装(或填充)有接受该对象的函数调用。(这个调用就是回调。)函数名作为获取的脚本的 URL 一部分的 URL 参数发送,一直送到接收者域以提供包装该函数名的 JSON 对象。
清单 2 演示了如何执行基于 JSONP 的客户端-服务器通信。
清单 2. JSONP 方法
<script type=”text/javascript”> // the next function receives the following arguments: // targetUrl – the url from which data is fetched and handled later as // jsonp // jsonpName – the name of the url parameter that the target url accepts and // knows to read from the callback name // callbackName – the name of the function that will handle the returned // json object function invokeJSONP(targetUrl, jsonpName ,callbackName){ // first we create a script object var scriptNode = document.createElement(“script”); // set its type so upon return it would be executed scriptNode.type = “text/javascript”; // set its src attribute to the url of the fetched script // and add to the url the callback name scriptNode.src = targetUrl+”?”+ targetDomainJsonpName+”=”+callbackName; // adding the script to the page to get it up and running document.getElementsByTagName(“head”)[0].appendChild(scriptNode); } // calling the function with any url – cross site scripting is possible here function handleJsonp(infoObject){ validateInfoObject(infoObject); handleInfoObject(infoObject); } invokeJSONP (“http://targetUrl.com/provideJsonpData”,“jsonpCallback”, “handleJsonp”); </script> |
一个微小的差别值得一提。当使用 <img
> 元素时,不需要将其加入 DOM 树。相反,当获取脚本时,将不会生成 GET 请求,除非 <script
> 元素已被添加到 DOM 树。
由目标域决定是否将 API 发布给 JSONP 调用,尤其是应用程序中的参数名,它应当提供回调名。API 的其他部分应包含在响应主体中发送的 JSON 对象的结构。
清单 2 中,目标域接受了一个 URL 参数,叫做 jsonpCallback
(不同域中名称可能不同),它希望该域返回一个调用 handleJsonp
的脚本。脚本看上去像清单 3。
清单 3. 从目标域返回的 JSONP
handleJsonp( { ‘height’:185, ‘units’:’cm’, ‘age’: 30, ‘favoriteFruit’:’apple’, ‘likesDogs’: true } );
请求中间人帮助:使用 <iframe> 元素
iframe
元素可让您将页面嵌入页面中。如果两个页面来自同一个域,就可能互相通信和传输信息。
在 Ajax 应用程序中,通常用这种模式将用户交互与客户端-服务器通信的角色分隔开。这通过使用位于主页面中的 iframe
元素。该元素对用户隐藏,不会影响应用程序中的任何用户交互活动。
这种联合作业允许您从服务器取得任意种类的内容,让您完成提交而无需刷新页面 — 所有都在幕后完成。
能做和不能做的
与此前的由元素组成页面的机制不同,iframe
元素本身就是页面。它不限于某一种特定内容(如图像)或过程(执行接收内容)。因此,任何类型的数据都能从服务器检索、发送给服务器。又由于可以接收任何内容,就可以根据任何可能格式的数据执行交互,这给予了服务器端更大的灵活性。可基于表单提交发送数据到 服务器。除了 GET ,还可用 POST 请求。也可以是多部分的请求,因此客户端机器的文件可以上传到服务器。(请记住,所有页面刷新会阻塞与用户的交互。)
使用 iframe
也不是完美无缺的。由于跨站点调用也有效,应用程序的安全性可能会很脆弱。另外,基于此机制的交互都会压入页面的历史对象,这会让用户在用 Back/Forward 动作导航时感到疑惑。
使用 iframe
以编程的方式使用 iframe
元素从服务器获取内容与使用 script
元素相似 — 但有一点重要的不同之处。创建 script
元素并连接到页面后,需要将其隐藏,从而用户不会因其出现在屏幕上而感到疑惑。
可以使用 iframe
元素作为表单提交的目标,从而避免由于表单提交导致的页面刷新。
清单 4 显示如何使用作为应用程序一部分的 iframe
元素上传文件,而非以编程方式执行客户端 - 服务器通信。
清单 4. 使用 <iframe> 上传文件
<!—a hidden iframe that is the target of the form that would be used to upload a file --> <iframe id="IFrame" name="IFrame" style="width:0px; height:0px; border:0px" src="blank.html"> </iframe> <!—the form is connected to the previous iframe by the target attribute, thus basically reloading that hidden frame upon form submission --> <form name="UploadFile" target="IFrame" method="POST" action="http://myServer/fileUploadServiceURL" enctype="multipart/form-data"> <input type="file" name="uploadFileNameId"/> <input type="submit" value="Upload" name="submit"/> </form>
以 Ajax 方式来做
基于 Ajax 的 web 应用程序通常作为一个应用程序客户端在服务器上运行,这会形成一个通信密集的应用程序,向服务器来回传送数据。需要提供数据传输的机制。作为应用程序的核 心部件,机制应当是轻量的,尽可能安全。幸运的是,所有现代的浏览器都配有可切实用于该目的的对象:XMLHttpRequest (XHR)。XHR 是轻量级的,它创建一个仅限于页面检索来源服务器的请求。它消除了所有跨站点问题,且可以传送文本。
能做和不能做的
使用 XHR 对象能让应用程序像服务器发送信息并获取数据。这种机制相比前面描述的机制而言,有几个优势:
-
可以使用任意类型的 HTTP 方法
- 因为最常见的现代服务器架构都是基于 REST 原则,因此需要使用 GET、PUT、POST 和 DELETE 请求。在客户端-服务器通信内核使用 XHR 来满足 REST 风格服务器架构。 完成后通知
-
XHR 对象可存在于几种状态,从创建到响应完全加载。发生每一个状态变化,都会激发一个事件,可以对每个即将发生的状态变化定义一个回调函数来调用。
该事件处理函数让您确保依赖于从服务器获得数据的代码在数据可用时立即执行。
不干预页面历史
- XHR 调用不反映在页面历史对象中,因此它不会偏离浏览器的 Back 和 Forward 动作的通常用法。 不允许跨站点脚本 (XSS)
- 应用程序更加安全,因为它缺少执行 XSS 的能力。 是否阻塞
-
可以定义的请求-响应周期是:
- 同步,代码执行时浏览器等待响应
- 异步,当响应到达时执行回调
XML 和任何其他格式
- 在响应是 XML 格式的情况下,将创建完整的 DOM 树用于响应数据。响应的原始文本内容也可用(响应以其他格式,如 JSON,到达时,也可以处理)。
还有,XHR 对象也不是完美无缺的。因为只可以发送文本,还需要使用 iframe
来上传文件到服务器。另外,在不同浏览器中创建的对象处理方式也不同。
结束语
客户端-服务器交互是所有现代 web 应用程序的主干。本文中,您学习了可用于应用程序交互的通用机制。不是每个应用程序都要用到这里讨论的所有机制。由您来决定要用哪个,如何使用。
很多 JavaScript 框架都含有 Ajax 通信机制的部分或完全实现。在您决定要用哪个框架、实现哪个机制时请考虑一下。