Jetty 是一個用 Java 實作、開源、基于标準的,并且具有豐富功能的 Http 伺服器和 Web 容器,可以免費的用于商業行為。Jetty 這個項目成立于 1995 年,現在已經有非常多的成功産品基于 Jetty,比如 Apache Geromino, JBoss, IBM Tivoli, Cisco SESM 等。Jetty 可以用來作為一個傳統的 Web 伺服器,也可以作為一個動态的内容伺服器,并且 Jetty 可以非常容易的嵌入到 Java 應用程式當中。
特性簡介
易用性
易用性是 Jetty 設計的基本原則,易用性主要展現在以下幾個方面:
- 通過 XML 或者 API 來對 Jetty 進行配置;
- 預設配置可以滿足大部分的需求;
- 将 Jetty 嵌入到應用程式當中隻需要非常少的代碼;
可擴充性
在使用了 Ajax 的 Web 2.0 的應用程式中,每個連接配接需要保持更長的時間,這樣線程和記憶體的消耗量會急劇的增加。這就使得我們擔心整個程式會因為單個元件陷入瓶頸而影響整個程式的性能。但是有了 Jetty:
- 即使在有大量服務請求的情況下,系統的性能也能保持在一個可以接受的狀态。
- 利用 Continuation 機制來處理大量的使用者請求以及時間比較長的連接配接。
另外 Jetty 設計了非常良好的接口,是以在 Jetty 的某種實作無法滿足使用者的需要時,使用者可以非常友善地對 Jetty 的某些實作進行修改,使得 Jetty 适用于特殊的應用程式的需求。
易嵌入性
Jetty 設計之初就是作為一個優秀的元件來設計的,這也就意味着 Jetty 可以非常容易的嵌入到應用程式當中而不需要程式為了使用 Jetty 做修改。從某種程度上,你也可以把 Jetty 了解為一個嵌入式的Web伺服器。
部署應用程式
将自己的應用程式部署到 Jetty 上面是非常簡單的,首先将開發好的應用程式打成 WAR 包放到 Jetty 的 Webapps 目錄下面。然後用如下的指令來啟動 Jetty 伺服器:
Java –jar start.jar
, 在啟動伺服器後。我們就可以通路我們的應用程式了,Jetty 的預設端口是 8080,WAR 的名字也就是我們的應用程式的 Root Context。例如一個典型的 URL 就是:http://127.0.0.1:8080/sample/index.jsp 。
如何将 Jetty 嵌入到程式當中
将 Jetty 嵌入到程式當中是非常簡單的, 如 代碼 1 所示:首先我們建立一個 Server 對象, 并設定端口為 8080,然後為這個 Server 對象添加一個預設的 Handler。接着我們用配置檔案 jetty.xml 對這個 server 進行設定,最後我們使用方法
server.start()
将 Server 啟動起來就可以了。從這段代碼可以看出,Jetty 是非常适合用于作為一個元件來嵌入到我們的應用程式當中的,這也是 Jetty 的一個非常重要的特點。
清單 1. 代碼片斷
public class JettyServer {
public static void main(String[] args) {
Server server = new Server(8080);
server.setHandler(new DefaultHandler());
XmlConfiguration configuration = null;
try {
configuration = new XmlConfiguration(
new FileInputStream("C:/development/Jetty/jetty-6.1.6rc0/etc/jetty.xml"));
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (SAXException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
try {
configuration.configure(server);
server.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
接下來我們分析一下 Jetty Server 是如何啟動的。首先我們注意到 Server 類,這個類實際上繼承了 HttpServer, 當啟動 Jetty 伺服器的時候,就是說,在 Jetty 根目錄下的指令行下如果輸入
java -jar start.jar etc/jetty.xml
,注意這裡有一個配置檔案 jetty.xml 做為運作參數,這個參數也可以是其它的配置檔案,可以是多個 XML 配置檔案,其實這個配置檔案好比我們使用 Struts 時的 struts-config.xml 檔案,将運作 Server 需要用到的元件寫在裡面,比如上一節中 HttpServer 的配置需要的元件類都可以寫在這個配置檔案中。按上述方法啟動 Jetty Server 時,就會調用 Server 類裡面的
main
方法,這個入口方法首先會構造一個 Server 類執行個體(其實也就構造了一個 HttpServer),建立執行個體的過程中就會構造 XmlConfiguration 類的對象來讀取參數配置檔案,之後再由這個配置檔案産生的 XmlConfiguration 對象來配置這個 Server,配置過程其實是運用了 Java 的反射機制,調用 Server 的方法并傳入配置檔案中所寫的參數來向這個 Server 添加 HttpListener,HttpContext,HttpHandler,以及 Web Application(對應于我們的 Web 應用)。
Jetty 的 Continuation 機制
讨論 Jetty 的 Continuation 機制,首先需要提到 Ajax 技術,Ajax 技術是目前開發 Web 應用的非常熱門的技術,也是 Web 2.0 的一個重要的組成部分。Ajax 技術中的一個核心對象是 XMLHttpRequest 對象,這個對象支援異步請求,所謂異步請求即是指當用戶端發送一個請求到伺服器的時候,用戶端不必一直等待伺服器的響應。這樣就不會造成整個頁面的重新整理,給使用者帶來更好的體驗。而當伺服器端響應傳回時,用戶端利用一個 Javascript 函數對傳回值進行處理,以更新頁面上的部分元素的值。但很多時候這種異步事件隻是在很小一部分的情況下才會發生,那麼怎麼保證一旦伺服器端有了響應之後用戶端馬上就知道呢,我們有兩種方法來解決這個問題,一是讓浏覽器每隔幾秒請求伺服器來獲得更改,我們稱之為輪詢。二是伺服器維持與浏覽器的長時間的連接配接來傳遞資料,長連接配接的技術稱之為 Comet。
大家很容易就能發現輪詢方式的主要缺點是産生了大量的傳輸浪費。因為可能大部分向伺服器的請求是無效的,也就是說用戶端等待發生的事件沒有發生,如果有大量的用戶端的話,那麼這種網絡傳輸的浪費是非常厲害的。特别是對于伺服器端很久才更新的應用程式來講,比如郵件程式,這種浪費就更是巨大了。并且對 Server 端處理請求的能力也相應提高了要求。如果很長時間才向 Server 端發送一次請求的話,那麼用戶端就不能的得到及時的響應。
如果使用 Comet 技術的話,用戶端和伺服器端必須保持一個長連接配接,一般情況下,伺服器端每一個 Servlet 都會獨占一個線程,這樣就會使得伺服器端有很多線程同時存在,這在用戶端非常多的情況下也會對伺服器端的處理能力帶來很大的挑戰。
Jetty 利用 Java 語言的非堵塞 I/O 技術來處理并發的大量連接配接。 Jetty 有一個處理長連接配接的機制:一個被稱為 Continuations 的特性。利用 Continuation 機制,Jetty 可以使得一個線程能夠用來同時處理多個從用戶端發送過來的異步請求,下面我們通過一個簡化的聊天程式的伺服器端的代碼來示範不使用 Continuation 機制和使用 Continuation 的差别。
清單 2. Continuation 機制
public class ChatContinuation extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response){
postMessage(request, response);
}
private void postMessage(HttpServletRequest request, HttpServletResponse response)
{
HttpSession session = request.getSession(true);
People people = (People)session.getAttribute(session.getId());
if (!people.hasEvent())
{
Continuation continuation =
ContinuationSupport.getContinuation(request, this);
people.setContinuation(continuation);
continuation.suspend(1000);
}
people.setContinuation(null);
people.sendEvent(response);
}
}
大家注意到,首先擷取一個 Continuation 對象,然後把它挂起 1 秒鐘,直到逾時或者中間被
resume
函數喚醒位置,這裡需要解釋的是,在調用完
suspend
函數之後,這個線程就可處理其他的請求了,這也就大大提高了程式的并發性,使得長連接配接能夠獲得非常好的擴充性。
如果我們不使用 Continuation 機制,那麼程式就如 清單 3 所示:
清單 3. 不使用 Continuation 機制
public class Chat extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response){
postMessage(request, response);
}
private void postMessage(HttpServletRequest request, HttpServletResponse response)
{
HttpSession session = request.getSession(true);
People people = (People)session.getAttribute(session.getId());
while (!people.hasEvent())
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
people.setContinuation(null);
people.sendEvent(response);
}
}
大家注意到在等待事件發生的時間裡,線程被挂起,直到所等待的事件發生為止,但在等待過程中,這個線程不能處理其他請求,這也就造成了在用戶端非常多的情況下伺服器的處理能力跟不上的情況。下面我們解釋一下 Jetty 的 Continuation 的機制是如何工作的。
為了使用 Continuatins,Jetty 必須配置為使用它的
SelectChannelConnector
處理請求。這個 connector 建構在 java.nio API 之上,允許它維持每個連接配接開放而不用消耗一個線程。當使用
SelectChannelConnector
時,
ContinuationSupport.getContinuation()
提供一個
SelectChannelConnector.RetryContinuation
執行個體(但是,您必須針對
Continuation
接口程式設計)。當在
RetryContinuation
上調用
suspend()
時,它抛出一個特殊的運作時異常 --
RetryRequest
,該異常傳播到 servlet 外并且回溯到 filter 鍊,最後被
SelectChannelConnector
捕獲。但是不會發送一個異常響應給用戶端,而是将請求維持在未決 Continuations 隊列裡,則 HTTP 連接配接保持開放。這樣,用來服務請求的線程傳回給 ThreadPool,然後又可以用來服務其他請求。暫停的請求停留在未決 Continuations 隊列裡直到指定的過期時間,或者在它的 Continuation 上調用
resume()
方法。當任何一個條件觸發時,請求會重新送出給 servlet(通過 filter 鍊)。這樣,整個請求被"重播"直到 RetryRequest 異常不再抛出,然後繼續按正常情況執行。
Jetty 的安全性
為了防止任何人都有權限去關閉一個已經開啟的 Jetty 伺服器, 我們可以通過在啟動 Jetty 伺服器的時候指定參數來進行控制,使得使用者必須提供密碼才能關閉 Jetty 伺服器,啟動 Jetty 伺服器的指令如下所示:
java -DSTOP.PORT=8079 -DSTOP.KEY=mypassword -jar start.jar
這樣,使用者在停止 Jetty 伺服器的時候,就必須提供密碼“mypassword”。
總結
Jetty 是一個非常友善使用的 Web 伺服器,它的特點在于非常小,很容易嵌入到我們的應用程式當中,而且針對 Web 2.0 的 Ajax 技術進行了特别的優化,這也使得我們的使用 Ajax 的應用程式可以擁有更好的性能。