實戰Jetty

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 的應用程序可以擁有更好的性能。

轉自:http://www.ibm.com/developerworks/cn/web/wa-lo-jetty/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章