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