<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    FreeZone

      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      4 隨筆 :: 0 文章 :: 2 評論 :: 0 Trackbacks

    2005年10月30日 #

    在 Web 應用程序開發中,頁面重載循環是最大的一個使用障礙,對于 Java? 開發人員來說也是一個嚴峻的挑戰。在這個系列中,作者 Philip McCarthy 介紹了一種創建動態應用程序體驗的開創性方式。Ajax(異步 JavaScript 和 XML)是一種編程技術,它允許為基于 Java 的 Web 應用程序把 Java 技術、XML 和 JavaScript 組合起來,從而打破頁面重載的范式。

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

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

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

    更好的購物車

    可 以用 Ajax 增強傳統的 Web 應用程序,通過消除頁面裝入從而簡化交互。為了演示這一點,采用一個簡單的購物車示例,在向里面添加項目時,它會動態更新。這項技術如果整合到在線商 店,那么用戶可以持續地瀏覽和向購物車中添加項目,而不必在每次點擊之后都等候完整的頁面更新。雖然這篇文章中的有些代碼特定于購物車示例,但是演示的技 術可以應用于任何 Ajax 應用程序。清單 1 顯示了購物車示例使用的有關 HTML 代碼,整篇文章中都會使用這個 HTML。


    清單1. 購物車示例的有關片斷

    <!-- 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 對象。顧名思義,它允許客戶端腳本執行 HTTP 請求,并解析 XML 服務器響應。Ajax 往返過程的第一步是創建 XMLHttpRequest 的實例。在 XMLHttpRequest 對象上設置請求使用的 HTTP 方法(GETPOST)以及目標 URL。

    現在,您還記得 Ajax 的第一個 a 是代表 異步(asynchronous) 嗎?在發送 HTTP 請求時,不想讓瀏覽器掛著等候服務器響應。相反,您想讓瀏覽器繼續對用戶與頁面的交互進行響應,并在服務器響應到達時再進行處理。為了實現這個要求,可以在 XMLHttpRequest 上注冊一個回調函數,然后異步地分派 XMLHttpRequest。然后控制就會返回瀏覽器,當服務器響應到達時,會調用回調函數。

    在 Java Web 服務器上,請求同其他 HttpServletRequest 一樣到達。在解析了請求參數之后,servlet 調用必要的應用程序邏輯,把響應序列化成 XML,并把 XML 寫入 HttpServletResponse

    回到客戶端時,現在調用注冊在 XMLHttpRequest 上的回調函數,處理服務器返回的 XML 文檔。最后,根據服務器返回的數據,用 JavaScript 操縱頁面的 HTML DOM,把用戶界面更新。圖 1 是 Ajax 往返過程的順序圖。


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

    現在您對 Ajax 往返過程有了一個高層面的認識。下面我將放大其中的每一步驟,進行更詳細的觀察。如果過程中迷了路,請回頭看圖 1 —— 由于 Ajax 方式的異步性質,所以順序并非十分簡單。





    分派 XMLHttpRequest

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


    清單 2. 創建跨瀏覽器的 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 的瀏覽器的技術。目前,示例假設清單 2 的 newXMLHttpRequest 函數總能返回 XMLHttpRequest 實例。

    返回示例的購物車場景,想要當用戶在目錄項目上點擊 Add to Cart 時啟動 Ajax 交互。名為 addToCart()onclick 處理函數負責通過 Ajax 調用來更新購物車的狀態(請參閱 清單 1)。正如清單 3 所示,addToCart() 需要做的第一件事是通過調用清單 2 的 newXMLHttpRequest() 函數得到 XMLHttpRequest 對象。接下來,它注冊一個回調函數,用來接收服務器響應(我稍后再詳細解釋這一步;請參閱 清單 6)。

    因為請求會修改服務器上的狀態,所以將用 HTTP POST 做這個工作。通過 POST 發送數據要求三個步驟。第一,需要打開與要通信的服務器資源的 POST 連接 —— 在這個示例中,服務器資源是一個映射到 URL cart.do 的 servlet。然后,我在 XMLHttpRequest 上設置一個頭,指明請求的內容是表單 編碼的數據。最后,用表單編碼的數據作為請求體發送請求。

    清單 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 往返過程的第一部分,即創建和分派來自客戶機的 HTTP 請求。接下來是用來處理請求的 Java servlet 代碼。





    servlet 請求處理

    用 servlet 處理 XMLHttpRequest,與處理普通的瀏覽器 HTTP 請求一樣。可以用 HttpServletRequest.getParameter() 得到在 POST 請求體中發送的表單編碼數據。Ajax 請求被放進與來自應用程序的常規 Web 請求一樣的 HttpSession 中。對于示例購物車場景來說,這很有用,因為這可以把購物車狀態封裝在 JavaBean 中,并在請求之間在會話中維持這個狀態。

    清單 4 是處理 Ajax 請求、更新購物車的簡單 servlet 的一部分。Cart bean 是從用戶會話中獲得的,并根據請求參數更新它的狀態。然后 Cart 被序列化成 XML,XML 又被寫入 ServletResponse。重要的是把響應的內容類型設置為 application/xml,否則 XMLHttpRequest 不會把響應內容解析成 XML DOM。


    清單 4. 處理 Ajax 請求的 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。它很簡單。請注意 cart 元素的 generated 屬性,它是 System.currentTimeMillis() 生成的一個時間戳。


    清單 5. Cart 對象的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>

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

    現在您已經知道了 CartServlet 響應 XMLHttpRequest 的方式。下一件事就是返回客戶端,查看如何用 XML 響應更新頁面狀態。





    用 JavaScript 進行響應處理

    XMLHttpRequestreadyState 屬性是一個數值,它指出請求生命周期的狀態。它從 0(代表“未初始化”)變化到 4(代表“完成”)。每次 readyState 變化時,readystatechange 事件就觸發,由 onreadystatechange 屬性指定的事件處理函數就被調用。

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


    清單 6. getReadyStateHandler() 函數

    /*
    * 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 狀態碼

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

    關于 getReadyStateHandler()

    getReadyStateHandler() 是段相對復雜的代碼,特別是如果您不習慣閱讀 JavaScript 的話。但是通過把這個函數放在 JavaScript 庫中,就可以處理 Ajax 服務器響應,而不必處理 XMLHttpRequest 的內部細節。重要的是要理解如何在自己的代碼中使用 getReadyStateHandler()

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

    提取購物車數據

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


    清單 7. 更新頁面,反映購物車的 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");
    }

    到此,整個 Ajax 往返過程完成了,但是您可能想讓 Web 應用程序運行一下查看實際效果(請參閱 下載 一節)。這個示例非常簡單,有很多需要改進之處。例如,我包含了從購物車中清除項目的服務器端代碼,但是無法從 UI 訪問它。作為一個好的練習,請試著在應用程序現有的 JavaScript 代碼之上構建出能夠實現這個功能的代碼。





    使用 Ajax 的挑戰

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

    XMLHttpRequest 的可用性

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

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

    可用性考慮

    關于 Ajax 應用程序的某些可用性問題比較普遍。例如,讓用戶知道他們的輸入已經注冊了可能是重要的,因為沙漏光標和 spinning 瀏覽器的常用反饋機制“throbber”對 XMLHttpRequest 不適用。一種技術是用“Now updating...”類型的信息替換 Submit 按鈕,這樣用戶在等候響應期間就不會反復單擊按鈕了。

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

    服務器負載

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

    處理異步

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

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

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

    在這個系列的 第一篇文章 中,我介紹了 Ajax 的構造塊:

    • 如何用 JavaScript XMLHttpRequest 對象從 Web 頁面向服務器發送異步請求。
    • 如何用 Java servlet 處理和響應請求(向客戶機返回 XML 文檔)。
    • 如何在客戶端用響應文檔更新頁面視圖。

    這一次,將繼續討論 Ajax 開發的基礎知識,但是將側重于許多 Java Web 開發人員最關心的問題:為客戶機生成數據。

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

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

    關于示例

        使用一個示例應用程序和幾個用例來演示這里討論的技術特性和技術。圖 1 顯示的極為簡單的數據模型可以表示示例用例。這個模型代表在線商店中的顧客帳戶。顧客擁有以前訂單的集合,每個訂單包含幾個商品。


    圖 1. 簡單的對象模型
    代表顧客帳戶的對象模型

    雖然 XMLHttpRequest 對于發送數據使用的格式沒有做任何限制,但是對于多數目的來說,只發送傳統的表單數據是適合的,所以我的討論集中在服務器的響應上。響應也可以有基于文本的格式,但是正如它的名字表示的,XMLHttpRequest 具有內置的處理 XML 響應數據的能力。這使 XML 成為 Ajax 響應的默認選擇,所以我們從 XML 格式開始討論。





    從 Java 類產生 XML

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

        我們從 Java 對象產生 XML 數據的三種方法,并討論每種方法的優劣。





    自行進行序列化

    首先,可以從對象圖以編程的方式生成 XML。這種方式可以簡單到只是在每個 JavaBean 類中實現 toXml() 方法即可。然后就可以選擇合適的 XML API,讓每個 bean 提供表示自己狀態的元素,并遞歸地對自己的成員調用對象圖。顯然,這種方式無法擴展到大量的類,因為每個類都需要專門編寫自己的 XML 生成代碼。從好的方面來看,這是一個實現起來簡單的方式,沒有額外的配置支出或者更復雜的構建過程支出,任何 JavaBean 圖都可以只用幾個調用就變成 XML 文檔。

         前一篇文章 的示例代碼中,把 XML 標記字符串連接在一起,實現了 toXml() 方法。上次我就提到過,這是個糟糕的方法,因為它把確保標記配對、實體編碼等工作的負擔放在每個 toXml() 方法的代碼中。在 Java 平臺上有幾個 XML API 可以替您做這些工作,這樣您就可以把精力集中在 XML 的內容上。清單 1 用 JDOM API 實現了在線商店示例中表示訂單的類中的 toXml()(請參閱 圖 1)。


    清單 1. Order 類的 toXml() 的 JDOM 實現

    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 創建元素、使用屬性和添加元素內容有多么簡單。遞歸地調用復合 JavaBean 的 toXml() 方法是為了取得它們子圖的 Element 表示。例如,items 元素的內容是通過調用 Order 聚合的每個 Item 對象上的 toXml() 得到的。

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


    清單 2. 從 JDOM 元素生成 XML 響應

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


    清單 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 的一個主要不足。假設要用這個文檔表示顧客的訂單歷史視圖。在這種情況下,不太可能要顯示每個歷史訂單中每個商品的完整說明,或者告訴顧客他或她自己的姓名。但是如果應用程序有一個 ProductSearch 類,它就是以 Item bean 列表的形式返回搜索結果,那么在 Item 的 XML 表示中包含說明可能會有幫助。而且,Item 類上代表當前庫存水平的額外字段,在產品搜索視圖中可能就是需要顯示的有用信息。但是,不管當前的庫存水平是否與當前情況相關(比如對顧客的訂單歷史來說),這個字段都會從包含 Item 的任何對象圖中序列化出來。

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

    稍后會解決這些問題,但是首先來看一個對自行序列化方式的可伸縮性問題的解決方案:XML 綁定框架。





    XML 綁定框架

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

    代碼生成方式

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

    如果應用程序要使用大 型 XML 語法,那么代碼生成方式是個很好的方法。在數十個類上編寫定制 XML 序列化代碼的可伸縮性問題由此消除。另一方面,也不再需要定義自己的 JavaBean。框架生成的 Java 類通常非常符合 XML 的結構,所以對它們進行編碼很難。而且,生成的類變成啞數據容器,因為一般不能向它們添加行為。一般來說,在應用程序代碼中要做些妥協,才能很好地處理方 案生成的類型。另一個缺陷是如果修改方案,會造成生成的類也要修改,所以也就會對圍繞它們編寫的代碼帶來相應的影響。

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

    映射方式

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

    這種方法是在可伸縮性與 靈活性之間的良好折中。可以按照自己喜歡的方式編寫 Java 類,編排器負責處理 XML。雖然映射定義文件編寫起來簡單,可伸縮性也足夠好,但是映射規則最多只能改變標準的綁定行為,而且在對象結構和它們的 XML 表示之間總要殘留一些耦合。最終,可能不得不在 Java 表示或 XML 格式之間任選一個做些折中,才能讓映射方法起作用。

    數據綁定總結

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

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

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

    在傳統的 Web 應用程序開發中,采用頁面模板系統把視圖生成與控制器邏輯和模型數據干凈地分離。這種方法在 Ajax 場景中也會有幫助。





    頁面模板系統

    任何通用目的的頁面模板技術都可以用來生成 XML,從而使 Ajax 應用程序根據自己的數據模型生成任何 XML 響應文檔。額外收獲是:模板可以用簡單的、表現力強的標記語言編寫,而不是用一行行的 Java 代碼編寫。清單 5 是一個 JSP 頁面,采用了 Customer bean 并表示出定制的 XML 視圖,適合客戶端代碼生成訂單歷史組件。


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


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





    ${order.date}




    ${item.formattedPrice}







    這個簡潔的模板只輸出訂單歷史視圖需要的數據,不輸出不相關的資料(例如商品說明)。創建產品搜索視圖的定制 XML 應當同樣簡單,這個視圖包含每個商品的完整說明和庫存水平。

    模板的問題

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





    不用 XML 的響應數據

    迄 今為止,我介紹的所有技術都用 XML 文檔的形式生成服務器響應。但是,XML 有一些問題。其中一個就是延遲。瀏覽器不能立即解析 XML 文檔并生成 DOM 模型,所以這會降低某些 Ajax 組件需要的“迅捷”感,特別是在較慢的機器上解析大型文檔的時候更是如此。“現場搜索”就是一個示例,在這種搜索中,當用戶輸入搜索術語時,就會從服務器 提取搜索結果并顯示給用戶。對于現場搜索組件來說,迅速地響應輸入是非常重要的,但是同時它還需要迅速而持續地解析服務器的響應。

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


    清單 5. 在 JavaScript 中導航 XML 響應文檔

    // 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;

    當元素中間存在空白時,情況就變得更加復雜,因為每個元素的 firstChild 經常是個空白文本節點。現在有 JavaScript 庫可以緩解處理 XML 文檔的麻煩。這些庫包括 Sarissa (請參閱 參考資料)和 Google-ajaXSLT,這兩個庫都把 XPath 功能添加到了大多數瀏覽器中。

    但是,想想替代方案還是值得的。除了 responseXML 之外,XMLHttpRequest 對象還提供了名為 responseText 的屬性,這個屬性只是以字符串的方式提供服務器的響應體。

    responseText 屬性

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

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


    清單 6. 用正則表達式處理 XMLHttpRequest 的 responseText 對象

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

    var date = matches[1];

    在某些情況下,采用即時方式使用 responseText 會比較方便。但是,理想情況下,應當有種途徑,可以用一種能夠讓 JavaScript 輕松導航、卻沒有 XML 處理支出的格式表示復雜的結構化數據。幸運的是,確實存在這樣一種格式。





    JavaScript 對象標注

    實 際上,JavaScript 對象的大部分都由聯合數組、數字索引數組、字符串、數字或者這些類型的嵌套組合而成。因為所有類型都可以用 JavaScript 直接聲明,所以可以在一條語句中靜態地定義對象圖。清單 7 使用 JSON 語法聲明了一個對象,并演示了如何訪問這個對象。大括號表示聯合數組(即對象),它的鍵 -值組合由逗號分隔。方括號表示數字索引數組。


    清單 7. 用 JSON 在 JavaScript 中直接聲明一個簡單對象

    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 是一個有趣的語言特性,那么它對 Ajax 有什么意義呢?妙處在于可以用 JSON 在 Ajax 服務器響應中通過網絡發送 JavaScript 對象圖。這意味著在客戶端可以避免使用笨拙的 DOM API 對 XML 進行導航 —— 只需要分析 JSON 響應,就會立即得到可以訪問的 JavaScript 對象圖。但是,首先需要把 JavaBean 變成 JSON。

    從 Java 類產生 JSON

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


    清單 8. Order 類的 toJSONObject() 方法實現

    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 非常簡單。 JSONObject 代表 JavaScript 對象(即聯合數組),有不同的 put() 方法,方法接受的 String 鍵和值是原生類型、String 類型或其他 JSON 類型。JSONArray 代表索引數組,所以它的 put() 方法只接受一個值。請注意在清單 8 中,創建 jsonItems 數組,然后再用 put() 把它附加到 json 對象上;可以用另外一種方法做這項工作,就是對每個項目調用 json.accumulate("items",iter.next().toJSONObject());accumulate() 方法與 put() 類似,區別在于它把值添加到按照鍵進行識別的索引數組。

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


    清單 9. 從 JSONObject 生成序列化的 JSON 響應

    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());
    }

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


    清單 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 數據變成 JavaScript 對象。這可以通過對 eval() 的簡單調用實現,這個函數可以即時地解釋包含 JavaScript 表達式的字符串。清單 11 把 JSON 響應轉變成 JavaScript 對象圖,然后執行清單 5 的任務,從顧客的最后一次訂單中得到第一個商品的名稱。


    清單 11. 評估 JSON 響應

    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 可以發現使用 JSON 的客戶端的優勢。如果在 Ajax 項目中要在客戶端對許多復雜的服務器響應進行導航,那么 JSON 可能適合您的需要。JSON 和 XMLHttpRequest 結合還會讓 Ajax 交互看起來更像 RPC 調用而不是 SOA 請求,這對應用程序的設計可能會有意義。在下一篇文章中,我要研究的框架,就是明確地為了讓 JavaScript 代碼對服務器端對象進行遠程方法調用而設計的。

    JSON 的不足

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





    比較序列化技術

    現 在已經看到了把 Java 狀態傳輸到 Ajax 客戶端的五種不同技術。我討論了自行手工編碼 XML 序列化、通過代碼生成的 XML 綁定、通過映射機制的 XML 綁定、基于模板的 XML 生成以及手工編碼到 JSON 的序列化。每種技術都有自己的優勢和不足,分別適用于不同的應用程序架構。

    為了總結每種方式的優勢與不足,表 1 從六個方面進行了粗略的評分:

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

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




    結束語

    表 1 中的數據并不表明某項序列化技術比其他的技術好。畢竟,六種標準的相對重要性取決于項目的具體情況。例如,如果要處理數百種數據類型,這時想要的是可伸縮 性,那么代碼生成可能就是最好的選擇。如果需要為同一數據模型生成多個不同視圖,那么就應當使用頁面模板。如果處理的是小規模項目,想降低需要編寫的 JavaScript 代碼數量,那么請考慮 JSON。

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

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

    1.前言

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

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

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

    2.什么是CDN?

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

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

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

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

    3.CDN新應用和客戶

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

      CDN能夠為網絡的快速、安全、穩定、可擴展等方面提供保障。

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

    4.CDN 的工作原理

      在描述CDN的實現原理,讓我們先看傳統的未加緩存服務的訪問過程,以便了解CDN緩存訪問方式與未加緩存訪問方式的差別:

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

      1)、用戶向瀏覽器提供要訪問的域名;

      2)、瀏覽器調用域名解析函數庫對域名進行解析,以得到此域名對應的IP地址;

      3)、瀏覽器使用所得到的IP地址,域名的服務主機發出數據訪問請求;

      4)、瀏覽器根據域名主機返回的數據顯示網頁的內容。

      通過以上四個步驟,瀏覽器完成從用戶處接收用戶要訪問的域名到從域名服務主機處獲取數據的整個過程。CDN網絡是在用戶和服務器之間增加 Cache層,如何將用戶的請求引導到Cache上獲得源服務器的數據,主要是通過接管DNS實現,下面讓我們看看訪問使用CDN緩存后的網站的過程:

      通過上圖,我們可以了解到,使用了CDN緩存后的網站的訪問過程變為:

      1)、用戶向瀏覽器提供要訪問的域名;

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

      3)、此次解析得到CDN緩存服務器的IP地址,瀏覽器在得到實際的IP地址以后,向緩存服務器發出訪問請求;

      4)、緩存服務器根據瀏覽器提供的要訪問的域名,通過Cache內部專用DNS解析得到此域名的實際IP地址,再由緩存服務器向此實際IP地址提交訪問請求;

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

      6)、客戶端得到由緩存服務器返回的數據以后顯示出來并完成整個瀏覽的數據請求過程。

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

      1)、作為ICP,只需要把域名解釋權交給CDN運營商,其他方面不需要進行任何的修改;操作時,ICP修改自己域名的解析記錄,一般用cname方式指向CDN網絡Cache服務器的地址。

      2)、作為CDN運營商,首先需要為ICP的域名提供公開的解析,為了實現sortlist,一般是把ICP的域名解釋結果指向一個CNAME記錄;

      3)、當需要進行sorlist時,CDN運營商可以利用DNS對CNAME指向的域名解析過程進行特殊處理,使DNS服務器在接收到客戶端請求時可以根據客戶端的IP地址,返回相同域名的不同IP地址;

      4)、由于從cname獲得的IP地址,并且帶有hostname信息,請求到達Cache之后,Cache必須知道源服務器的IP地址,所以在CDN運營商內部維護一個內部DNS服務器,用于解釋用戶所訪問的域名的真實IP地址;

      5)、在維護內部DNS服務器時,還需要維護一臺授權服務器,控制哪些域名可以進行緩存,而哪些又不進行緩存,以免發生開放代理的情況。

    5.CDN的技術手段

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

      鏡像站點(Mirror Site)服務器是我們經常可以看到的,它讓內容直截了當地進行分布,適用于靜態和準動態的數據同步。但是購買和維護新服務器的費用較高,另外還必須在各 個地區設置鏡像服務器,配備專業技術人員進行管理與維護。大型網站在隨時更新各地服務器的同時,對帶寬的需求也會顯著增加,因此一般的互聯網公司不會建立 太多的鏡像服務器。

      高速緩存手段的成本較低,適用于靜態內容。Internet的統計表明,超過80%的用戶經常訪問的是20%的網站的內容,在這個規律下,緩存 服務器可以處理大部分客戶的靜態請求,而原始的WWW服務器只需處理約20%左右的非緩存請求和動態請求,于是大大加快了客戶請求的響應時間,并降低了原 始WWW服務器的負載。根據美國IDC公司的調查,作為CDN的一項重要指標-緩存的市場正在以每年近100%的速度增長,全球的營業額在2004年將達 到45

    6.CDN的網絡架構

      CDN網絡架構主要由兩大部分,分為中心和邊緣兩部分,中心指CDN網管中心和DNS重定向解析中心,負責全局負載均衡,設備系統安裝在管理中心機房,邊緣主要指異地節點,CDN分發的載體,主要由Cache和負載均衡器等組成。

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

      對于普通的Internet用戶來講,每個CDN節點就相當于一個放置在它周圍的WEB。通過全局負載均衡DNS的控制,用戶的請求被透明地指向離他最近的節點,節點中CDN服務器會像網站的原始服務器一樣,響應用戶的請求。由于它離用戶更近,因而響應時間必然更快。

      每個CDN節點由兩部分組成:負載均衡設備和高速緩存服務器

      負載均衡設備負責每個節點中各個Cache的負載均衡,保證節點的工作效率;同時,負載均衡設備還負責收集節點與周圍環境的信息,保持與全局負載DNS的通信,實現整個系統的負載均衡。

      高速緩存服務器(Cache)負責存儲客戶網站的大量信息,就像一個靠近用戶的網站服務器一樣響應本地用戶的訪問請求。

      CDN的管理系統是整個系統能夠正常運轉的保證。它不僅能對系統中的各個子系統和設備進行實時監控,對各種故障產生相應的告警,還可以實時監測 到系統中總的流量和各節點的流量,并保存在系統的數據庫中,使網管人員能夠方便地進行進一步分析。通過完善的網管系統,用戶可以對系統配置進行修改。

      理論上,最簡單的CDN網絡有一個負責全局負載均衡的DNS和各節點一臺Cache,即可運行。DNS支持根據用戶源IP地址解析不同的IP, 實現就近訪問。為了保證高可用性等,需要監視各節點的流量、健康狀況等。一個節點的單臺Cache承載數量不夠時,才需要多臺Cache,多臺Cache 同時工作,才需要負載均衡器,使Cache群協同工作。

    億美元。網絡流媒體的發展還將剌激這個市場的需求。

    7. CDN 示例

      商業化的CDN網絡是用于服務性質的,高可用性等要求非常高,有專業產品和CDN網絡解決方案,本文主要從理論角度,理解CDN的實現過程,并利用已有網絡環境和開源軟件做實際配置,更深刻理解CDN的具體工作過程。

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

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


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

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

      Bind 9的基本功能可以根據不同的源IP地址段解析對應的IP,實現根據地域就近訪問的負載均衡,一般可以通過Bind 9的sortlist選項實現根據用戶端IP地址返回最近的節點IP地址,具體的過程為:

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


    $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 ;有多少個CACHE地址
    cache IN A 210.21.30.90 ;就有多少個CACHE的A記錄
    cache IN A 211.99.13.47

      2) /etc/named.conf中的內容為:


    options {
    directory "/var/named";
    sortlist {
    #這一段表示當在本地執行查詢時
    #將按照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; };
    };
    };
    #這一段表示當在202/8地址段進行DNS查詢時
    #將按照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; };
    };
    };
    #這一段表示當在211/8地址段進行DNS查詢時
    #將按照211.99.13.47,202.93.22.13,210.21.30.90的順序返回地址,
    #也就是211.99.13.47是最靠近查詢地點的節點
    { 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網絡中如果工作在服務器加速模式,因為配置里已經寫明加速服務器的url,所以Cache直接匹配用戶請求,到源服務器 獲得內容并緩存供下次使用;如果Cache工作在客戶端加速模式,Cache需要知道源服務器的IP地址,所以CDN網絡維護和運行一個供Cache使用 的DNS服務器,解析域名的真實IP地址,如202.99.11.120 ,各域名的解析記錄與未加入CDN網絡之前一樣。

      4、工作在CDN網絡中緩存服務器必須工作在透明方式,對于Squid來說,需要設置以下參數:


    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 閱讀(338) | 評論 (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>屏蔽鼠標右鍵、Ctrl+N、Shift+F10、Alt+F4、F11、F5刷新、退格鍵title>?
    head>?
    <body>?
    <script?language="Javascript">