FreeZone

            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            4 隨筆 :: 0 文章 :: 2 評(píng)論 :: 0 Trackbacks

          2005年10月30日 #

          在 Web 應(yīng)用程序開發(fā)中,頁(yè)面重載循環(huán)是最大的一個(gè)使用障礙,對(duì)于 Java? 開發(fā)人員來(lái)說也是一個(gè)嚴(yán)峻的挑戰(zhàn)。在這個(gè)系列中,作者 Philip McCarthy 介紹了一種創(chuàng)建動(dòng)態(tài)應(yīng)用程序體驗(yàn)的開創(chuàng)性方式。Ajax(異步 JavaScript 和 XML)是一種編程技術(shù),它允許為基于 Java 的 Web 應(yīng)用程序把 Java 技術(shù)、XML 和 JavaScript 組合起來(lái),從而打破頁(yè)面重載的范式。

          Ajax(即異步 JavaScript 和 XML)是一種 Web 應(yīng)用程序開發(fā)的手段,它采用客戶端腳本與 Web 服務(wù)器交換數(shù)據(jù)。所以,不必采用會(huì)中斷交互的完整頁(yè)面刷新,就可以動(dòng)態(tài)地更新 Web 頁(yè)面。使用 Ajax,可以創(chuàng)建更加豐富、更加動(dòng)態(tài)的 Web 應(yīng)用程序用戶界面,其即時(shí)性與可用性甚至能夠接近本機(jī)桌面應(yīng)用程序。

          Ajax 不是一項(xiàng)技術(shù),而更像是一個(gè) 模式 —— 一種識(shí)別和描述有用的設(shè)計(jì)技術(shù)的方式。Ajax 是新穎的,因?yàn)樵S多開發(fā)人員才剛剛開始知道它,但是所有實(shí)現(xiàn) Ajax 應(yīng)用程序的組件都已經(jīng)存在若干年了。它目前受到重視是因?yàn)樵?2004 和 2005 年出現(xiàn)了一些基于 Ajax 技術(shù)的非常棒的動(dòng)態(tài) Web UI,最著名的就是 Google 的 GMail 和 Maps 應(yīng)用程序,以及照片共享站點(diǎn) Flickr。這些用戶界面具有足夠的開創(chuàng)性,有些開發(fā)人員稱之為“Web 2.0”,因此對(duì) Ajax 應(yīng)用程序的興趣飛速上升。

          在這個(gè) 系列中,將提供使用 Ajax 開發(fā)應(yīng)用程序需要的全部工具 。在第一篇文章中,將解釋 Ajax 背后的概念,演示為基于 Java 的 Web 應(yīng)用程序創(chuàng)建 Ajax 界面的基本步驟。我將使用代碼示例演示讓 Ajax 應(yīng)用程序如此動(dòng)態(tài)的服務(wù)器端 Java 代碼和客戶端 JavaScript。最后,將指出 Ajax 方式的一些不足,以及在創(chuàng)建 Ajax 應(yīng)用程序時(shí)應(yīng)當(dāng)考慮的一些更廣的可用性和訪問性問題。

          更好的購(gòu)物車

          可 以用 Ajax 增強(qiáng)傳統(tǒng)的 Web 應(yīng)用程序,通過消除頁(yè)面裝入從而簡(jiǎn)化交互。為了演示這一點(diǎn),采用一個(gè)簡(jiǎn)單的購(gòu)物車示例,在向里面添加項(xiàng)目時(shí),它會(huì)動(dòng)態(tài)更新。這項(xiàng)技術(shù)如果整合到在線商 店,那么用戶可以持續(xù)地瀏覽和向購(gòu)物車中添加項(xiàng)目,而不必在每次點(diǎn)擊之后都等候完整的頁(yè)面更新。雖然這篇文章中的有些代碼特定于購(gòu)物車示例,但是演示的技 術(shù)可以應(yīng)用于任何 Ajax 應(yīng)用程序。清單 1 顯示了購(gòu)物車示例使用的有關(guān) HTML 代碼,整篇文章中都會(huì)使用這個(gè) HTML。


          清單1. 購(gòu)物車示例的有關(guān)片斷

          <!-- Table of products from store's catalog, one row per item -->
          <th>Name</th> <th>Description</th> <th>Price</th> <th></th>
          ...
          <tr>
          <!-- Item details -->
          <td>Hat</td> <td>Stylish bowler hat</td> <td>$19.99</td>
          <td>
          <!-- Click button to add item to cart via Ajax request -->
          <button onclick="addToCart('hat001')">Add to Cart</button>
          </td>
          </tr>
          ...

          <!-- Representation of shopping cart, updated asynchronously -->
          <ul id="cart-contents">

          <!-- List-items will be added here for each item in the cart -->

          </ul>

          <!-- Total cost of items in cart displayed inside span element -->
          Total cost: <span id="total">$0.00</span>





          Ajax 往返過程

          Ajax 交互開始于叫作 XMLHttpRequest 的 JavaScript 對(duì)象。顧名思義,它允許客戶端腳本執(zhí)行 HTTP 請(qǐng)求,并解析 XML 服務(wù)器響應(yīng)。Ajax 往返過程的第一步是創(chuàng)建 XMLHttpRequest 的實(shí)例。在 XMLHttpRequest 對(duì)象上設(shè)置請(qǐng)求使用的 HTTP 方法(GETPOST)以及目標(biāo) URL。

          現(xiàn)在,您還記得 Ajax 的第一個(gè) a 是代表 異步(asynchronous) 嗎?在發(fā)送 HTTP 請(qǐng)求時(shí),不想讓瀏覽器掛著等候服務(wù)器響應(yīng)。相反,您想讓瀏覽器繼續(xù)對(duì)用戶與頁(yè)面的交互進(jìn)行響應(yīng),并在服務(wù)器響應(yīng)到達(dá)時(shí)再進(jìn)行處理。為了實(shí)現(xiàn)這個(gè)要求,可以在 XMLHttpRequest 上注冊(cè)一個(gè)回調(diào)函數(shù),然后異步地分派 XMLHttpRequest。然后控制就會(huì)返回瀏覽器,當(dāng)服務(wù)器響應(yīng)到達(dá)時(shí),會(huì)調(diào)用回調(diào)函數(shù)。

          在 Java Web 服務(wù)器上,請(qǐng)求同其他 HttpServletRequest 一樣到達(dá)。在解析了請(qǐng)求參數(shù)之后,servlet 調(diào)用必要的應(yīng)用程序邏輯,把響應(yīng)序列化成 XML,并把 XML 寫入 HttpServletResponse

          回到客戶端時(shí),現(xiàn)在調(diào)用注冊(cè)在 XMLHttpRequest 上的回調(diào)函數(shù),處理服務(wù)器返回的 XML 文檔。最后,根據(jù)服務(wù)器返回的數(shù)據(jù),用 JavaScript 操縱頁(yè)面的 HTML DOM,把用戶界面更新。圖 1 是 Ajax 往返過程的順序圖。


          圖 1. Ajax 往返過程
           Ajax 往返過程的順序圖

          現(xiàn)在您對(duì) Ajax 往返過程有了一個(gè)高層面的認(rèn)識(shí)。下面我將放大其中的每一步驟,進(jìn)行更詳細(xì)的觀察。如果過程中迷了路,請(qǐng)回頭看圖 1 —— 由于 Ajax 方式的異步性質(zhì),所以順序并非十分簡(jiǎn)單。





          分派 XMLHttpRequest

          我將從 Ajax 序列的起點(diǎn)開始:創(chuàng)建和分派來(lái)自瀏覽器的 XMLHttpRequest。不幸的是,不同的瀏覽器創(chuàng)建 XMLHttpRequest 的方法各不相同。清單 2 的 JavaScript 函數(shù)消除了這些依賴于瀏覽器的技巧,它可以檢測(cè)當(dāng)前瀏覽器要使用的正確方式,并返回一個(gè)可以使用的 XMLHttpRequest。最好是把它當(dāng)作輔助代碼:只要把它拷貝到 JavaScript 庫(kù),并在需要 XMLHttpRequest 的時(shí)候使用它就可以了。


          清單 2. 創(chuàng)建跨瀏覽器的 XMLHttpRequest

          /*
          * Returns a new XMLHttpRequest object, or false if this browser
          * doesn't support it
          */
          function newXMLHttpRequest() {

          var xmlreq = false;

          if (window.XMLHttpRequest) {

          // Create XMLHttpRequest object in non-Microsoft browsers
          xmlreq = new XMLHttpRequest();

          } else if (window.ActiveXObject) {

          // Create XMLHttpRequest via MS ActiveX
          try {
          // Try to create XMLHttpRequest in later versions
          // of Internet Explorer

          xmlreq = new ActiveXObject("Msxml2.XMLHTTP");

          } catch (e1) {

          // Failed to create required ActiveXObject

          try {
          // Try version supported by older versions
          // of Internet Explorer

          xmlreq = new ActiveXObject("Microsoft.XMLHTTP");

          } catch (e2) {

          // Unable to create an XMLHttpRequest with ActiveX
          }
          }
          }

          return xmlreq;
          }

          稍后將討論處理那些不支持 XMLHttpRequest 的瀏覽器的技術(shù)。目前,示例假設(shè)清單 2 的 newXMLHttpRequest 函數(shù)總能返回 XMLHttpRequest 實(shí)例。

          返回示例的購(gòu)物車場(chǎng)景,想要當(dāng)用戶在目錄項(xiàng)目上點(diǎn)擊 Add to Cart 時(shí)啟動(dòng) Ajax 交互。名為 addToCart()onclick 處理函數(shù)負(fù)責(zé)通過 Ajax 調(diào)用來(lái)更新購(gòu)物車的狀態(tài)(請(qǐng)參閱 清單 1)。正如清單 3 所示,addToCart() 需要做的第一件事是通過調(diào)用清單 2 的 newXMLHttpRequest() 函數(shù)得到 XMLHttpRequest 對(duì)象。接下來(lái),它注冊(cè)一個(gè)回調(diào)函數(shù),用來(lái)接收服務(wù)器響應(yīng)(我稍后再詳細(xì)解釋這一步;請(qǐng)參閱 清單 6)。

          因?yàn)檎?qǐng)求會(huì)修改服務(wù)器上的狀態(tài),所以將用 HTTP POST 做這個(gè)工作。通過 POST 發(fā)送數(shù)據(jù)要求三個(gè)步驟。第一,需要打開與要通信的服務(wù)器資源的 POST 連接 —— 在這個(gè)示例中,服務(wù)器資源是一個(gè)映射到 URL cart.do 的 servlet。然后,我在 XMLHttpRequest 上設(shè)置一個(gè)頭,指明請(qǐng)求的內(nèi)容是表單 編碼的數(shù)據(jù)。最后,用表單編碼的數(shù)據(jù)作為請(qǐng)求體發(fā)送請(qǐng)求。

          清單 3 把這些步驟放在了一起。


          清單 3. 分派 Add to Cart XMLHttpRequest

          /*
          * Adds an item, identified by its product code, to the shopping cart
          * itemCode - product code of the item to add.
          */
          function addToCart(itemCode) {

          // Obtain an XMLHttpRequest instance
          var req = newXMLHttpRequest();

          // Set the handler function to receive callback notifications
          // from the request object
          var handlerFunction = getReadyStateHandler(req, updateCart);
          req.onreadystatechange = handlerFunction;

          // Open an HTTP POST connection to the shopping cart servlet.
          // Third parameter specifies request is asynchronous.
          req.open("POST", "cart.do", true);

          // Specify that the body of the request contains form data
          req.setRequestHeader("Content-Type",
          "application/x-www-form-urlencoded");

          // Send form encoded data stating that I want to add the
          // specified item to the cart.
          req.send("action=add&item="+itemCode);
          }

          這就是建立 Ajax 往返過程的第一部分,即創(chuàng)建和分派來(lái)自客戶機(jī)的 HTTP 請(qǐng)求。接下來(lái)是用來(lái)處理請(qǐng)求的 Java servlet 代碼。





          servlet 請(qǐng)求處理

          用 servlet 處理 XMLHttpRequest,與處理普通的瀏覽器 HTTP 請(qǐng)求一樣。可以用 HttpServletRequest.getParameter() 得到在 POST 請(qǐng)求體中發(fā)送的表單編碼數(shù)據(jù)。Ajax 請(qǐng)求被放進(jìn)與來(lái)自應(yīng)用程序的常規(guī) Web 請(qǐng)求一樣的 HttpSession 中。對(duì)于示例購(gòu)物車場(chǎng)景來(lái)說,這很有用,因?yàn)檫@可以把購(gòu)物車狀態(tài)封裝在 JavaBean 中,并在請(qǐng)求之間在會(huì)話中維持這個(gè)狀態(tài)。

          清單 4 是處理 Ajax 請(qǐng)求、更新購(gòu)物車的簡(jiǎn)單 servlet 的一部分。Cart bean 是從用戶會(huì)話中獲得的,并根據(jù)請(qǐng)求參數(shù)更新它的狀態(tài)。然后 Cart 被序列化成 XML,XML 又被寫入 ServletResponse。重要的是把響應(yīng)的內(nèi)容類型設(shè)置為 application/xml,否則 XMLHttpRequest 不會(huì)把響應(yīng)內(nèi)容解析成 XML DOM。


          清單 4. 處理 Ajax 請(qǐng)求的 servlet 代碼

          public void doPost(HttpServletRequest req, HttpServletResponse res)
          throws java.io.IOException {

          Cart cart = getCartFromSession(req);

          String action = req.getParameter("action");
          String item = req.getParameter("item");

          if ((action != null)&&(item != null)) {

          // Add or remove items from the Cart
          if ("add".equals(action)) {
          cart.addItem(item);

          } else if ("remove".equals(action)) {
          cart.removeItems(item);

          }
          }

          // Serialize the Cart's state to XML
          String cartXml = cart.toXml();

          // Write XML to response.
          res.setContentType("application/xml");
          res.getWriter().write(cartXml);
          }

          清單 5 顯示了 Cart.toXml() 方法生成的示例 XML。它很簡(jiǎn)單。請(qǐng)注意 cart 元素的 generated 屬性,它是 System.currentTimeMillis() 生成的一個(gè)時(shí)間戳。


          清單 5. Cart 對(duì)象的XML 序列化示例

          <?xml version="1.0"?>
          <cart generated="1123969988414" total="$171.95">
          <item code="hat001">
          <name>Hat</name>
          <quantity>2</quantity>
          </item>
          <item code="cha001">
          <name>Chair</name>
          <quantity>1</quantity>
          </item>
          <item code="dog001">
          <name>Dog</name>
          <quantity>1</quantity>
          </item>
          </cart>

          如果查看應(yīng)用程序源代碼(可以從 下載 一節(jié)得到)中的 Cart.java,可以看到生成 XML 的方式只是把字符串添加在一起。雖然對(duì)這個(gè)示例來(lái)說足夠了,但是對(duì)于從 Java 代碼生成 XML 來(lái)說則是最差的方式。這個(gè)系列的下一期中介紹一些更好的方式。

          現(xiàn)在您已經(jīng)知道了 CartServlet 響應(yīng) XMLHttpRequest 的方式。下一件事就是返回客戶端,查看如何用 XML 響應(yīng)更新頁(yè)面狀態(tài)。





          用 JavaScript 進(jìn)行響應(yīng)處理

          XMLHttpRequestreadyState 屬性是一個(gè)數(shù)值,它指出請(qǐng)求生命周期的狀態(tài)。它從 0(代表“未初始化”)變化到 4(代表“完成”)。每次 readyState 變化時(shí),readystatechange 事件就觸發(fā),由 onreadystatechange 屬性指定的事件處理函數(shù)就被調(diào)用。

          清單 3 中已經(jīng)看到了如何調(diào)用 getReadyStateHandler() 函數(shù)創(chuàng)建事件處理函數(shù)。然后把這個(gè)事件處理函數(shù)分配給 onreadystatechange 屬性。getReadyStateHandler() 利用了這樣一個(gè)事實(shí):函數(shù)是 JavaScript 中的一級(jí)對(duì)象。這意味著函數(shù)可以是其他函數(shù)的參數(shù),也可以創(chuàng)建和返回其他函數(shù)。getReadyStateHandler() 的工作是返回一個(gè)函數(shù),檢查 XMLHttpRequest 是否已經(jīng)完成,并把 XML 響應(yīng)傳遞給調(diào)用者指定的事件處理函數(shù)。清單 6 是 getReadyStateHandler() 的代碼。


          清單 6. getReadyStateHandler() 函數(shù)

          /*
          * Returns a function that waits for the specified XMLHttpRequest
          * to complete, then passes its XML response to the given handler function.
          * req - The XMLHttpRequest whose state is changing
          * responseXmlHandler - Function to pass the XML response to
          */
          function getReadyStateHandler(req, responseXmlHandler) {

          // Return an anonymous function that listens to the
          // XMLHttpRequest instance
          return function () {

          // If the request's status is "complete"
          if (req.readyState == 4) {

          // Check that a successful server response was received
          if (req.status == 200) {

          // Pass the XML payload of the response to the
          // handler function
          responseXmlHandler(req.responseXML);

          } else {

          // An HTTP problem has occurred
          alert("HTTP error: "+req.status);
          }
          }
          }
          }

          HTTP 狀態(tài)碼

          在清單 6 中,檢查 XMLHttpRequeststatus 屬性以查看請(qǐng)求是否成功完成。status 包含服務(wù)器響應(yīng)的 HTTP 狀態(tài)碼。在執(zhí)行簡(jiǎn)單的 GETPOST 請(qǐng)求時(shí),可以假設(shè)任何大于 200 (OK)的碼都是錯(cuò)誤。如果服務(wù)器發(fā)送重定向響應(yīng)(例如 301 或 302),瀏覽器會(huì)透明地進(jìn)行重定向并從新的位置獲取資源;XMLHttpRequest 看不到重定向狀態(tài)碼。而且,瀏覽器會(huì)自動(dòng)添加 Cache-Control: no-cache 頭到所有 XMLHttpRequest,這樣客戶代碼永遠(yuǎn)也不用處理 304(未經(jīng)修改)服務(wù)器響應(yīng)。

          關(guān)于 getReadyStateHandler()

          getReadyStateHandler() 是段相對(duì)復(fù)雜的代碼,特別是如果您不習(xí)慣閱讀 JavaScript 的話。但是通過把這個(gè)函數(shù)放在 JavaScript 庫(kù)中,就可以處理 Ajax 服務(wù)器響應(yīng),而不必處理 XMLHttpRequest 的內(nèi)部細(xì)節(jié)。重要的是要理解如何在自己的代碼中使用 getReadyStateHandler()

          清單 3 中看到了 getReadyStateHandler() 像這樣被調(diào)用:handlerFunction = getReadyStateHandler(req, updateCart)。在這個(gè)示例中,getReadyStateHandler() 返回的函數(shù)將檢查在 req 變量中的 XMLHttpRequest 是否已經(jīng)完成,然后用響應(yīng)的 XML 調(diào)用名為 updateCart 的函數(shù)。

          提取購(gòu)物車數(shù)據(jù)

          清單 7 是 updateCart() 本身的代碼。函數(shù)用 DOM 調(diào)用檢查購(gòu)物車的 XML 文檔,然后更新 Web 頁(yè)面(請(qǐng)參閱 清單 1),反映新的購(gòu)物車內(nèi)容。這里的重點(diǎn)是用來(lái)從 XML DOM 提取數(shù)據(jù)的調(diào)用。cart 元素的 generated 屬性是在 Cart 序列化為 XML 時(shí)生成的一個(gè)時(shí)間戳,檢查它可以保證新的購(gòu)物車數(shù)據(jù)不會(huì)被舊的數(shù)據(jù)覆蓋。Ajax 請(qǐng)求天生是異步的,所以這個(gè)檢查可以處理服務(wù)器響應(yīng)未按次序到達(dá)的情況。


          清單 7. 更新頁(yè)面,反映購(gòu)物車的 XML 文檔

          function updateCart(cartXML) {

          // Get the root "cart" element from the document
          var cart = cartXML.getElementsByTagName("cart")[0];

          // Check that a more recent cart document hasn't been processed
          // already
          var generated = cart.getAttribute("generated");
          if (generated > lastCartUpdate) {
          lastCartUpdate = generated;

          // Clear the HTML list used to display the cart contents
          var contents = document.getElementById("cart-contents");
          contents.innerHTML = "";

          // Loop over the items in the cart
          var items = cart.getElementsByTagName("item");
          for (var I = 0 ; I < items.length ; I++) {

          var item = items[I];

          // Extract the text nodes from the name and quantity elements
          var name = item.getElementsByTagName("name")[0]
          .firstChild.nodeValue;

          var quantity = item.getElementsByTagName("quantity")[0]
          .firstChild.nodeValue;

          // Create and add a list item HTML element for this cart item
          var li = document.createElement("li");
          li.appendChild(document.createTextNode(name+" x "+quantity));
          contents.appendChild(li);
          }
          }

          // Update the cart's total using the value from the cart document
          document.getElementById("total").innerHTML =
          cart.getAttribute("total");
          }

          到此,整個(gè) Ajax 往返過程完成了,但是您可能想讓 Web 應(yīng)用程序運(yùn)行一下查看實(shí)際效果(請(qǐng)參閱 下載 一節(jié))。這個(gè)示例非常簡(jiǎn)單,有很多需要改進(jìn)之處。例如,我包含了從購(gòu)物車中清除項(xiàng)目的服務(wù)器端代碼,但是無(wú)法從 UI 訪問它。作為一個(gè)好的練習(xí),請(qǐng)?jiān)囍趹?yīng)用程序現(xiàn)有的 JavaScript 代碼之上構(gòu)建出能夠?qū)崿F(xiàn)這個(gè)功能的代碼。





          使用 Ajax 的挑戰(zhàn)

          就像任何技術(shù)一樣,使用 Ajax 也有許多出錯(cuò)的可能性。我目前在這里討論的問題還缺乏容易的解決方案,但是會(huì)隨著 Ajax 的成熟而改進(jìn)。隨著開發(fā)人員社區(qū)增加開發(fā) Ajax 應(yīng)用程序的經(jīng)驗(yàn),將會(huì)記錄下最佳實(shí)踐和指南。

          XMLHttpRequest 的可用性

          Ajax 開發(fā)人員面臨的一個(gè)最大問題是:在沒有 XMLHttpRequest 可用時(shí)該如何響應(yīng)?雖然主要的現(xiàn)代瀏覽器都支持 XMLHttpRequest,但仍然有少數(shù)用戶的瀏覽器不支持,或者瀏覽器的安全設(shè)置阻止使用 XMLHttpRequest。如果開發(fā)的 Web 應(yīng)用程序要部署在企業(yè)內(nèi)部網(wǎng),那么可能擁有指定支持哪種瀏覽器的權(quán)力,從而可以認(rèn)為 XMLHttpRequest 總能使用。但是,如果要部署在公共 Web 上,那么就必須當(dāng)心,如果假設(shè) XMLHttpRequest 可用,那么就可能會(huì)阻止那些使用舊的瀏覽器、殘疾人專用瀏覽器和手持設(shè)備上的輕量級(jí)瀏覽器的用戶使用您的應(yīng)用程序。

          所以,您應(yīng)當(dāng)努力讓應(yīng)用程序“平穩(wěn)降級(jí)”,在沒有 XMLHttpRequest 支持的瀏覽器中也能夠工作。在購(gòu)物車的示例中,把應(yīng)用程序降級(jí)的最好方式可能是讓 Add to Cart 按鈕執(zhí)行一個(gè)常規(guī)的表單提交,刷新頁(yè)面來(lái)反映購(gòu)物車更新后的狀態(tài)。Ajax 的行為應(yīng)當(dāng)在頁(yè)面裝入的時(shí)候就通過 JavaScript 添加到頁(yè)面,只有在 XMLHttpRequest 可用時(shí)才把 JavaScript 事件處理函數(shù)附加到每個(gè) Add to Cart 按鈕。另一種方式是在用戶登錄時(shí)檢測(cè) XMLHttpRequest 是否可用,然后相應(yīng)地提供應(yīng)用程序的 Ajax 版本或基于表單的普通版本。

          可用性考慮

          關(guān)于 Ajax 應(yīng)用程序的某些可用性問題比較普遍。例如,讓用戶知道他們的輸入已經(jīng)注冊(cè)了可能是重要的,因?yàn)樯陈┕鈽?biāo)和 spinning 瀏覽器的常用反饋機(jī)制“throbber”對(duì) XMLHttpRequest 不適用。一種技術(shù)是用“Now updating...”類型的信息替換 Submit 按鈕,這樣用戶在等候響應(yīng)期間就不會(huì)反復(fù)單擊按鈕了。

          另 一個(gè)問題是,用戶可能沒有注意到他們正在查看的頁(yè)面的某一部分已經(jīng)更新了。可以使用不同的可視技術(shù),把用戶的眼球帶到頁(yè)面的更新區(qū)域,從而緩解這個(gè)問題。 由 Ajax 更新頁(yè)面造成的其他問題還包括:“破壞了”瀏覽器的后退按鈕,地址欄中的 URL 也無(wú)法反映頁(yè)面的整個(gè)狀態(tài),妨礙了設(shè)置書簽。請(qǐng)參閱 參考資料 一節(jié),獲得專門解決 Ajax 應(yīng)用程序可用性問題的文章。

          服務(wù)器負(fù)載

          用 Ajax 實(shí)現(xiàn)代替普通的基于表單的 UI,會(huì)大大提高對(duì)服務(wù)器發(fā)出的請(qǐng)求數(shù)量。例如,一個(gè)普通的 Google Web 搜索對(duì)服務(wù)器只有一個(gè)請(qǐng)求,是在用戶提交搜索表單時(shí)出現(xiàn)的。而 Google Suggest 試圖自動(dòng)完成搜索術(shù)語(yǔ),它要在用戶輸入時(shí)向服務(wù)器發(fā)送多個(gè)請(qǐng)求。在開發(fā) Ajax 應(yīng)用程序時(shí),要注意將要發(fā)送給服務(wù)器的請(qǐng)求數(shù)量以及由此造成的服務(wù)器負(fù)荷。降低服務(wù)器負(fù)載的辦法是,在客戶機(jī)上對(duì)請(qǐng)求進(jìn)行緩沖并且緩存服務(wù)器響應(yīng)(如果可 能的話)。還應(yīng)該嘗試將 Ajax Web 應(yīng)用程序設(shè)計(jì)為在客戶機(jī)上執(zhí)行盡可能多的邏輯,而不必聯(lián)絡(luò)服務(wù)器。

          處理異步

          非常重要的是,要理解無(wú)法保證 XMLHttpRequest 會(huì)按照分派它們的順序完成。實(shí)際上,應(yīng)當(dāng)假設(shè)它們不會(huì)按順序完成,并且在設(shè)計(jì)應(yīng)用程序時(shí)把這一點(diǎn)記在心上。在購(gòu)物車的示例中,使用最后更新的時(shí)間戳來(lái)確保新的購(gòu)物車數(shù)據(jù)不會(huì)被舊的數(shù)據(jù)覆蓋(請(qǐng)參閱 清單 7)。這個(gè)非常基本的方式可以用于購(gòu)物車場(chǎng)景,但是可能不適合其他場(chǎng)景。所以在設(shè)計(jì)時(shí)請(qǐng)考慮如何處理異步的服務(wù)器響應(yīng)。

          posted @ 2005-10-30 15:36 bluesky 閱讀(289) | 評(píng)論 (0)編輯 收藏

          如果您正在使用異步 JavaScript 和 XML(Ajax)進(jìn)行 Java? Web 開發(fā),那么您最關(guān)心的問題可能就是把數(shù)據(jù)從服務(wù)器傳遞給客戶機(jī)。在 面向 Java 開發(fā)人員的 Ajax 系列的第二篇文章中,Philip McCarthy 介紹了 Java 對(duì)象序列化的五種方式,并提供了選擇最適合應(yīng)用程序的數(shù)據(jù)格式和技術(shù)所需要的全部信息。

          在這個(gè)系列的 第一篇文章 中,我介紹了 Ajax 的構(gòu)造塊:

          • 如何用 JavaScript XMLHttpRequest 對(duì)象從 Web 頁(yè)面向服務(wù)器發(fā)送異步請(qǐng)求。
          • 如何用 Java servlet 處理和響應(yīng)請(qǐng)求(向客戶機(jī)返回 XML 文檔)。
          • 如何在客戶端用響應(yīng)文檔更新頁(yè)面視圖。

          這一次,將繼續(xù)討論 Ajax 開發(fā)的基礎(chǔ)知識(shí),但是將側(cè)重于許多 Java Web 開發(fā)人員最關(guān)心的問題:為客戶機(jī)生成數(shù)據(jù)。

          多 數(shù) Java 開發(fā)人員已經(jīng)把模型-視圖-控制器(MVC)模式應(yīng)用在他們的 Web 應(yīng)用程序上。在傳統(tǒng)的 Web 應(yīng)用程序中,視圖組件由 JSP 或者其他表示技術(shù)(例如 Velocity 模板)構(gòu)成。這些表示組件動(dòng)態(tài)地生成全新的 HTML 頁(yè)面,替代用戶以前正在查看的頁(yè)面,從而更新用戶界面。但是,在 Java Web 應(yīng)用程序使用 Ajax UI 的情況下,基于從 XMLHttpRequest 的響應(yīng)接收到的數(shù)據(jù),JavaScript 客戶端代碼對(duì)于更新用戶看到的內(nèi)容負(fù)有最終責(zé)任。從服務(wù)器的角度來(lái)看,視圖成為它響應(yīng)客戶機(jī)請(qǐng)求而發(fā)送的數(shù)據(jù)表示。

          這 篇文章側(cè)重于可以用來(lái)生成 Java 對(duì)象以數(shù)據(jù)為中心的視圖的技術(shù)。我將演示可以把 JavaBeans 變成 XML 文檔的各種方法,并且討論每種方法的優(yōu)劣。您將看到為什么 XML 并不總是最好的途徑:對(duì)于簡(jiǎn)單的 Ajax 請(qǐng)求來(lái)說,傳輸純文本更好。最后,我將介紹 JavaScript 對(duì)象標(biāo)注(JSON)。JSON 允許數(shù)據(jù)以序列化的 JavaScript 對(duì)象圖的形式傳輸,在客戶端代碼中處理序列化的 JavaScript 對(duì)象圖極為容易。

          關(guān)于示例

              使用一個(gè)示例應(yīng)用程序和幾個(gè)用例來(lái)演示這里討論的技術(shù)特性和技術(shù)。圖 1 顯示的極為簡(jiǎn)單的數(shù)據(jù)模型可以表示示例用例。這個(gè)模型代表在線商店中的顧客帳戶。顧客擁有以前訂單的集合,每個(gè)訂單包含幾個(gè)商品。


          圖 1. 簡(jiǎn)單的對(duì)象模型
          代表顧客帳戶的對(duì)象模型

          雖然 XMLHttpRequest 對(duì)于發(fā)送數(shù)據(jù)使用的格式?jīng)]有做任何限制,但是對(duì)于多數(shù)目的來(lái)說,只發(fā)送傳統(tǒng)的表單數(shù)據(jù)是適合的,所以我的討論集中在服務(wù)器的響應(yīng)上。響應(yīng)也可以有基于文本的格式,但是正如它的名字表示的,XMLHttpRequest 具有內(nèi)置的處理 XML 響應(yīng)數(shù)據(jù)的能力。這使 XML 成為 Ajax 響應(yīng)的默認(rèn)選擇,所以我們從 XML 格式開始討論。





          從 Java 類產(chǎn)生 XML

          把 Ajax 響應(yīng)作為 XML 來(lái)傳遞有許多原因:每個(gè)支持 Ajax 的瀏覽器都有導(dǎo)航 XML 文檔的方法,也有許多服務(wù)器端技術(shù)可以處理 XML 數(shù)據(jù)。通過制定一個(gè)方案,描述要交換的文檔類型,在 Ajax 客戶端和服務(wù)器端之間很容易定義合約,而且如果服務(wù)器端架構(gòu)采用面向服務(wù)的方式,那么使用 XML 也可以允許非 Ajax 客戶機(jī)使用您提供的數(shù)據(jù)。

              我們從 Java 對(duì)象產(chǎn)生 XML 數(shù)據(jù)的三種方法,并討論每種方法的優(yōu)劣。





          自行進(jìn)行序列化

          首先,可以從對(duì)象圖以編程的方式生成 XML。這種方式可以簡(jiǎn)單到只是在每個(gè) JavaBean 類中實(shí)現(xiàn) toXml() 方法即可。然后就可以選擇合適的 XML API,讓每個(gè) bean 提供表示自己狀態(tài)的元素,并遞歸地對(duì)自己的成員調(diào)用對(duì)象圖。顯然,這種方式無(wú)法擴(kuò)展到大量的類,因?yàn)槊總€(gè)類都需要專門編寫自己的 XML 生成代碼。從好的方面來(lái)看,這是一個(gè)實(shí)現(xiàn)起來(lái)簡(jiǎn)單的方式,沒有額外的配置支出或者更復(fù)雜的構(gòu)建過程支出,任何 JavaBean 圖都可以只用幾個(gè)調(diào)用就變成 XML 文檔。

               前一篇文章 的示例代碼中,把 XML 標(biāo)記字符串連接在一起,實(shí)現(xiàn)了 toXml() 方法。上次我就提到過,這是個(gè)糟糕的方法,因?yàn)樗汛_保標(biāo)記配對(duì)、實(shí)體編碼等工作的負(fù)擔(dān)放在每個(gè) toXml() 方法的代碼中。在 Java 平臺(tái)上有幾個(gè) XML API 可以替您做這些工作,這樣您就可以把精力集中在 XML 的內(nèi)容上。清單 1 用 JDOM API 實(shí)現(xiàn)了在線商店示例中表示訂單的類中的 toXml()(請(qǐng)參閱 圖 1)。


          清單 1. Order 類的 toXml() 的 JDOM 實(shí)現(xiàn)

          public Element toXml() {

          Element elOrder = new Element("order");
          elOrder.setAttribute("id",id);

          elOrder.setAttribute("cost",getFormattedCost());

          Element elDate = new Element("date").addContent(date);
          elOrder.addContent(elDate);

          Element elItems = new Element("items");
          for (Iterator iter =
          items.iterator() ; iter.hasNext() ; ) {
          elItems.addContent(iter.next().toXml());
          }
          elOrder.addContent(elItems);

          return elOrder;
          }

          在這里可以看到用 JDOM 創(chuàng)建元素、使用屬性和添加元素內(nèi)容有多么簡(jiǎn)單。遞歸地調(diào)用復(fù)合 JavaBean 的 toXml() 方法是為了取得它們子圖的 Element 表示。例如,items 元素的內(nèi)容是通過調(diào)用 Order 聚合的每個(gè) Item 對(duì)象上的 toXml() 得到的。

          一旦所有的 JavaBean 都實(shí)現(xiàn)了 toXml() 方法,那么把任意對(duì)象圖序列化成 XML 文檔并返回給 Ajax 客戶機(jī)就簡(jiǎn)單了,如清單 2 所示。


          清單 2. 從 JDOM 元素生成 XML 響應(yīng)

          public void doGet(HttpServletRequest req, HttpServletResponse res)
          throws java.io.IOException, ServletException {

          String custId = req.getParameter("username");
          Customer customer = getCustomer(custId);

          Element responseElem = customer.toXml();
          Document responseDoc = new Document(responseElem);

          res.setContentType("application/xml");
          new XMLOutputter().output(responseDoc,res.getWriter());
          }

          JDOM 再次把工作變得非常簡(jiǎn)單。只需要在對(duì)象圖返回的 XML 元素外面包裝一個(gè) Document,然后用 XMLOutputter 把文檔寫入 servlet 響應(yīng)即可。清單 3 顯示了用這種方式生成的 XML 示例,用 JDOM Format.getPrettyFormat() 對(duì) XMLOutputter 進(jìn)行初始化,格式化得非常好。在這個(gè)示例中,顧客只做了一個(gè)訂單,包含兩個(gè)商品。


          清單 3. 代表顧客的 XML 文檔



          James Hyrax


          08-26-2005


          Oolong 512MB CF Card
          512 Megabyte Type 1 CompactFlash card.
          Manufactured by Oolong Industries

          $49.99


          Fujak Superpix72 Camera
          7.2 Megapixel digital camera featuring six
          shooting modes and 3x optical zoom. Silver.

          $299.99






          自行序列化的不足

          有趣的是,清單 3 中的代碼展示了讓 JavaBean 把自己序列化為 XML 的一個(gè)主要不足。假設(shè)要用這個(gè)文檔表示顧客的訂單歷史視圖。在這種情況下,不太可能要顯示每個(gè)歷史訂單中每個(gè)商品的完整說明,或者告訴顧客他或她自己的姓名。但是如果應(yīng)用程序有一個(gè) ProductSearch 類,它就是以 Item bean 列表的形式返回搜索結(jié)果,那么在 Item 的 XML 表示中包含說明可能會(huì)有幫助。而且,Item 類上代表當(dāng)前庫(kù)存水平的額外字段,在產(chǎn)品搜索視圖中可能就是需要顯示的有用信息。但是,不管當(dāng)前的庫(kù)存水平是否與當(dāng)前情況相關(guān)(比如對(duì)顧客的訂單歷史來(lái)說),這個(gè)字段都會(huì)從包含 Item 的任何對(duì)象圖中序列化出來(lái)。

          從 設(shè)計(jì)的角度來(lái)看,這是數(shù)據(jù)模型與視圖生成耦合的經(jīng)典問題。每個(gè) bean 只能用一種途徑序列化自己,一成不變的方式意味著 Ajax 交互最終要交換它們不需要交換的數(shù)據(jù),因此造成客戶端代碼要從文檔中找到需要的信息更加困難,而且也會(huì)增加帶寬消耗和客戶端的 XML 解析時(shí)間。這種耦合的另一個(gè)后果就是 XML 的語(yǔ)法不能脫離 Java 類獨(dú)立變化。例如,對(duì)顧客文檔的方案做修改,可能會(huì)影響多個(gè) Java 類,造成它們也不得不做修改和重新編譯。

          稍后會(huì)解決這些問題,但是首先來(lái)看一個(gè)對(duì)自行序列化方式的可伸縮性問題的解決方案:XML 綁定框架。





          XML 綁定框架

          近 些年來(lái),已經(jīng)開發(fā)了多個(gè) Java API 來(lái)簡(jiǎn)化 XML 文檔到 Java 對(duì)象圖的綁定過程。多數(shù)都提供了 XML 編排和拆解;也就是說,它們可以在 Java 對(duì)象圖和 XML 之間執(zhí)行雙向會(huì)話。這些框架封裝了 XML 處理的全部工作,這意味著應(yīng)用程序代碼只需要處理普通的 Java 類。它們還希望提供有用的輔助功能,例如文檔驗(yàn)證。籠統(tǒng)來(lái)說,這些框架采用了兩種不同的方式:代碼生成和對(duì)象到 XML 映射。我將分別解釋這兩種方式。

          代碼生成方式

          使 用代碼生成的框架包括 XMLBeans、JAXB、Zeus 和 JBind。Castor 也能使用這項(xiàng)技術(shù)。這類框架的起點(diǎn)是描述文檔數(shù)據(jù)類型的 XML 方案。使用框架提供的工具,就可以生成代表這些方案定義類型的 Java 類。最后,用這些生成的類編寫應(yīng)用程序,表示自己的模型數(shù)據(jù),并通過框架提供的一些輔助機(jī)制把數(shù)據(jù)序列化成 XML。

          如果應(yīng)用程序要使用大 型 XML 語(yǔ)法,那么代碼生成方式是個(gè)很好的方法。在數(shù)十個(gè)類上編寫定制 XML 序列化代碼的可伸縮性問題由此消除。另一方面,也不再需要定義自己的 JavaBean。框架生成的 Java 類通常非常符合 XML 的結(jié)構(gòu),所以對(duì)它們進(jìn)行編碼很難。而且,生成的類變成啞數(shù)據(jù)容器,因?yàn)橐话悴荒芟蛩鼈兲砑有袨椤R话銇?lái)說,在應(yīng)用程序代碼中要做些妥協(xié),才能很好地處理方 案生成的類型。另一個(gè)缺陷是如果修改方案,會(huì)造成生成的類也要修改,所以也就會(huì)對(duì)圍繞它們編寫的代碼帶來(lái)相應(yīng)的影響。

          這種類型的 XML 綁定框架在數(shù)據(jù)拆解時(shí)最有用(例如,使用 XML 文檔并把它們轉(zhuǎn)化成 Java 對(duì)象)。除非擁有大型數(shù)據(jù)模型而且有可能從生成的類中獲益,否則基于代碼生成的框架對(duì)于 Ajax 應(yīng)用程序來(lái)說可能有很大的殺傷力。

          映射方式

          采 用映射方式的框架包括 Castor 和 Apache Commons Betwixt。映射通常是比代碼生成更靈活和更輕量的解決方案。首先,可以像通常一樣編寫 JavaBean,包括任何行為以及任何自己喜歡的方便的方法。然后,在運(yùn)行時(shí),調(diào)用框架中基于內(nèi)省的編排器,并根據(jù)對(duì)象成員的類型、名稱和值生成 XML 文檔。通過定義類的映射文件,可以覆蓋默認(rèn)的綁定策略,并就類在 XML 中的表示方式對(duì)編排器提出建議。

          這種方法是在可伸縮性與 靈活性之間的良好折中。可以按照自己喜歡的方式編寫 Java 類,編排器負(fù)責(zé)處理 XML。雖然映射定義文件編寫起來(lái)簡(jiǎn)單,可伸縮性也足夠好,但是映射規(guī)則最多只能改變標(biāo)準(zhǔn)的綁定行為,而且在對(duì)象結(jié)構(gòu)和它們的 XML 表示之間總要?dú)埩粢恍詈稀W罱K,可能不得不在 Java 表示或 XML 格式之間任選一個(gè)做些折中,才能讓映射方法起作用。

          數(shù)據(jù)綁定總結(jié)

          Dennis Sosnoski 就 XML 數(shù)據(jù)綁定 API 的主題,在代碼生成和代碼映射兩個(gè)方面寫了深入的文章。如果想進(jìn)一步研究這個(gè)領(lǐng)域,我推薦他在 Castor 和代碼生成框架方面的精彩文章(請(qǐng)參閱 參考資料 中的鏈接)。

          總之,代碼生成方式損失了過多的靈活性和方便性,對(duì)于典型的 Ajax 應(yīng)用程序用處不大。另一方面,基于映射的框架可能工作得很好,但是要恰到好處地調(diào)整它們的映射策略,以便從對(duì)象生成需要的 XML。

          所 有的 XML 綁定 API 都具有手工序列化技術(shù)的一個(gè)主要不足:模型和視圖的耦合。被限制為一個(gè)類型一個(gè) XML 表示,就意味著在網(wǎng)絡(luò)上總要有冗余數(shù)據(jù)傳輸。更嚴(yán)重的問題是,在情況要求客戶端代碼使用專門視圖時(shí),客戶端代碼卻無(wú)法得到它,所以可能要費(fèi)力地處理給定對(duì) 象圖的一成不變的視圖。

          在傳統(tǒng)的 Web 應(yīng)用程序開發(fā)中,采用頁(yè)面模板系統(tǒng)把視圖生成與控制器邏輯和模型數(shù)據(jù)干凈地分離。這種方法在 Ajax 場(chǎng)景中也會(huì)有幫助。





          頁(yè)面模板系統(tǒng)

          任何通用目的的頁(yè)面模板技術(shù)都可以用來(lái)生成 XML,從而使 Ajax 應(yīng)用程序根據(jù)自己的數(shù)據(jù)模型生成任何 XML 響應(yīng)文檔。額外收獲是:模板可以用簡(jiǎn)單的、表現(xiàn)力強(qiáng)的標(biāo)記語(yǔ)言編寫,而不是用一行行的 Java 代碼編寫。清單 5 是一個(gè) JSP 頁(yè)面,采用了 Customer bean 并表示出定制的 XML 視圖,適合客戶端代碼生成訂單歷史組件。


          清單 4. 生成訂單歷史文檔的 JSP


          <%@ page contentType="application/xml" %>
          <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>





          ${order.date}




          ${item.formattedPrice}







          這個(gè)簡(jiǎn)潔的模板只輸出訂單歷史視圖需要的數(shù)據(jù),不輸出不相關(guān)的資料(例如商品說明)。創(chuàng)建產(chǎn)品搜索視圖的定制 XML 應(yīng)當(dāng)同樣簡(jiǎn)單,這個(gè)視圖包含每個(gè)商品的完整說明和庫(kù)存水平。

          模板的問題

          另 一方面,現(xiàn)在我需要為每個(gè)不同視圖創(chuàng)建一個(gè)新 JSP,而不能僅僅把需要的對(duì)象圖組織起來(lái)并序列化它。從設(shè)計(jì)的角度來(lái)說,許多人可能會(huì)有爭(zhēng)議,認(rèn)為這無(wú)論如何是件好事,因?yàn)檫@意味著正式地考慮服務(wù)器要 生成的文檔類型。而且,因?yàn)槲椰F(xiàn)在要處理通用的模板環(huán)境,而不是特定于 XML 的 API,所以確保標(biāo)記匹配、元素和屬性的順序正確以及 XML 實(shí)體(例如 <&)正確轉(zhuǎn)義就成了我的責(zé)任。JSP 的核心 out 標(biāo)記使后面這項(xiàng)工作變得很容易,但是不是所有的模板技術(shù)都提供了這樣的機(jī)制。最后,沒有方便的途徑可以在服務(wù)器端根據(jù)方案檢驗(yàn)生成的 XML 文檔的正確性,但這畢竟不是要在生產(chǎn)環(huán)境中做的事,可以方便地在開發(fā)期間處理它。





          不用 XML 的響應(yīng)數(shù)據(jù)

          迄 今為止,我介紹的所有技術(shù)都用 XML 文檔的形式生成服務(wù)器響應(yīng)。但是,XML 有一些問題。其中一個(gè)就是延遲。瀏覽器不能立即解析 XML 文檔并生成 DOM 模型,所以這會(huì)降低某些 Ajax 組件需要的“迅捷”感,特別是在較慢的機(jī)器上解析大型文檔的時(shí)候更是如此。“現(xiàn)場(chǎng)搜索”就是一個(gè)示例,在這種搜索中,當(dāng)用戶輸入搜索術(shù)語(yǔ)時(shí),就會(huì)從服務(wù)器 提取搜索結(jié)果并顯示給用戶。對(duì)于現(xiàn)場(chǎng)搜索組件來(lái)說,迅速地響應(yīng)輸入是非常重要的,但是同時(shí)它還需要迅速而持續(xù)地解析服務(wù)器的響應(yīng)。

          延遲是一個(gè)重要的考慮因素,但是避免使用 XML 的最大原因是差勁的客戶端 DOM API。清單 5 顯示了使用跨瀏覽器兼容的方式通過 DOM 得到某個(gè)值的時(shí)候,通常不得不面對(duì)的困難。


          清單 5. 在 JavaScript 中導(dǎo)航 XML 響應(yīng)文檔

          // Find name of first item in customer's last order
          var orderHistoryDoc = req.responseXML;

          var orders = orderHistoryDoc.getElementsByTagName("order");
          var lastOrder = orders[orders.length - 1];

          var firstItem = lastOrder.getElementsByTagName("item")[0];
          var itemNameElement = firstItem.firstChild;

          var itemNameText = itemNameElement.firstChild.data;

          當(dāng)元素中間存在空白時(shí),情況就變得更加復(fù)雜,因?yàn)槊總€(gè)元素的 firstChild 經(jīng)常是個(gè)空白文本節(jié)點(diǎn)。現(xiàn)在有 JavaScript 庫(kù)可以緩解處理 XML 文檔的麻煩。這些庫(kù)包括 Sarissa (請(qǐng)參閱 參考資料)和 Google-ajaXSLT,這兩個(gè)庫(kù)都把 XPath 功能添加到了大多數(shù)瀏覽器中。

          但是,想想替代方案還是值得的。除了 responseXML 之外,XMLHttpRequest 對(duì)象還提供了名為 responseText 的屬性,這個(gè)屬性只是以字符串的方式提供服務(wù)器的響應(yīng)體。

          responseText 屬性

          當(dāng)服務(wù)器需要向客戶機(jī)發(fā)送非常簡(jiǎn)單的值時(shí),responseText 特別方便,它可以避免 XML 導(dǎo)致的帶寬支出和處理支出。例如,簡(jiǎn)單的 true/false 響應(yīng)可以由服務(wù)器以純文本方式返回,可以是逗號(hào)分隔的簡(jiǎn)單的名稱或數(shù)字列表。但是,一般來(lái)說,最好不要在同一個(gè)應(yīng)用程序中把 XML 響應(yīng)和純文本響應(yīng)混合使用;保持單一數(shù)據(jù)格式可以讓代碼抽象和重用更加簡(jiǎn)單。

          responseText 與 XML 響應(yīng)數(shù)據(jù)結(jié)合時(shí)也會(huì)有用。在只需要從響應(yīng)文檔中提取單一值的場(chǎng)景中,“欺騙性”地把 XML 當(dāng)作文本字符串,而不把它當(dāng)作結(jié)構(gòu)化的文檔對(duì)待,會(huì)更方便。例如,清單 6 顯示了如何用正則表達(dá)式從顧客的訂單歷史中提取第一筆訂單的日期。不過,這實(shí)際是種花招,一般不應(yīng)當(dāng)依賴 XML 文檔的詞匯表達(dá)。


          清單 6. 用正則表達(dá)式處理 XMLHttpRequest 的 responseText 對(duì)象

          var orderHistoryText = req.responseText;
          var matches = orderHistoryText.match(/(.*?)<\/date>/);

          var date = matches[1];

          在某些情況下,采用即時(shí)方式使用 responseText 會(huì)比較方便。但是,理想情況下,應(yīng)當(dāng)有種途徑,可以用一種能夠讓 JavaScript 輕松導(dǎo)航、卻沒有 XML 處理支出的格式表示復(fù)雜的結(jié)構(gòu)化數(shù)據(jù)。幸運(yùn)的是,確實(shí)存在這樣一種格式。





          JavaScript 對(duì)象標(biāo)注

          實(shí) 際上,JavaScript 對(duì)象的大部分都由聯(lián)合數(shù)組、數(shù)字索引數(shù)組、字符串、數(shù)字或者這些類型的嵌套組合而成。因?yàn)樗蓄愋投伎梢杂? JavaScript 直接聲明,所以可以在一條語(yǔ)句中靜態(tài)地定義對(duì)象圖。清單 7 使用 JSON 語(yǔ)法聲明了一個(gè)對(duì)象,并演示了如何訪問這個(gè)對(duì)象。大括號(hào)表示聯(lián)合數(shù)組(即對(duì)象),它的鍵 -值組合由逗號(hào)分隔。方括號(hào)表示數(shù)字索引數(shù)組。


          清單 7. 用 JSON 在 JavaScript 中直接聲明一個(gè)簡(jiǎn)單對(duì)象

          var band = {
          name: "The Beatles",
          members: [
          {
          name: "John",
          instruments: ["Vocals","Guitar","Piano"]
          },
          {
          name: "Paul",
          instruments: ["Vocals","Bass","Piano","Guitar"]
          },
          {
          name: "George",
          instruments: ["Guitar","Vocals"]
          },
          {
          name: "Ringo",
          instruments: ["Drums","Vocals"]
          }
          ]
          };

          // Interrogate the band object
          var musician = band.members[3];
          alert( musician.name
          + " played " + musician.instruments[0]
          + " with " + band.name );

          既然 JSON 是一個(gè)有趣的語(yǔ)言特性,那么它對(duì) Ajax 有什么意義呢?妙處在于可以用 JSON 在 Ajax 服務(wù)器響應(yīng)中通過網(wǎng)絡(luò)發(fā)送 JavaScript 對(duì)象圖。這意味著在客戶端可以避免使用笨拙的 DOM API 對(duì) XML 進(jìn)行導(dǎo)航 —— 只需要分析 JSON 響應(yīng),就會(huì)立即得到可以訪問的 JavaScript 對(duì)象圖。但是,首先需要把 JavaBean 變成 JSON。

          從 Java 類產(chǎn)生 JSON

          不同 XML 生成技術(shù)所具有的優(yōu)缺點(diǎn)也適用于 JSON 的產(chǎn)生。而且可以證明,存在需要再次使用表示模板技術(shù)的情況。但是,使用 JSON 在理念上更接近于在應(yīng)用層之間傳遞序列化的對(duì)象,而不是創(chuàng)建應(yīng)用程序狀態(tài)的視圖。我將介紹如何用 org.json 這個(gè) Java API 在 Java 類上創(chuàng)建 toJSONObject() 方法。然后,就可以把 JSONObject 簡(jiǎn)單地序列化成 JSON。清單 8 反映了 清單 1 討論的 XML,顯示了 Order 類的 toJSONObject() 實(shí)現(xiàn)。


          清單 8. Order 類的 toJSONObject() 方法實(shí)現(xiàn)

          public JSONObject toJSONObject() {

          JSONObject json = new JSONObject();
          json.put("id",id);
          json.put("cost",getFormattedCost());
          json.put("date",date);

          JSONArray jsonItems = new JSONArray();
          for (Iterator iter =
          items.iterator() ; iter.hasNext() ; ) {
          jsonItems.put(iter.next().toJSONObject());
          }
          json.put("items",jsonItems);

          return json;
          }

          可以看到,org.json API 非常簡(jiǎn)單。 JSONObject 代表 JavaScript 對(duì)象(即聯(lián)合數(shù)組),有不同的 put() 方法,方法接受的 String 鍵和值是原生類型、String 類型或其他 JSON 類型。JSONArray 代表索引數(shù)組,所以它的 put() 方法只接受一個(gè)值。請(qǐng)注意在清單 8 中,創(chuàng)建 jsonItems 數(shù)組,然后再用 put() 把它附加到 json 對(duì)象上;可以用另外一種方法做這項(xiàng)工作,就是對(duì)每個(gè)項(xiàng)目調(diào)用 json.accumulate("items",iter.next().toJSONObject());accumulate() 方法與 put() 類似,區(qū)別在于它把值添加到按照鍵進(jìn)行識(shí)別的索引數(shù)組。

          清單 9 顯示了如何序列化 JSONObject 并把它寫入 servlet 響應(yīng)。


          清單 9. 從 JSONObject 生成序列化的 JSON 響應(yīng)

          public void doGet(HttpServletRequest req, HttpServletResponse res)
          throws java.io.IOException, ServletException {

          String custId = req.getParameter("username");
          Customer customer = getCustomer(custId);

          res.setContentType("application/x-json");
          res.getWriter().print(customer.toJSONObject());
          }

          可以看到,它實(shí)際上什么也沒有做。在這里隱式調(diào)用的 JSONObjecttoString() 方法做了所有工作。請(qǐng)注意,application/x-json 內(nèi)容類型還有一點(diǎn)不確定 —— 在編寫這篇文章的時(shí)候,關(guān)于 JSON 應(yīng)當(dāng)屬于什么 MIME 類型還沒有定論。但是,目前 application/x-json 是合理的選擇。清單 10 顯示了這個(gè) servlet 代碼的示例響應(yīng)。


          清單 10. Customer bean 的 JSON 表示

          {
          "orders": [
          {
          "items": [
          {
          "price": "$49.99",
          "description": "512 Megabyte Type 1 CompactFlash card.
          Manufactured by Oolong Industries",
          "name": "Oolong 512MB CF Card",
          "id": "i-55768"
          },
          {
          "price": "$299.99",
          "description": "7.2 Megapixel digital camera featuring six
          shooting modes and 3x optical zoom. Silver.",
          "name": "Fujak Superpix72 Camera",
          "id": "i-74491"
          }
          ],
          "date": "08-26-2005",
          "cost": "$349.98",
          "id": "o-11123"
          }
          ],
          "realname": "James Hyrax",
          "username": "jimmy66"
          }

          在客戶端使用 JSON

          處理的最后一步是把在客戶端把 JSON 數(shù)據(jù)變成 JavaScript 對(duì)象。這可以通過對(duì) eval() 的簡(jiǎn)單調(diào)用實(shí)現(xiàn),這個(gè)函數(shù)可以即時(shí)地解釋包含 JavaScript 表達(dá)式的字符串。清單 11 把 JSON 響應(yīng)轉(zhuǎn)變成 JavaScript 對(duì)象圖,然后執(zhí)行清單 5 的任務(wù),從顧客的最后一次訂單中得到第一個(gè)商品的名稱。


          清單 11. 評(píng)估 JSON 響應(yīng)

          var jsonExpression = "(" + req.responseText + ")";
          var customer = eval(jsonExpression);

          // Find name of first item in customer's last order
          var lastOrder = customer.orders[customer.orders.length-1];
          var name = lastOrder.items[0].name;

          比較清單 11 和 清單 5 可以發(fā)現(xiàn)使用 JSON 的客戶端的優(yōu)勢(shì)。如果在 Ajax 項(xiàng)目中要在客戶端對(duì)許多復(fù)雜的服務(wù)器響應(yīng)進(jìn)行導(dǎo)航,那么 JSON 可能適合您的需要。JSON 和 XMLHttpRequest 結(jié)合還會(huì)讓 Ajax 交互看起來(lái)更像 RPC 調(diào)用而不是 SOA 請(qǐng)求,這對(duì)應(yīng)用程序的設(shè)計(jì)可能會(huì)有意義。在下一篇文章中,我要研究的框架,就是明確地為了讓 JavaScript 代碼對(duì)服務(wù)器端對(duì)象進(jìn)行遠(yuǎn)程方法調(diào)用而設(shè)計(jì)的。

          JSON 的不足

          JSON 也有它的不足。使用這里介紹的 JSON 方式,就沒有辦法針對(duì)每個(gè)請(qǐng)求對(duì)對(duì)象的序列化進(jìn)行裁剪,所以不需要的字段可能經(jīng)常會(huì)在網(wǎng)絡(luò)上發(fā)送。另外,添加 toJSONObject() 方法到每個(gè) JavaBean,可伸縮性不太好,雖然用內(nèi)省和標(biāo)注編寫一個(gè)通用的 JavaBean 到 JSON 的序列化器可能很簡(jiǎn)單。最后,如果服務(wù)器端代碼是面向服務(wù)的,沒有單獨(dú)針對(duì)處理 Ajax 客戶請(qǐng)求調(diào)整過,那么由于對(duì) XML 一致的支持,XML 會(huì)是更好的選擇。





          比較序列化技術(shù)

          現(xiàn) 在已經(jīng)看到了把 Java 狀態(tài)傳輸?shù)?Ajax 客戶端的五種不同技術(shù)。我討論了自行手工編碼 XML 序列化、通過代碼生成的 XML 綁定、通過映射機(jī)制的 XML 綁定、基于模板的 XML 生成以及手工編碼到 JSON 的序列化。每種技術(shù)都有自己的優(yōu)勢(shì)和不足,分別適用于不同的應(yīng)用程序架構(gòu)。

          為了總結(jié)每種方式的優(yōu)勢(shì)與不足,表 1 從六個(gè)方面進(jìn)行了粗略的評(píng)分:

          可伸縮性
          描述技術(shù)適應(yīng)大量數(shù)據(jù)類型的容易程度。對(duì)于每個(gè)附加類型,編碼和配置工作量是否會(huì)增長(zhǎng)?
          易于集成
          評(píng)估把技術(shù)集成到項(xiàng)目的簡(jiǎn)單程度。是否需要更加復(fù)雜的構(gòu)建過程?是否增加了部署的復(fù)雜性?
          Java 類 API
          描述以指定方式處理服務(wù)器端 Java 對(duì)象的容易程度。是可以編寫普通的 bean,還是不得不處理笨拙的文檔表示?
          對(duì)輸出的控制
          描述對(duì)類的序列化表示控制的精確程度。
          視圖靈活性
          評(píng)估從同一組對(duì)象是否可以創(chuàng)建不同的、定制的數(shù)據(jù)序列化。
          客戶端數(shù)據(jù)訪問
          描述 JavaScript 代碼處理服務(wù)器響應(yīng)數(shù)據(jù)的難易程度。
          表 1. 數(shù)據(jù)生成技術(shù)的相對(duì)價(jià)值

          自行編寫 XML通過代碼生成的 XML 綁定通過映射的 XML 綁定頁(yè)面模板 XML手工編碼的 JSON 序列化
          可伸縮性一般一般
          易于集成一般一般
          Java 類 API
          對(duì)輸出的控制一般
          視圖靈活性
          客戶端數(shù)據(jù)訪問一般




          結(jié)束語(yǔ)

          表 1 中的數(shù)據(jù)并不表明某項(xiàng)序列化技術(shù)比其他的技術(shù)好。畢竟,六種標(biāo)準(zhǔn)的相對(duì)重要性取決于項(xiàng)目的具體情況。例如,如果要處理數(shù)百種數(shù)據(jù)類型,這時(shí)想要的是可伸縮 性,那么代碼生成可能就是最好的選擇。如果需要為同一數(shù)據(jù)模型生成多個(gè)不同視圖,那么就應(yīng)當(dāng)使用頁(yè)面模板。如果處理的是小規(guī)模項(xiàng)目,想降低需要編寫的 JavaScript 代碼數(shù)量,那么請(qǐng)考慮 JSON。

          希望這篇文章為您提供了選擇適合自己應(yīng)用程序的序列化技術(shù)所需要的信息。

          posted @ 2005-10-30 15:32 bluesky 閱讀(409) | 評(píng)論 (0)編輯 收藏

          1.前言

            Internet的高速發(fā)展,給人們的工作和生活帶來(lái)了極大的便利,對(duì)Internet的服務(wù)品質(zhì)和訪問速度要求越來(lái)越高,雖然帶寬不斷增加, 用戶數(shù)量也在不斷增加,受Web服務(wù)器的負(fù)荷和傳輸距離等因數(shù)的影響,響應(yīng)速度慢還是經(jīng)常抱怨和困擾。解決方案就是在網(wǎng)絡(luò)傳輸上利用緩存技術(shù)使得Web服 務(wù)數(shù)據(jù)流能就近訪問,是優(yōu)化網(wǎng)絡(luò)數(shù)據(jù)傳輸非常有效的技術(shù),從而獲得高速的體驗(yàn)和品質(zhì)保證。

            網(wǎng)絡(luò)緩存技術(shù),其目的就是減少網(wǎng)絡(luò)中冗余數(shù)據(jù)的重復(fù)傳輸,使之最小化,將廣域傳輸轉(zhuǎn)為本地或就近訪問。互聯(lián)網(wǎng)上傳遞的內(nèi)容,大部分為重復(fù)的 Web/FTP數(shù)據(jù),Cache服務(wù)器及應(yīng)用Caching技術(shù)的網(wǎng)絡(luò)設(shè)備,可大大優(yōu)化數(shù)據(jù)鏈路性能,消除數(shù)據(jù)峰值訪問造成的結(jié)點(diǎn)設(shè)備阻塞。Cache服 務(wù)器具有緩存功能,所以大部分網(wǎng)頁(yè)對(duì)象(Web page object),如html, htm, php等頁(yè)面文件,gif,tif,png,bmp等圖片文件,以及其他格式的文件,在有效期(TTL)內(nèi),對(duì)于重復(fù)的訪問,不必從原始網(wǎng)站重新傳送文件 實(shí)體, 只需通過簡(jiǎn)單的認(rèn)證(Freshness Validation)- 傳送幾十字節(jié)的Header,即可將本地的副本直接傳送給訪問者。由于緩存服務(wù)器通常部署在靠近用戶端,所以能獲得近似局域網(wǎng)的響應(yīng)速度,并有效減少?gòu)V域 帶寬的消耗。據(jù)統(tǒng)計(jì),Internet上超過80%的用戶重復(fù)訪問20%的信息資源,給緩存技術(shù)的應(yīng)用提供了先決的條件。緩存服務(wù)器的體系結(jié)構(gòu)與Web服 務(wù)器不同,緩存服務(wù)器能比Web服務(wù)器獲得更高的性能,緩存服務(wù)器不僅能提高響應(yīng)速度,節(jié)約帶寬,對(duì)于加速Web服務(wù)器,有效減輕源服務(wù)器的負(fù)荷是非常有 效的。

            高速緩存服務(wù)器(Cache Server)是軟硬件高度集成的專業(yè)功能服務(wù)器,主要做高速緩存加速服務(wù),一般部署在網(wǎng)絡(luò)邊緣。根據(jù)加速對(duì)象不同,分為客戶端加速和服務(wù)器加速,客戶端 加速Cache部署在網(wǎng)絡(luò)出口處,把常訪問的內(nèi)容緩存在本地,提高響應(yīng)速度和節(jié)約帶寬;服務(wù)器加速,Cache部署在服務(wù)器前端,作為Web服務(wù)器的前置 機(jī),提高Web服務(wù)器的性能,加速訪問速度。如果多臺(tái)Cache加速服務(wù)器且分布在不同地域,需要通過有效地機(jī)制管理Cache網(wǎng)絡(luò),引導(dǎo)用戶就近訪問, 全局負(fù)載均衡流量,這就是CDN內(nèi)容傳輸網(wǎng)絡(luò)的基本思想。

          2.什么是CDN?

            CDN的全稱是Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò)。其目的是通過在現(xiàn)有的Internet中增加一層新的網(wǎng)絡(luò)架構(gòu),將網(wǎng)站的內(nèi)容發(fā)布到最接近用戶的網(wǎng)絡(luò)"邊緣",使用戶可 以就近取得所需的內(nèi)容,解決Internet網(wǎng)絡(luò)擁塞狀況,提高用戶訪問網(wǎng)站的響應(yīng)速度。從技術(shù)上全面解決由于網(wǎng)絡(luò)帶寬小、用戶訪問量大、網(wǎng)點(diǎn)分布不均等 原因,解決用戶訪問網(wǎng)站的響應(yīng)速度慢的根本原因。

            狹義地講,內(nèi)容分發(fā)布網(wǎng)絡(luò)(CDN)是一種新型的網(wǎng)絡(luò)構(gòu)建方式,它是為能在傳統(tǒng)的IP網(wǎng)發(fā)布寬帶豐富媒體而特別優(yōu)化的網(wǎng)絡(luò)覆蓋層;而從廣義的角 度,CDN代表了一種基于質(zhì)量與秩序的網(wǎng)絡(luò)服務(wù)模式。簡(jiǎn)單地說,內(nèi)容發(fā)布網(wǎng)(CDN)是一個(gè)經(jīng)策略性部署的整體系統(tǒng),包括分布式存儲(chǔ)、負(fù)載均衡、網(wǎng)絡(luò)請(qǐng)求 的重定向和內(nèi)容管理4個(gè)要件,而內(nèi)容管理和全局的網(wǎng)絡(luò)流量管理(Traffic Management)是CDN的核心所在。通過用戶就近性和服務(wù)器負(fù)載的判斷,CDN確保內(nèi)容以一種極為高效的方式為用戶的請(qǐng)求提供服務(wù)。總的來(lái)說,內(nèi) 容服務(wù)基于緩存服務(wù)器,也稱作代理緩存(Surrogate),它位于網(wǎng)絡(luò)的邊緣,距用戶僅有"一跳"(Single Hop)之遙。同時(shí),代理緩存是內(nèi)容提供商源服務(wù)器(通常位于CDN服務(wù)提供商的數(shù)據(jù)中心)的一個(gè)透明鏡像。這樣的架構(gòu)使得CDN服務(wù)提供商能夠代表他們 客戶,即內(nèi)容供應(yīng)商,向最終用戶提供盡可能好的體驗(yàn),而這些用戶是不能容忍請(qǐng)求響應(yīng)時(shí)間有任何延遲的。據(jù)統(tǒng)計(jì),采用CDN技術(shù),能處理整個(gè)網(wǎng)站頁(yè)面的 70%~95%的內(nèi)容訪問量,減輕服務(wù)器的壓力,提升了網(wǎng)站的性能和可擴(kuò)展性。

            與目前現(xiàn)有的內(nèi)容發(fā)布模式相比較,CDN強(qiáng)調(diào)了網(wǎng)絡(luò)在內(nèi)容發(fā)布中的重要性。通過引入主動(dòng)的內(nèi)容管理層的和全局負(fù)載均衡,CDN從根本上區(qū)別于傳 統(tǒng)的內(nèi)容發(fā)布模式。在傳統(tǒng)的內(nèi)容發(fā)布模式中,內(nèi)容的發(fā)布由ICP的應(yīng)用服務(wù)器完成,而網(wǎng)絡(luò)只表現(xiàn)為一個(gè)透明的數(shù)據(jù)傳輸通道,這種透明性表現(xiàn)在網(wǎng)絡(luò)的質(zhì)量保 證僅僅停留在數(shù)據(jù)包的層面,而不能根據(jù)內(nèi)容對(duì)象的不同區(qū)分服務(wù)質(zhì)量。此外,由于IP網(wǎng)的"盡力而為"的特性使得其質(zhì)量保證是依靠在用戶和應(yīng)用服務(wù)器之間端 到端地提供充分的、遠(yuǎn)大于實(shí)際所需的帶寬通量來(lái)實(shí)現(xiàn)的。在這樣的內(nèi)容發(fā)布模式下,不僅大量寶貴的骨干帶寬被占用,同時(shí)ICP的應(yīng)用服務(wù)器的負(fù)載也變得非常 重,而且不可預(yù)計(jì)。當(dāng)發(fā)生一些熱點(diǎn)事件和出現(xiàn)浪涌流量時(shí),會(huì)產(chǎn)生局部熱點(diǎn)效應(yīng),從而使應(yīng)用服務(wù)器過載退出服務(wù)。這種基于中心的應(yīng)用服務(wù)器的內(nèi)容發(fā)布模式的 另外一個(gè)缺陷在于個(gè)性化服務(wù)的缺失和對(duì)寬帶服務(wù)價(jià)值鏈的扭曲,內(nèi)容提供商承擔(dān)了他們不該干也干不好的內(nèi)容發(fā)布服務(wù)。

            縱觀整個(gè)寬帶服務(wù)的價(jià)值鏈,內(nèi)容提供商和用戶位于整個(gè)價(jià)值鏈的兩端,中間依靠網(wǎng)絡(luò)服務(wù)提供商將其串接起來(lái)。隨著互聯(lián)網(wǎng)工業(yè)的成熟和商業(yè)模式的變 革,在這條價(jià)值鏈上的角色越來(lái)越多也越來(lái)越細(xì)分。比如內(nèi)容/應(yīng)用的運(yùn)營(yíng)商、托管服務(wù)提供商、骨干網(wǎng)絡(luò)服務(wù)提供商、接入服務(wù)提供商等等。在這一條價(jià)值鏈上的 每一個(gè)角色都要分工合作、各司其職才能為客戶提供良好的服務(wù),從而帶來(lái)多贏的局面。從內(nèi)容與網(wǎng)絡(luò)的結(jié)合模式上看,內(nèi)容的發(fā)布已經(jīng)走過了ICP的內(nèi)容(應(yīng) 用)服務(wù)器和IDC這兩個(gè)階段。IDC的熱潮也催生了托管服務(wù)提供商這一角色。但是,IDC并不能解決內(nèi)容的有效發(fā)布問題。內(nèi)容位于網(wǎng)絡(luò)的中心并不能解決 骨干帶寬的占用和建立IP網(wǎng)絡(luò)上的流量秩序。因此將內(nèi)容推到網(wǎng)絡(luò)的邊緣,為用戶提供就近性的邊緣服務(wù),從而保證服務(wù)的質(zhì)量和整個(gè)網(wǎng)絡(luò)上的訪問秩序就成了一 種顯而易見的選擇。而這就是內(nèi)容發(fā)布網(wǎng)(CDN)服務(wù)模式。CDN的建立解決了困擾內(nèi)容運(yùn)營(yíng)商的內(nèi)容"集中與分散"的兩難選擇。無(wú)疑對(duì)于構(gòu)建良好的互聯(lián)網(wǎng) 價(jià)值鏈?zhǔn)怯袃r(jià)值的,也是不可或缺的。

          3.CDN新應(yīng)用和客戶

            目前的CDN服務(wù)主要應(yīng)用于證券、金融保險(xiǎn)、ISP、ICP、網(wǎng)上交易、門戶網(wǎng)站、大中型公司、網(wǎng)絡(luò)教學(xué)等領(lǐng)域。另外在行業(yè)專網(wǎng)、互聯(lián)網(wǎng)中都可 以用到,甚至可以對(duì)局域網(wǎng)進(jìn)行網(wǎng)絡(luò)優(yōu)化。利用CDN,這些網(wǎng)站無(wú)需投資昂貴的各類服務(wù)器、設(shè)立分站點(diǎn),特別是流媒體信息的廣泛應(yīng)用、遠(yuǎn)程教學(xué)課件等消耗帶 寬資源多的媒體信息,應(yīng)用CDN網(wǎng)絡(luò),把內(nèi)容復(fù)制到網(wǎng)絡(luò)的最邊緣,使內(nèi)容請(qǐng)求點(diǎn)和交付點(diǎn)之間的距離縮至最小,從而促進(jìn)Web站點(diǎn)性能的提高,具有重要的意 義。CDN網(wǎng)絡(luò)的建設(shè)主要有企業(yè)建設(shè)的CDN網(wǎng)絡(luò),為企業(yè)服務(wù);IDC的CDN網(wǎng)絡(luò),主要服務(wù)于IDC和增值服務(wù);網(wǎng)絡(luò)運(yùn)營(yíng)上主建的CDN網(wǎng)絡(luò),主要提供 內(nèi)容推送服務(wù);CDN網(wǎng)絡(luò)服務(wù)商,專門建設(shè)的CDN用于做服務(wù),用戶通過與CDN機(jī)構(gòu)進(jìn)行合作,CDN負(fù)責(zé)信息傳遞工作,保證信息正常傳輸,維護(hù)傳送網(wǎng) 絡(luò),而網(wǎng)站只需要內(nèi)容維護(hù),不再需要考慮流量問題。

            CDN能夠?yàn)榫W(wǎng)絡(luò)的快速、安全、穩(wěn)定、可擴(kuò)展等方面提供保障。

            IDC建立CDN網(wǎng)絡(luò),IDC運(yùn)營(yíng)商一般需要有分部各地的多個(gè)IDC中心,服務(wù)對(duì)象是托管在IDC中心的客戶,利用現(xiàn)有的網(wǎng)絡(luò)資源,投資較少, 容易建設(shè)。例如某IDC全國(guó)有10個(gè)機(jī)房,加入IDC的CDN網(wǎng)絡(luò),托管在一個(gè)節(jié)點(diǎn)的Web服務(wù)器,相當(dāng)于有了10個(gè)鏡像服務(wù)器,就近供客戶訪問。寬帶城 域網(wǎng),域內(nèi)網(wǎng)絡(luò)速度很快,出城帶寬一般就會(huì)瓶頸,為了體現(xiàn)城域網(wǎng)的高速體驗(yàn),解決方案就是將Internet網(wǎng)上內(nèi)容高速緩存到本地,將Cache部署在 城域網(wǎng)各POP點(diǎn)上,這樣形成高效有序的網(wǎng)絡(luò),用戶僅一跳就能訪問大部分的內(nèi)容,這也是一種加速所有網(wǎng)站CDN的應(yīng)用。

          4.CDN 的工作原理

            在描述CDN的實(shí)現(xiàn)原理,讓我們先看傳統(tǒng)的未加緩存服務(wù)的訪問過程,以便了解CDN緩存訪問方式與未加緩存訪問方式的差別:

            由上圖可見,用戶訪問未使用CDN緩存網(wǎng)站的過程為:

            1)、用戶向?yàn)g覽器提供要訪問的域名;

            2)、瀏覽器調(diào)用域名解析函數(shù)庫(kù)對(duì)域名進(jìn)行解析,以得到此域名對(duì)應(yīng)的IP地址;

            3)、瀏覽器使用所得到的IP地址,域名的服務(wù)主機(jī)發(fā)出數(shù)據(jù)訪問請(qǐng)求;

            4)、瀏覽器根據(jù)域名主機(jī)返回的數(shù)據(jù)顯示網(wǎng)頁(yè)的內(nèi)容。

            通過以上四個(gè)步驟,瀏覽器完成從用戶處接收用戶要訪問的域名到從域名服務(wù)主機(jī)處獲取數(shù)據(jù)的整個(gè)過程。CDN網(wǎng)絡(luò)是在用戶和服務(wù)器之間增加 Cache層,如何將用戶的請(qǐng)求引導(dǎo)到Cache上獲得源服務(wù)器的數(shù)據(jù),主要是通過接管DNS實(shí)現(xiàn),下面讓我們看看訪問使用CDN緩存后的網(wǎng)站的過程:

            通過上圖,我們可以了解到,使用了CDN緩存后的網(wǎng)站的訪問過程變?yōu)椋?/p>

            1)、用戶向?yàn)g覽器提供要訪問的域名;

            2)、瀏覽器調(diào)用域名解析庫(kù)對(duì)域名進(jìn)行解析,由于CDN對(duì)域名解析過程進(jìn)行了調(diào)整,所以解析函數(shù)庫(kù)一般得到的是該域名對(duì)應(yīng)的CNAME記錄,為 了得到實(shí)際IP地址,瀏覽器需要再次對(duì)獲得的CNAME域名進(jìn)行解析以得到實(shí)際的IP地址;在此過程中,使用的全局負(fù)載均衡DNS解析,如根據(jù)地理位置信 息解析對(duì)應(yīng)的IP地址,使得用戶能就近訪問。

            3)、此次解析得到CDN緩存服務(wù)器的IP地址,瀏覽器在得到實(shí)際的IP地址以后,向緩存服務(wù)器發(fā)出訪問請(qǐng)求;

            4)、緩存服務(wù)器根據(jù)瀏覽器提供的要訪問的域名,通過Cache內(nèi)部專用DNS解析得到此域名的實(shí)際IP地址,再由緩存服務(wù)器向此實(shí)際IP地址提交訪問請(qǐng)求;

            5)、緩存服務(wù)器從實(shí)際IP地址得得到內(nèi)容以后,一方面在本地進(jìn)行保存,以備以后使用,二方面把獲取的數(shù)據(jù)返回給客戶端,完成數(shù)據(jù)服務(wù)過程;

            6)、客戶端得到由緩存服務(wù)器返回的數(shù)據(jù)以后顯示出來(lái)并完成整個(gè)瀏覽的數(shù)據(jù)請(qǐng)求過程。

            通過以上的分析我們可以得到,為了實(shí)現(xiàn)既要對(duì)普通用戶透明(即加入緩存以后用戶客戶端無(wú)需進(jìn)行任何設(shè)置,直接使用被加速網(wǎng)站原有的域名即可訪 問),又要在為指定的網(wǎng)站提供加速服務(wù)的同時(shí)降低對(duì)ICP的影響,只要修改整個(gè)訪問過程中的域名解析部分,以實(shí)現(xiàn)透明的加速服務(wù),下面是CDN網(wǎng)絡(luò)實(shí)現(xiàn)的 具體操作過程。

            1)、作為ICP,只需要把域名解釋權(quán)交給CDN運(yùn)營(yíng)商,其他方面不需要進(jìn)行任何的修改;操作時(shí),ICP修改自己域名的解析記錄,一般用cname方式指向CDN網(wǎng)絡(luò)Cache服務(wù)器的地址。

            2)、作為CDN運(yùn)營(yíng)商,首先需要為ICP的域名提供公開的解析,為了實(shí)現(xiàn)sortlist,一般是把ICP的域名解釋結(jié)果指向一個(gè)CNAME記錄;

            3)、當(dāng)需要進(jìn)行sorlist時(shí),CDN運(yùn)營(yíng)商可以利用DNS對(duì)CNAME指向的域名解析過程進(jìn)行特殊處理,使DNS服務(wù)器在接收到客戶端請(qǐng)求時(shí)可以根據(jù)客戶端的IP地址,返回相同域名的不同IP地址;

            4)、由于從cname獲得的IP地址,并且?guī)в衕ostname信息,請(qǐng)求到達(dá)Cache之后,Cache必須知道源服務(wù)器的IP地址,所以在CDN運(yùn)營(yíng)商內(nèi)部維護(hù)一個(gè)內(nèi)部DNS服務(wù)器,用于解釋用戶所訪問的域名的真實(shí)IP地址;

            5)、在維護(hù)內(nèi)部DNS服務(wù)器時(shí),還需要維護(hù)一臺(tái)授權(quán)服務(wù)器,控制哪些域名可以進(jìn)行緩存,而哪些又不進(jìn)行緩存,以免發(fā)生開放代理的情況。

          5.CDN的技術(shù)手段

            實(shí)現(xiàn)CDN的主要技術(shù)手段是高速緩存、鏡像服務(wù)器。可工作于DNS解析或HTTP重定向兩種方式,通過Cache服務(wù)器,或異地的鏡像站點(diǎn) 完成內(nèi)容的傳送與同步更新。DNS方式用戶位置判斷準(zhǔn)確率大于85%,HTTP方式準(zhǔn)確率為99%以上;一般情況,各Cache服務(wù)器群的用戶訪問流入數(shù) 據(jù)量與Cache服務(wù)器到原始網(wǎng)站取內(nèi)容的數(shù)據(jù)量之比在2:1到3:1之間,即分擔(dān)50%到70%的到原始網(wǎng)站重復(fù)訪問數(shù)據(jù)量(主要是圖片,流媒體文件等 內(nèi)容);對(duì)于鏡像,除數(shù)據(jù)同步的流量,其余均在本地完成,不訪問原始服務(wù)器。

            鏡像站點(diǎn)(Mirror Site)服務(wù)器是我們經(jīng)常可以看到的,它讓內(nèi)容直截了當(dāng)?shù)剡M(jìn)行分布,適用于靜態(tài)和準(zhǔn)動(dòng)態(tài)的數(shù)據(jù)同步。但是購(gòu)買和維護(hù)新服務(wù)器的費(fèi)用較高,另外還必須在各 個(gè)地區(qū)設(shè)置鏡像服務(wù)器,配備專業(yè)技術(shù)人員進(jìn)行管理與維護(hù)。大型網(wǎng)站在隨時(shí)更新各地服務(wù)器的同時(shí),對(duì)帶寬的需求也會(huì)顯著增加,因此一般的互聯(lián)網(wǎng)公司不會(huì)建立 太多的鏡像服務(wù)器。

            高速緩存手段的成本較低,適用于靜態(tài)內(nèi)容。Internet的統(tǒng)計(jì)表明,超過80%的用戶經(jīng)常訪問的是20%的網(wǎng)站的內(nèi)容,在這個(gè)規(guī)律下,緩存 服務(wù)器可以處理大部分客戶的靜態(tài)請(qǐng)求,而原始的WWW服務(wù)器只需處理約20%左右的非緩存請(qǐng)求和動(dòng)態(tài)請(qǐng)求,于是大大加快了客戶請(qǐng)求的響應(yīng)時(shí)間,并降低了原 始WWW服務(wù)器的負(fù)載。根據(jù)美國(guó)IDC公司的調(diào)查,作為CDN的一項(xiàng)重要指標(biāo)-緩存的市場(chǎng)正在以每年近100%的速度增長(zhǎng),全球的營(yíng)業(yè)額在2004年將達(dá) 到45

          6.CDN的網(wǎng)絡(luò)架構(gòu)

            CDN網(wǎng)絡(luò)架構(gòu)主要由兩大部分,分為中心和邊緣兩部分,中心指CDN網(wǎng)管中心和DNS重定向解析中心,負(fù)責(zé)全局負(fù)載均衡,設(shè)備系統(tǒng)安裝在管理中心機(jī)房,邊緣主要指異地節(jié)點(diǎn),CDN分發(fā)的載體,主要由Cache和負(fù)載均衡器等組成。

            當(dāng)用戶訪問加入CDN服務(wù)的網(wǎng)站時(shí),域名解析請(qǐng)求將最終交給全局負(fù)載均衡DNS進(jìn)行處理。全局負(fù)載均衡DNS通過一組預(yù)先定義好的策略,將當(dāng)時(shí) 最接近用戶的節(jié)點(diǎn)地址提供給用戶,使用戶能夠得到快速的服務(wù)。同時(shí),它還與分布在世界各地的所有CDNC節(jié)點(diǎn)保持通信,搜集各節(jié)點(diǎn)的通信狀態(tài),確保不將用 戶的請(qǐng)求分配到不可用的CDN節(jié)點(diǎn)上,實(shí)際上是通過DNS做全局負(fù)載均衡。

            對(duì)于普通的Internet用戶來(lái)講,每個(gè)CDN節(jié)點(diǎn)就相當(dāng)于一個(gè)放置在它周圍的WEB。通過全局負(fù)載均衡DNS的控制,用戶的請(qǐng)求被透明地指向離他最近的節(jié)點(diǎn),節(jié)點(diǎn)中CDN服務(wù)器會(huì)像網(wǎng)站的原始服務(wù)器一樣,響應(yīng)用戶的請(qǐng)求。由于它離用戶更近,因而響應(yīng)時(shí)間必然更快。

            每個(gè)CDN節(jié)點(diǎn)由兩部分組成:負(fù)載均衡設(shè)備和高速緩存服務(wù)器

            負(fù)載均衡設(shè)備負(fù)責(zé)每個(gè)節(jié)點(diǎn)中各個(gè)Cache的負(fù)載均衡,保證節(jié)點(diǎn)的工作效率;同時(shí),負(fù)載均衡設(shè)備還負(fù)責(zé)收集節(jié)點(diǎn)與周圍環(huán)境的信息,保持與全局負(fù)載DNS的通信,實(shí)現(xiàn)整個(gè)系統(tǒng)的負(fù)載均衡。

            高速緩存服務(wù)器(Cache)負(fù)責(zé)存儲(chǔ)客戶網(wǎng)站的大量信息,就像一個(gè)靠近用戶的網(wǎng)站服務(wù)器一樣響應(yīng)本地用戶的訪問請(qǐng)求。

            CDN的管理系統(tǒng)是整個(gè)系統(tǒng)能夠正常運(yùn)轉(zhuǎn)的保證。它不僅能對(duì)系統(tǒng)中的各個(gè)子系統(tǒng)和設(shè)備進(jìn)行實(shí)時(shí)監(jiān)控,對(duì)各種故障產(chǎn)生相應(yīng)的告警,還可以實(shí)時(shí)監(jiān)測(cè) 到系統(tǒng)中總的流量和各節(jié)點(diǎn)的流量,并保存在系統(tǒng)的數(shù)據(jù)庫(kù)中,使網(wǎng)管人員能夠方便地進(jìn)行進(jìn)一步分析。通過完善的網(wǎng)管系統(tǒng),用戶可以對(duì)系統(tǒng)配置進(jìn)行修改。

            理論上,最簡(jiǎn)單的CDN網(wǎng)絡(luò)有一個(gè)負(fù)責(zé)全局負(fù)載均衡的DNS和各節(jié)點(diǎn)一臺(tái)Cache,即可運(yùn)行。DNS支持根據(jù)用戶源IP地址解析不同的IP, 實(shí)現(xiàn)就近訪問。為了保證高可用性等,需要監(jiān)視各節(jié)點(diǎn)的流量、健康狀況等。一個(gè)節(jié)點(diǎn)的單臺(tái)Cache承載數(shù)量不夠時(shí),才需要多臺(tái)Cache,多臺(tái)Cache 同時(shí)工作,才需要負(fù)載均衡器,使Cache群協(xié)同工作。

          億美元。網(wǎng)絡(luò)流媒體的發(fā)展還將剌激這個(gè)市場(chǎng)的需求。

          7. CDN 示例

            商業(yè)化的CDN網(wǎng)絡(luò)是用于服務(wù)性質(zhì)的,高可用性等要求非常高,有專業(yè)產(chǎn)品和CDN網(wǎng)絡(luò)解決方案,本文主要從理論角度,理解CDN的實(shí)現(xiàn)過程,并利用已有網(wǎng)絡(luò)環(huán)境和開源軟件做實(shí)際配置,更深刻理解CDN的具體工作過程。

            Linux 是開放源代碼的免費(fèi)操作系統(tǒng),已經(jīng)成功應(yīng)用于許多關(guān)鍵領(lǐng)域。Bind是Unix/FreeBSD/Linux等類unix平臺(tái)上非常有名DNS服務(wù)程序, Internet上超過60%的DNS運(yùn)行的是bind。Bind的最新版本是9.x,用的比較多的是8.x,bind 9有很多新特性,其中一項(xiàng)是根據(jù)用戶端源地址對(duì)同一域名解析不同的IP地址,有了這種特性,能把用戶對(duì)同一域名的訪問,引導(dǎo)到不同地域節(jié)點(diǎn)的服務(wù)器上去訪 問。Squid是Linux等操作系統(tǒng)上有名的Cache引擎,與商業(yè)Cache引擎相比,Squid的性能比較低,基本功能工作原理與商業(yè)Cache產(chǎn) 品是一致的,作為試驗(yàn),是非常容易配置運(yùn)行起來(lái)。以下簡(jiǎn)要介紹CDN的配置流程。

            1、要加入CDN服務(wù)的網(wǎng)站,需要域名(如www.linuxaid.com.cn,地址202.99.11.120)解析權(quán)提供給CDN運(yùn)營(yíng) 商,Linuxaid的域名解析記錄只要把www主機(jī)的A記錄改為CNAME并指向cache.cdn.com即可。cache.cdn.com是CDN 網(wǎng)絡(luò)自定義的緩存服務(wù)器的標(biāo)識(shí)。在/var/named/linuxaid.com.cn域名解析記錄中,由:


          www IN A 202.99.11.120
          改為
          www IN CNAME cache.cdn.com.

            2、CDN運(yùn)營(yíng)商得到域名解析權(quán)以后,得到域名的CNAME記錄,指向CDN網(wǎng)絡(luò)屬下緩存服務(wù)器的域名,如cache.cdn.com,CDN網(wǎng)絡(luò)的全局負(fù)載均衡DNS,需要把CNAME記錄根據(jù)策略解析出IP地址,一般是給出就近訪問的Cache地址。

            Bind 9的基本功能可以根據(jù)不同的源IP地址段解析對(duì)應(yīng)的IP,實(shí)現(xiàn)根據(jù)地域就近訪問的負(fù)載均衡,一般可以通過Bind 9的sortlist選項(xiàng)實(shí)現(xiàn)根據(jù)用戶端IP地址返回最近的節(jié)點(diǎn)IP地址,具體的過程為:

            1)為cache.cdn.com設(shè)置多個(gè)A記錄,/var/named/cdn.com 的內(nèi)容如下:


          $TTL 3600
          @ IN SOA ns.cdn.com. root.ns.cdn.com. (
          2002090201 ;Serial num
          10800 ;Refresh after 3 hours
          3600 ;Retry
          604800 ;Expire
          1800 ;Time to live
          )
          IN NS ns
          www IN A 210.33.21.168
          ns IN A 202.96.128.68
          cache IN A 202.93.22.13 ;有多少個(gè)CACHE地址
          cache IN A 210.21.30.90 ;就有多少個(gè)CACHE的A記錄
          cache IN A 211.99.13.47

            2) /etc/named.conf中的內(nèi)容為:


          options {
          directory "/var/named";
          sortlist {
          #這一段表示當(dāng)在本地執(zhí)行查詢時(shí)
          #將按照202.93.22.13,210.21.30.90,211.99.13.47的順序返回地址
          { localhost;
          { localnets;
          202.93.22.13;
          { 210.21.30.90; 211.99.13.47; };
          };
          };
          #這一段表示當(dāng)在202/8地址段進(jìn)行DNS查詢時(shí)
          #將按照202.93.22.13,210.21.30.90,211.99.13.47的順序返回地址
          { 202/8;
          { 202.93.22.13;
          { 210.21.30.90; 211.99.13.47; };
          };
          };
          #這一段表示當(dāng)在211/8地址段進(jìn)行DNS查詢時(shí)
          #將按照211.99.13.47,202.93.22.13,210.21.30.90的順序返回地址,
          #也就是211.99.13.47是最靠近查詢地點(diǎn)的節(jié)點(diǎn)
          { 211/8;
          { 211.99.13.47;
          { 202.93.22.13; 210.21.30.90; };
          };
          };
          { 61/8;
          { 202.93.22.13;
          { 210.21.30.90; 211.99.13.47; };
          };
          };
          };
          };

          zone "." {
          type hint;
          file "root.cache";
          };

          zone "localhost" {
          type master;
          file "localhost";
          };

          zone "cdn.com" {
          type master;
          file "cdn.com";
          };

            3、Cache在CDN網(wǎng)絡(luò)中如果工作在服務(wù)器加速模式,因?yàn)榕渲美镆呀?jīng)寫明加速服務(wù)器的url,所以Cache直接匹配用戶請(qǐng)求,到源服務(wù)器 獲得內(nèi)容并緩存供下次使用;如果Cache工作在客戶端加速模式,Cache需要知道源服務(wù)器的IP地址,所以CDN網(wǎng)絡(luò)維護(hù)和運(yùn)行一個(gè)供Cache使用 的DNS服務(wù)器,解析域名的真實(shí)IP地址,如202.99.11.120 ,各域名的解析記錄與未加入CDN網(wǎng)絡(luò)之前一樣。

            4、工作在CDN網(wǎng)絡(luò)中緩存服務(wù)器必須工作在透明方式,對(duì)于Squid來(lái)說,需要設(shè)置以下參數(shù):


          httpd_accel_host virtual
          httpd_accel_port 80
          httpd_accel_with_proxy on
          httpd_accel_uses_host_header on


          posted @ 2005-10-30 14:48 bluesky 閱讀(341) | 評(píng)論 (0)編輯 收藏

          <html>?
          <head>?
          ??
          <meta?http-equiv="Content-Type"?content="text/html;?charset=gb2312">?
          ??
          <noscript><meta?http-equiv="refresh"?content="0;url=about:noscript">noscript>?
          ??
          <title>屏蔽鼠標(biāo)右鍵、Ctrl+N、Shift+F10、Alt+F4、F11、F5刷新、退格鍵title>?
          head>?
          <body>?
          <script?language="Javascript">