傳統上,要為基于 Web 的客戶(hù)機提供比較豐富的客戶(hù)機功能,必須創(chuàng )建基于 Web 的應用程序,它由來(lái)自各種不同技術(shù)的同質(zhì)系統組成,其中可能包括:
- 服務(wù)器端 Web 或應用服務(wù)器,比如 Apache HTTP Server、 Microsoft? Internet Information Services (IIS)、Sun Java? Web Server、IBM? WebSphere? 或 BEA WebLogic
- 服務(wù)器端腳本或處理語(yǔ)言,比如 Java、PHP、JavaServer Pages? (JSP) 或 Active Server Pages (ASP)
- 客戶(hù)端腳本和格式化,比如 HTML、Cascading Style Sheets (CSS)、JavaScript 或 DOM
- HTTP 通信協(xié)議或應用程序編程接口 (API),比如 XMLHttpRequests 或 JavaScript Serialized Object Notation (JSON)
但是現在,您可以使用 Jaxer,一種新的 Ajax 服務(wù)器,不僅將所有這些技術(shù)集成到一個(gè)可部署服務(wù)器中,還使用一些相同的基于客戶(hù)端技術(shù)(比如 JavaScript 代碼、DOM 等)提供了服務(wù)器端腳本和處理。Jaxer 是免費的開(kāi)源代碼,您可以原樣使用,或者使用其 JavaScript 框架進(jìn)一步擴展。
想象一下,能夠直接在 HTML 頁(yè)面中使用 JavaScript 代碼,并簡(jiǎn)單指明代碼應在客戶(hù)端返回 HTML 之前在服務(wù)器端執行。這將允許客戶(hù)端直接與服務(wù)器端進(jìn)一步通信,而無(wú)需刷新頁(yè)面。此外,得到的 HTML 可以基于在服務(wù)器端執行的 JavaScript 代碼。這將減少需要使用的技術(shù)種類(lèi)和您必須編寫(xiě)的代碼量,從而為開(kāi)發(fā)人員和用戶(hù)提供更好的整體體驗。
對于用戶(hù),您現在可以提供與富原生應用程序更接近的基于 Ajax 的功能。Jaxer 使其成為可能,因為它實(shí)際上是第一個(gè)真正的 Ajax 服務(wù)器。您不需要確定您的 Ajax 代碼運行在哪個(gè)瀏覽器中。您也不必編寫(xiě)協(xié)議代碼來(lái)執行服務(wù)器通信。通過(guò)調用簡(jiǎn)單的 Jaxer API,您提供了具有最少缺陷的健壯的 Web 應用程序。甚至更重要的是,您再也不必使用內嵌的 JavaScrip 公開(kāi)所有代碼。您實(shí)際上可以使用 Jaxer 統一客戶(hù)機代碼與服務(wù)器代碼,將策略代碼安全地隱藏在防火墻后面,并允許它仍可從客戶(hù)機訪(fǎng)問(wèn)。
在使用 Jaxer 開(kāi)發(fā)任何基于 Web 的應用程序之前,您首先必須將其安裝在您的機器或開(kāi)發(fā)環(huán)境中。有三個(gè)選擇。Jaxer 可用于 Microsoft Windows?、Mac OS X 或 Linux?。Jaxer 安裝是自包含、獨立的 Apache/Jaxer 服務(wù)器。但是,您還可以在現有的 Apache 或 Jetty 環(huán)境中將其作為模塊安裝。Aptana 報告計劃在不久的將來(lái)支持 IIS。
出于本文目的,因為大多數開(kāi)發(fā)人員在 Windows 上編碼,然后部署到 Windows 或 *NIX 環(huán)境(比如 UNIX? 或 Linux)中,所以我選擇安裝獨立的 Windows 版本。在 Windows 上安裝十分簡(jiǎn)單。您只需轉至 Aptana Jaxer 下載頁(yè)面(請參見(jiàn) 參考資料 中此頁(yè)面的鏈接),并下載 Windows 獨立版本的壓縮文件(.zip)。在撰寫(xiě)本文時(shí),最新的版本是 0.9.7.2472。
下載并打開(kāi)壓縮文件之后,將 Aptana Jaxer 文件夾復制到硬盤(pán)驅動(dòng)器上。我直接復制到我的 C: 驅動(dòng)器上。因此,我可以通過(guò)轉至 C:\Aptana Jaxer 訪(fǎng)問(wèn)我的根文件夾來(lái)訪(fǎng)問(wèn) Jaxer。
Aptana Jaxer 文件夾中有許多文件和文件夾。根文件夾包含下列文件:
- ConfigureFirewall.exe
- LICENSE.TXT
- README.TXT
- StartServers.bat
它還包含下列文件夾:
- Apache22
- data
- jaxer
- local_jaxer
- logs
- public
- tmp
啟動(dòng)服務(wù)器甚至比前面的安裝更簡(jiǎn)單。只需運行 StartServers.bat 文件,然后就顯示啟動(dòng)窗口,如圖 1 所示。
圖 1. Jaxer 啟動(dòng)窗口

只要您打算在 Jaxer 環(huán)境中使用和測試應用程序,就必須一直打開(kāi)此窗口。通過(guò)打開(kāi)瀏覽器窗口并導航至 http://localhost:8081/aptana,可以測試 Jaxer 服務(wù)器是否正在正常運行。About Jaxer 頁(yè)面顯示在瀏覽器中,如圖 2 所示。
圖 2. About Jaxer 頁(yè)面

Samples 文件夾中還有其他基于 Jaxer 的 Web 應用程序,位于 <jaxer_install_dir>\jaxer\aptana\samples 中。您將找到幾個(gè)應用程序,其中包括表 1 中描述的那些。
表 1. Jaxer Samples 文件夾中包括的應用程序
| 應用程序 | 描述 |
|---|---|
| chat | 演示聊天消息的發(fā)送和接收,而不必刷新整個(gè)頁(yè)面。 |
| csajax | 使用 Ajax 為那些嘗試使用 JavaScript 代碼與客戶(hù)機瀏覽器某些域通信的 Web 應用程序通常面臨的問(wèn)題提供解決方案。相反,基于瀏覽器的客戶(hù)機使用 Jaxer 與服務(wù)器通信,然后允許服務(wù)器與其他域通信,實(shí)際上為跨站點(diǎn)問(wèn)題提供了解決方案。有關(guān)此問(wèn)題的更多信息,請參見(jiàn) “參考資料”。 |
| logging | 展示如何包裝服務(wù)器端日志并使用客戶(hù)端 JavaScript 進(jìn)行訪(fǎng)問(wèn)。 |
| rss-sample | 演示可以如何劃分頁(yè)面以在頁(yè)面的一部分中顯示新故事列表,并在頁(yè)面的另一部分中顯示選定的故事。故事顯示在自己的區段中,而不會(huì )刷新整個(gè)頁(yè)面;一切工作使用 Ajax 和 Jaxer 完成。 |
| smtp-email | 允許使用 Jaxer Simple Mail Transfer Protocol (SMTP) 對象異步發(fā)送電子郵件。 |
| tasks | 允許使用異步通信在單個(gè) to-do 或任務(wù)列表中添加、修改或刪除項目。 |
| wikilite | 允許用戶(hù)以編輯模式為簡(jiǎn)單的 wiki 提供文本,保存文本,然后在將來(lái)查看文本。 |
可以通過(guò)單擊圖 2 所示的 Samples and Tools 鏈接,訪(fǎng)問(wèn)這些示例。得到的 Samples and Tools 頁(yè)面如圖 3 所示。
圖 3. Samples and Tools 頁(yè)面

在本文后面,您將使用任務(wù)示例應用程序進(jìn)一步了解 Aptana Jaxer。
您可能已經(jīng)注意到,還有幾個(gè) Web 工具和診斷工具可用于與 Jaxer 交互,并在出現問(wèn)題時(shí)診斷問(wèn)題。例如,單擊 Server Diagnostics 鏈接返回 Jaxer Diagnostics 頁(yè)面,如圖 4 所示。
圖 4. Jaxer Diagnostics 頁(yè)面

介紹 Jaxer 根文件夾時(shí),我提到了它里面有幾個(gè)子文件夾。其中一個(gè)文件夾名為 public。您可以使用此文件夾提供自己的內容。如果這樣做,而且您僅在瀏覽器地址行中指定 host:port 地址,則它將從瀏覽器中調用。例如,http://localhost:8081/ 返回您提供的任何索引文件。
甚至更好的是,您可以在 public 文件夾中創(chuàng )建多個(gè)項目,并通過(guò)名稱(chēng)加載項目。例如,創(chuàng )建名為 my_project 的項目。此文件夾的完整路徑應為 C:\<jaxer_root>\public\my_project。
現在,創(chuàng )建名為 index.html 的索引文件,并將其放入 my_project 文件夾中。通過(guò)在瀏覽器中輸入其路徑,可以使用 Jaxer 服務(wù)器加載此文件。在這種情況下,輸入 http://localhost:8081/my_project??梢栽诖宋募A或其任何子文件夾中放置任何 HTML、圖片、JavaScript 文件或 CSS 文件。
在我的 my_project 中,我選擇創(chuàng )建一個(gè) index.html 文件,它是從 Aptana 網(wǎng)站上獲得的快速啟動(dòng)代碼中借來(lái)的。此代碼演示了服務(wù)器端 DOM、JavaScript 代碼和回調,如清單 1 所示。
清單 1. 測試 Jaxer 的示例代碼
<html> <head> <title>Quick Jaxer Sample</title> </head><body> <h3>Quick Jaxer Sample</h3> <p>This demonstrates server-side DOM, JavaScript and callbacks.</p> <script runat="server-proxy"> document.write("This is Jaxer version " + Jaxer.buildNumber); function getLatestVersion() { var url = "http://update.aptana.com/update/jaxer/win32/version.txt"; try { var versionString = Jaxer.Web.get(url); } catch (e) { throw "Could not retrieve version number from " + url; } var matches = versionString.match(/\"([\.\d]+)\"/); return (matches && matches.length > 1) ? matches[1] : "(unknown)"; } </script><input type="button" value="Check Latest" onclick="alert('Latest version: ' + getLatestVersion())"></body> </html> |
執行時(shí),此代碼生成如圖 5 所示的頁(yè)面。
圖 5. my_project 中使用的快速啟動(dòng)代碼

如果查看發(fā)送到瀏覽器中的實(shí)際 HTML 源碼,您將發(fā)現它與原始代碼非常不同。請看清單 2。
清單 2. 返回給瀏覽器的 Jaxer 生成的 HTML 代碼
<html><head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"><script src="\jaxer\framework\clientFramework_compressed.js? version=0.9.7.2472"></script><script>Jaxer.Callback.pageSignature = -1837648436; Jaxer.Callback.pageName = 'localhost:8081/my_project'; Jaxer.CALLBACK_URI = '/jaxer-server/callback'; Jaxer.ALERT_CALLBACK_ERRORS = true;</script> <title>Quick Jaxer Sample</title> </head><body> <h3>Quick Jaxer Sample</h3> <p>This demonstrates server-side DOM, JavaScript and callbacks.</p> <script>function getLatestVersion() {return Jaxer.remote("getLatestVersion", arguments);}function getLatestVersionAsync(callback) {return Jaxer.remote( "getLatestVersion", arguments, callback);}</script>This is Jaxer version 0.9.7.2472 <input value="Check Latest" onclick= "alert('Latest version: ' + getLatestVersion())" type="button"></body></html> |
注意,原始代碼不需要包括任何外部 JavaScript 文件。接下來(lái),查看一下 <script> 塊封裝的 JavaScript 部分。此代碼與通常為瀏覽器編寫(xiě)的 JavaScript 代碼之間的惟一差別是,此代碼使用 runat 屬性指定此代碼應使用 server-proxy 執行。
在查看 Jaxer 架構之前,首先看一下傳統應用程序的架構,如圖 6 所示。
圖 6. 傳統應用程序堆棧

所有堆棧從客戶(hù)機到服務(wù)器的請求開(kāi)始。在傳統情況下,服務(wù)器端使用一種或多種技術(shù)處理請求來(lái)進(jìn)行響應,這些技術(shù)可能包括 PHP、Java 代碼、C#、Ruby On Rails 或任何數量的其他腳本/對象技術(shù)。服務(wù)器端可以執行數據庫或文件訪(fǎng)問(wèn),處理數據,然后格式化數據,并將其返回給瀏覽器。這些技術(shù)是單獨的組件,必須通過(guò)連接代碼連接在一起。
如果基于 Web 的應用程序使用 Ajax,它必須采取步驟來(lái)處理客戶(hù)端請求,封裝調用/參數,并發(fā)送請求。在服務(wù)器端,代碼必須打開(kāi)請求以確定調用/參數,然后執行調用,接收響應,重新封裝響應,并將其返回給客戶(hù)機。然后,客戶(hù)機必須展開(kāi)響應,最后進(jìn)行處理。封裝/打開(kāi)可以使用 JSON 完成,實(shí)際的請求/響應/回調可以使用 XMLHttpRequest 完成。相比之下,請查看一下 Jaxer 堆棧,如圖 7 所示。
圖 7. Jaxer 應用程序堆棧

注意,堆棧要簡(jiǎn)單得多。還要注意,無(wú)需外部腳本;全部操作在實(shí)際的 HTML 中處理。通過(guò)使用 runat 屬性指定位置,JavaScript 代碼只瞄準它運行的位置。
當客戶(hù)機代碼需要調用服務(wù)器端時(shí),Jaxer 無(wú)縫地將 JavaScript 包裝器代碼封裝到返回給瀏覽器的 HTML 中。這就是清單 2 為什么與清單 1 中的原始代碼有所不同的原因。
如果您覺(jué)得奇怪的話(huà),Jaxer 的核心實(shí)際是用 C++ 編寫(xiě)的。它還集成了 Mozilla 引擎,為 HTML、CSS 和 JavaScript 代碼提供解析和 API。從程序員觀(guān)點(diǎn)來(lái)看,您看到的是作為框架提供的純 JavaScript 代碼,可用于實(shí)現服務(wù)器端 JavaScript 代碼和客戶(hù)端 Ajax。
Jaxer 提供服務(wù)來(lái)支持應用程序的開(kāi)發(fā),其中需要下列技術(shù):
- Ajax
- 來(lái)自客戶(hù)機或服務(wù)器的數據庫通信
- 客戶(hù)端或服務(wù)器端日志
- 會(huì )話(huà)操作
- 客戶(hù)端或服務(wù)器端網(wǎng)絡(luò ) API 連接
- 文件對象
- 等等……
此外,可以將 Jaxer 與傳統技術(shù)(比如 Java 代碼)集成以幫助解決跨平臺問(wèn)題。
為了更好地理解如何構建 Jaxer 應用程序,我想為您演示 Jaxer 附帶的示例站點(diǎn)。您將查看任務(wù)應用程序,您可以通過(guò)轉至 http://localhost:8081/aptana/samples/tasks 來(lái)訪(fǎng)問(wèn)它。將顯示圖 8 所示的頁(yè)面。我已經(jīng)添加了一些任務(wù)。
圖 8. 任務(wù)示例主頁(yè)面

從用戶(hù)觀(guān)點(diǎn)來(lái)看,任務(wù)應用程序非常簡(jiǎn)單。您只需在 New 框中鍵入新任務(wù)并單擊 add。要刪除項目,只需單擊您要刪除的項目旁邊的復選框。
請查看清單 3,其中顯示了任務(wù) HTML 頁(yè)面中嵌入的一些 JavaScript 代碼。
清單 3. 客戶(hù)機和服務(wù)器上運行的任務(wù) JavaScript 代碼
<script type="text/javascript" runat="both"> /* * Easy access to a named element in the DOM */ function $(id) { return document.getElementById(id); } /* * */ function addTaskToUI(description, id) { var newId = id || Math.ceil(1000000000 * Math.random()); var div = document.createElement("div"); div.id = "task_" + newId; div.className = "task"; var checkbox = document.createElement("input"); checkbox.setAttribute("type", "checkbox"); checkbox.setAttribute("title", "done"); checkbox.setAttribute("id", "checkbox_" + newId); Jaxer.setEvent(checkbox, "onclick", "completeTask(" + newId + ")"); div.appendChild(checkbox); var input = document.createElement("input"); input.setAttribute("type", "text"); input.setAttribute("size", "60"); input.setAttribute("title", "description"); input.setAttribute("id", "input_" + newId); input.value = description; Jaxer.setEvent(input, "onchange", "saveTaskInDB(" + newId + ", this.value)"); div.appendChild(input); $("tasks").insertBefore(div, $("tasks").firstChild); if (!Jaxer.isOnServer) { saveTaskInDB(newId, description); } }</script> |
首先,注意有多個(gè) JavaScript 塊。第一個(gè)塊具有自己的 runat 屬性集,這意味著(zhù)其中的代碼將可用于客戶(hù)機和服務(wù)器端。清單 3 中的函數提供了可由在客戶(hù)機和服務(wù)器端上執行的其他函數訪(fǎng)問(wèn)的一般函數。$ 函數只基于提供的 ID 返回一個(gè)元素。addTaskToUI 使用 createElement 和 insertBefore 方法創(chuàng )建用戶(hù)接口,在客戶(hù)端提供任務(wù)。
此處對任何特定于 Jaxer 的 API 沒(méi)有太多調用,只有附加到輸入和復選框的事件定義,以及與 Jaxer.isOnServer 屬性的邏輯比較,如果 JavaScript 代碼當前在服務(wù)器端執行,Jaxer.isOnServer 則設置為真。
Jaxer.setEvent 用于建立通信代理,此代理用于從客戶(hù)機對服務(wù)器進(jìn)行調用。在下文中,我將詳細介紹。
基于傳遞給 runat 屬性的值,下一段 JavaScript 代碼固定在服務(wù)器上運行,如清單 4 所示。
清單 4. 任務(wù)服務(wù)器端 JavaScript 代碼
<script type="text/javascript" runat="server"> /* * The SQL to create the database table we'll use to store the tasks */ var sql = "CREATE TABLE IF NOT EXISTS tasks " + "( id INTEGER NOT NULL" + ", description VARCHAR(255)" + ", created DATETIME NOT NULL" + ")"; // Execute the sql statement against the default Jaxer database Jaxer.DB.execute(sql); /* * Set the 'onserverload' property to call our function * once the page has been fully * loaded server-side. We could have also set this attribute * on the <body> tag of our * page and had it call a function by name. */ window.onserverload = function() { var resultSet = Jaxer.DB.execute("SELECT * FROM tasks ORDER BY created"); for (var i=0; i<resultSet.rows.length; i++) { var task = resultSet.rows[i]; addTaskToUI(task.description, task.id); } } /* * Save a task directly into the database */ function saveTaskInDB(id, description) { var resultSet = Jaxer.DB.execute( "SELECT * FROM tasks WHERE id = ?", [id]); if (resultSet.rows.length > 0) // task already exists { Jaxer.DB.execute("UPDATE tasks SET description = ? WHERE id = ?", [description, id]); } else // insert new task { Jaxer.DB.execute("INSERT INTO tasks (id, description, created) " + "VALUES (?, ?, ?)", [id, description, new Date()]); } } // Because we want this function callable from the client, we set its proxy // value to true saveTaskInDB.proxy = true; /* * Delete a task from the database */ function deleteSavedTask(id) { Jaxer.DB.execute("DELETE FROM tasks WHERE id = ?", [id]); } // Because we want this function callable from the client, we set its proxy // value to true deleteSavedTask.proxy = true;</script> |
運行此示例時(shí),請在瀏覽器中查看由 Jaxer 生成的源代碼。注意,清單 4 中的代碼消失了。非常方便的是,定義的函數已經(jīng)轉換為真實(shí)函數的簡(jiǎn)單 shell。取而代之的是,創(chuàng )建了簡(jiǎn)單的 Jaxer 遠程。
清單 4 提供了一些有趣的 JavaScript 代碼。雖然全部是 JavaScript 代碼,但要現在可以在服務(wù)器端定義和運行 JavaScript 代碼。在此代碼段的第一部分中,注意創(chuàng )建了 Structured Query Language (SQL) 表(如果不存在的話(huà))。接下來(lái),建立了類(lèi)似瀏覽器端的 onload 的事件,只不過(guò)這個(gè)位于服務(wù)器端。在 Jaxer 加載整個(gè)文件,并記錄所有的全局服務(wù)器函數和變量之后,調用定義并分配給 onserverload 的函數。
在本例中,定義的方法包括 saveTaskInDB 和 deleteSavedTask。要讓函數可從客戶(hù)端調用,必須定義代理。通過(guò)將函數的 proxy 屬性設置為真,可完成此操作。
Jaxer 加載文件之后,分配給 onserverload 的功能將執行。在本例中,函數執行 SQL 調用從數據庫中檢索現有任務(wù)。接下來(lái),它繼續遍歷所有任務(wù)并調用 addTaskToUI 方法。如果您沒(méi)記錯的話(huà),此方法將復選框和輸入框插入到 HTML 文檔中。但等一等!在本例中,這將意味著(zhù),服務(wù)器實(shí)際正在將這些復選框和輸入框推入文檔中。在服務(wù)器端執行此代碼時(shí),惟一在 addTaskToUI 中不執行的代碼是邏輯檢查中封裝的代碼,用于查看 isOnServer 是真還是假。只有客戶(hù)端需要這段代碼,用于對服務(wù)器執行遠程調用,以將任務(wù)添加到數據庫中。
清單 4 的函數中散布著(zhù)各種 Jaxer 調用,大多數用于處理數據庫交互。而且,您可以看到,不需要任何外部組件、facade 或 Data Access Objects (DAO)。它使用對 Jaxer API 的簡(jiǎn)單調用來(lái)執行 SQL 函數。切記,沒(méi)有什么可以阻止您使用更特定的 JavaScript 組件封裝這些簡(jiǎn)單的調用,這些組件似乎更熟悉數據庫的實(shí)際詳細信息,實(shí)際上通過(guò) SQL 數據庫提供了一個(gè) facade。
任務(wù)示例中其余的 JavaScript 代碼只處理您與表單控件交互時(shí)發(fā)生的事件,如清單 5 所示。
清單 5. 任務(wù)中其余的 JavaScript 代碼,僅在客戶(hù)端執行。
<script type="text/javascript"> /* * This client function sets a task as completed and * calls the server-side function * 'deleteSavedTask' to remove it from the database */ function completeTask(taskId) { var div = $("task_" + taskId); div.parentNode.removeChild(div); deleteSavedTask(taskId); } /* * Create a new task and add it to the user interface */ function newTask() { var description = $('txt_new').value; if (description != '') { addTaskToUI(description); $('txt_new').value = ''; } } /* * Create a new task if the enter key was hit */ function newKeyDown(evt) { if (evt.keyCode == 13) { newTask(); return false; } } </script> |
此代碼是普通的舊 JavaScript 客戶(hù)機代碼,僅在客戶(hù)端執行。但是要注意,這些函數調用了服務(wù)器端 JavaScript 代碼中封裝的函數。HTML 生成的代碼為這些函數提供代理方法,這些方法調用服務(wù)器端的遠程方法。服務(wù)器端方法的實(shí)際函數或代碼絕不公開(kāi)給客戶(hù)端。
我希望您能夠看到 Jaxer 必須提供的巨大潛能,甚至是在早期階段。是的,它正出于萌芽階段,可能還有待成熟。但是,它展示了許多承諾。畢竟,它可以在服務(wù)器端運行 JavaScript 代碼,在客戶(hù)端運行 Ajax,而且構建于許多現有的成熟技術(shù)之上,僅僅這些事實(shí)就足夠有說(shuō)服力。它很容易設置,而且可以運行在現有 Web 服務(wù)器環(huán)境中,而不出現任何問(wèn)題,或很少出現問(wèn)題。我在我的 Windows 機器上進(jìn)行了嘗試,該機器當前運行 Apache 以及 PHP、MySQL 等,沒(méi)有出現任何問(wèn)題。它從來(lái)沒(méi)有停止我的其他 Apache 實(shí)例,而且能夠在端口 8081 上與 Jaxer 實(shí)例通信,也沒(méi)有出現問(wèn)題。我看到的惟一可能出現問(wèn)題的情況是,您當前正將端口 8081 用于其他地方(比如 Tomcat)。



