Jetty
簡介: Jetty 是一個用 Java 實現(xiàn)、開源、基于標準的,并且具有豐富功能的 Http 服務器和 Web 容器,可以mf的用于商業(yè)行為。Jetty 這個項目成立于 1995 年,現(xiàn)在已經(jīng)有非常多的成功產(chǎn)品基于 Jetty,比如 Apache Geromino, JBoss, IBM Tivoli, Cisco SESM 等。Jetty 可以用來作為一個傳統(tǒng)的 Web 服務器,也可以作為一個動態(tài)的內(nèi)容服務器,并且 Jetty 可以非常容易的嵌入到 Java 應用程序當中。
特性簡介
易用性
易用性是 Jetty 設(shè)計的基本原則,易用性主要體現(xiàn)在以下幾個方面:
通過 XML 或者 API 來對 Jetty 進行配置;
默認配置可以滿足大部分的需求;
將 Jetty 嵌入到應用程序當中只需要非常少的代碼;
可擴展性
在使用了 Ajax 的 Web 2.0 的應用程序中,每個連接需要保持更長的時間,這樣線程和內(nèi)存的消耗量會急劇的增加。這就使得我們擔心整個程序會因為單個組件陷入瓶頸而影響整個程序的性能。但是有了 Jetty:
即使在有大量服務請求的情況下,系統(tǒng)的性能也能保持在一個可以接受的狀態(tài)。
利用 Continuation 機制來處理大量的用戶請求以及時間比較長的連接。
另外 Jetty 設(shè)計了非常良好的接口,因此在 Jetty 的某種實現(xiàn)無法滿足用戶的需要時,用戶可以非常方便地對 Jetty 的某些實現(xiàn)進行修改,使得 Jetty 適用于特殊的應用程序的需求。
易嵌入性
Jetty 設(shè)計之初就是作為一個優(yōu)秀的組件來設(shè)計的,這也就意味著 Jetty 可以非常容易的嵌入到應用程序當中而不需要程序為了使用 Jetty 做修改。從某種程度上,你也可以把 Jetty 理解為一個嵌入式的Web服務器。
部署應用程序
將自己的應用程序部署到 Jetty 上面是非常簡單的,首先將開發(fā)好的應用程序打成 WAR 包放到 Jetty 的 Webapps 目錄下面。然后用如下的命令來啟動 Jetty 服務器:Java –jar start.jar, 在啟動服務器后。我們就可以訪問我們的應用程序了,Jetty 的默認端口是 8080,WAR 的名字也就是我們的應用程序的 Root Context。
如何將 Jetty 嵌入到程序當中
將 Jetty 嵌入到程序當中是非常簡單的, 如 代碼 1 所示:首先我們創(chuàng)建一個 Server 對象, 并設(shè)置端口為 8080,然后為這個 Server 對象添加一個默認的 Handler。接著我們用配置文件 jetty.xml 對這個 server 進行設(shè)置,后我們使用方法 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 做為運行參數(shù),這個參數(shù)也可以是其它的配置文件,可以是多個 XML 配置文件,其實這個配置文件好比我們使用 Struts 時的 struts-config.xml 文件,將運行 Server 需要用到的組件寫在里面,比如上一節(jié)中 HttpServer 的配置需要的組件類都可以寫在這個配置文件中。按上述方法啟動 Jetty Server 時,就會調(diào)用 Server 類里面的 main 方法,這個入口方法首先會構(gòu)造一個 Server 類實例(其實也就構(gòu)造了一個 HttpServer),創(chuàng)建實例的過程中就會構(gòu)造 XmlConfiguration 類的對象來讀取參數(shù)配置文件,之后再由這個配置文件產(chǎn)生的 XmlConfiguration 對象來配置這個 Server,配置過程其實是運用了 Java 的反射機制,調(diào)用 Server 的方法并傳入配置文件中所寫的參數(shù)來向這個 Server 添加 HttpListener,HttpContext,HttpHandler,以及 Web Application(對應于我們的 Web 應用)。
Jetty 的 Continuation 機制
討論 Jetty 的 Continuation 機制,首先需要提到 Ajax 技術(shù),Ajax 技術(shù)是當前開發(fā) Web 應用的非常熱門的技術(shù),也是 Web 2.0 的一個重要的組成部分。Ajax 技術(shù)中的一個核心對象是 XMLHttpRequest 對象,這個對象支持異步請求,所謂異步請求即是指當客戶端發(fā)送一個請求到服務器的時候,客戶端不必一直等待服務器的響應。這樣就不會造成整個頁面的刷新,給用戶帶來更好的體驗。而當服務器端響應返回時,客戶端利用一個 Javascript 函數(shù)對返回值進行處理,以更新頁面上的部分元素的值。但很多時候這種異步事件只是在很小一部分的情況下才會發(fā)生,那么怎么保證一旦服務器端有了響應之后客戶端馬上就知道呢,我們有兩種方法來解決這個問題,一是讓瀏覽器每隔幾秒請求服務器來獲得更改,我們稱之為輪詢。二是服務器維持與瀏覽器的長時間的連接來傳遞數(shù)據(jù),長連接的技術(shù)稱之為 Comet。
大家很容易就能發(fā)現(xiàn)輪詢方式的主要缺點是產(chǎn)生了大量的傳輸浪費。因為可能大部分向服務器的請求是無效的,也就是說客戶端等待發(fā)生的事件沒有發(fā)生,如果有大量的客戶端的話,那么這種網(wǎng)絡(luò)傳輸?shù)睦速M是非常厲害的。特別是對于服務器端很久才更新的應用程序來講,比如郵件程序,這種浪費就更是巨大了。并且對 Server 端處理請求的能力也相應提高了要求。如果很長時間才向 Server 端發(fā)送一次請求的話,那么客戶端就不能的得到及時的響應。
如果使用 Comet 技術(shù)的話,客戶端和服務器端必須保持一個長連接,一般情況下,服務器端每一個 Servlet 都會獨占一個線程,這樣就會使得服務器端有很多線程同時存在,這在客戶端非常多的情況下也會對服務器端的處理能力帶來很大的挑戰(zhàn)。
Jetty 利用 Java 語言的非堵塞 I/O 技術(shù)來處理并發(fā)的大量連接。 Jetty 有一個處理長連接的機制:一個被稱為 Continuations 的特性。利用 Continuation 機制,Jetty 可以使得一個線程能夠用來同時處理多個從客戶端發(fā)送過來的異步請求,下面我們通過一個簡化的聊天程序的服務器端的代碼來演示不使用 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 函數(shù)喚醒位置,這里需要解釋的是,在調(diào)用完 suspend 函數(shù)之后,這個線程就可處理其他的請求了,這也就大大提高了程序的并發(fā)性,使得長連接能夠獲得非常好的擴展性。
如果我們不使用 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);
}
}
大家注意到在等待事件發(fā)生的時間里,線程被掛起,直到所等待的事件發(fā)生為止,但在等待過程中,這個線程不能處理其他請求,這也就造成了在客戶端非常多的情況下服務器的處理能力跟不上的情況。下面我們解釋一下 Jetty 的 Continuation 的機制是如何工作的。
為了使用 Continuatins,Jetty 必須配置為使用它的 SelectChannelConnector 處理請求。這個 connector 構(gòu)建在 java.nio API 之上,允許它維持每個連接開放而不用消耗一個線程。當使用 SelectChannelConnector 時,ContinuationSupport.getContinuation() 提供一個 SelectChannelConnector.RetryContinuation 實例(但是,您必須針對 Continuation 接口編程)。當在 RetryContinuation 上調(diào)用 suspend() 時,它拋出一個特殊的運行時異常 -- RetryRequest,該異常傳播到 servlet 外并且回溯到 filter 鏈,后被 SelectChannelConnector 捕獲。但是不會發(fā)送一個異常響應給客戶端,而是將請求維持在未決 Continuations 隊列里,則 HTTP 連接保持開放。這樣,用來服務請求的線程返回給 ThreadPool,然后又可以用來服務其他請求。暫停的請求停留在未決 Continuations 隊列里直到指定的過期時間,或者在它的 Continuation 上調(diào)用 resume() 方法。當任何一個條件觸發(fā)時,請求會重新提交給 servlet(通過 filter 鏈)。這樣,整個請求被"重播"直到 RetryRequest 異常不再拋出,然后繼續(xù)按正常情況執(zhí)行。
Jetty 的安全性
為了防止任何人都有權(quán)限去關(guān)閉一個已經(jīng)開啟的 Jetty 服務器, 我們可以通過在啟動 Jetty 服務器的時候指定參數(shù)來進行控制,使得用戶必須提供密碼才能關(guān)閉 Jetty 服務器,啟動 Jetty 服務器的命令如下所示:
java -DSTOP.PORT=8079 -DSTOP.KEY=mypassword -jar start.jar
這樣,用戶在停止 Jetty 服務器的時候,就必須提供密碼“mypassword”。
總結(jié)
Jetty 是一個非常方便使用的 Web 服務器,它的特點在于非常小,很容易嵌入到我們的應用程序當中,而且針對 Web 2.0 的 Ajax 技術(shù)進行了特別的優(yōu)化,這也使得我們的使用 Ajax 的應用程序可以擁有更好的性能。