TOMCAT .

TOMCAT源碼分析(消息處理)

2004-05-21 13:292291人閱讀評論(3)收藏舉報
0:前言  

我們知道了tomcat的整體框架了, 也明白了裏面都有些什麼組件, 以及各個組件是幹什麼用的了。

http://www.csdn.net/Develop/read_article.asp?id=27225

我想,接下來我們應該去了解一下 tomcat 是如何處理jsp和servlet請求的。

1.  我們以一個具體的例子,來跟蹤TOMCAT, 看看它是如何把Request一層一層地遞交給下一個容器, 並最後交給Wrapper來處理的。

http://localhost:8080/web/login.jsp爲例子

(以下例子, 都是以tomcat4 源碼爲參考)

這篇心得主要分爲3個部分: 前期, 中期, 和末期。

前期:講解了在瀏覽器裏面輸入一個URL,是怎麼被tomcat抓住的。

中期:講解了被tomcat抓住後,又是怎麼在各個容器裏面穿梭, 最後到達最後的處理地點。

末期:講解到達最後的處理地點後,又是怎麼具體處理的。

2、  前期 Request的born.

    在這裏我先簡單講一下request這個東西。

     我們先看着這個URL:http://localhost:8080/web/login.jsp  它是動用了8080端口來進行socket通訊的。

     我們知道, 通過 

       InputStream in = socket.getInputStream() 和

       OutputStream out = socket.getOutputStream() 

     就可以實現消息的來來往往了。

     但是如果把Stream給應用層看,顯然操作起來不方便。 

     所以,在tomcat 的Connector裏面, socket被封裝成了Request和Response這兩個對象。

     我們可以簡單地把Request看成管發到服務器來的數據,把Response看成想發出服務器的數據。

     但是這樣又有其他問題了啊? Request這個對象是把socket封裝起來了, 但是他提供的又東西太多了。

     諸如Request.getAuthorization(), Request.getSocket()。  像Authorization這種東西開發人員拿來基本上用不太着,而像socket這種東西,暴露給開發人員又有潛在的危險。 而且啊, 在Servlet Specification裏面標準的通信類是ServletRequest和HttpServletRequest,而非這個Request類。 So, So, So. Tomcat必須得搗持搗持Request才行。 最後tomcat選擇了使用搗持模式(應該叫適配器模式)來解決這個問題。它把org.apache.catalina.Request 搗持成了 org.apache.coyote.tomcat4.CoyoteRequest。 而CoyoteRequest又實現了ServletRequest和HttpServletRequest 這兩種接口。 這樣就提供給開發人員需要且剛剛需要的方法了。

    ok, 讓我們在 tomcat的頂層容器 - StandardEngin 的invoke()方法這裏設置一個斷點, 然後訪問

http://localhost:8080/web/login.jsp , 我們來看看在前期都會路過哪些地方:

       1. run(): 536, java.lang.Thread, Thread.java

       CurrentThread

      2.  run():666, org.apache.tomcat.util.threads.ThreadPool$ControlRunnable, ThreadPool.java

               ThreadPool

       3.  runIt():589, org.apache.tomcat.util.net.TcpWorkerThread, PoolTcpEndpoint.java

          ThreadWorker

4.        processConnection():  549

org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler, Http11Protocol.java

                  http protocol parser

      5.  Process(): 781, org.apache.coyote.http11.Http11Processor, Http11Processor.java

          http request processor

       6. service(): 193, org.apache.coyote.tomcat4.CoyoteAdapter,CoyoteAdapter.java

          adapter

       7. invoke(): 995, org.apache.catalina.core.ContainerBase, ContainerBase.java

   StandardEngin

    1. 主線程

    2. 啓動線程池.

    3. 調出線程池裏面空閒的工作線程。

    4. 把8080端口傳過來由httpd協議封裝的數據,解析成Request和Response對象。

    5. 使用Http11Processor來處理request

    6. 在Http11Processor裏面, 又會call CoyoteAdapter來進行適配處理,把Request適配成實現了ServletRequest和HttpServletRequest接口的CoyoteRequest.

7. 到了這裏,前期的去毛拔皮工作就基本上搞定,可以交給StandardEngin 做核心的處理工作了。

3. 中期。 在各個容器間的穿梭。

    Request在各個容器裏面的穿梭大致是這樣一種方式:

    每個容器裏面都有一個管道(pipline), 專門用來傳送Request用的。

    管道里面又有好幾個閥門(valve), 專門用來過濾Request用的。

    在管道的低部通常都會放上一個默認的閥們。 這個閥們至少會做一件事情,就是把Request交給子容器。

    讓我們來想象一下:

     當一個Request進入一個容器後, 它就在管道里面流動,波羅~ 波羅~ 波羅~ 地穿過各個閥門。在流到最後一個閥門的時候,吧唧~ 那個該死的閥門就把它扔給了子容器。 然後又開始 波羅~ 波羅~ 波羅~ ... 吧唧~....  波羅~  波羅~ 波羅~ ....吧唧~....

    就是通過這種方式, Request 走完了所有的容器。( 感覺有點像消化系統,最後一個地方有點像那裏~  )

    OK, 讓我們具體看看都有些什麼容器, 各個容器裏面又都有些什麼閥門,這些閥們都對我們的Request做了些什麼吧:

3.1 StandardEngin 的pipeline裏面放的是:StandardEnginValve

在這裏,VALVE做了三件事:

1.   驗證傳遞過來的request是不是httpservletRequest.

2    驗證傳遞過來的 request 是否攜帶了host header信息.

3    選擇相應的host去處理它。(一般我們都只有一個host:localhost,也就是127.0.0.1)。

到了這個地方, 我們的request就已經完成了在Engin這個部分的歷史使命, 通向前途未卜的下一站: host了。

3.2 StandardHost 的pipline裏面放的是: StandardHostValve

1.   驗證傳遞過來的request是不是httpservletRequest.

2.   根據Request來確定哪個Context來處理。

Context其實就是webapp, 比如http://localhost:8080/web/login.jsp

這裏web就是Context羅!

3.   既然確定了是哪個Context了,那麼就應該把那個Context的classloader付給當前線程了。 

        Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());

   這樣request就只看得見指定的context下面的classes啊, jar啊這些, 而看不見tomcat本身的類, 什麼Engin啊, Valve啊。 不然還得了啊!

4. 既然request到了這裏了,看來用戶是準備訪問web這個web app了,咋們得更新一下這個用戶的session不是! Ok , 就由manager更新一下用戶的session信息

5. 交給具體的Context 容器去繼續處理Request.

6. Context處理完畢了,把classloader還回來。

3.3 StandardContext 的pipline裏面放的是: StandardContextValve

1.   驗證傳遞過來的request是不是httpservletRequest.

2.   如果request意圖不軌,想要訪問/meta-inf, /web-inf這些目錄下的東西,呵呵,沒有用D!

3.   這個時候就會根據Request到底是Servlet,還是jsp,還是靜態資源來決定到底用哪種Wrapper來處理這個Reqeust了。

4.   一旦決定了到底用哪種Wrapper,OK,交給那個Wrapper處理。

4. 末期。 不同的需求是怎麼處理的.

StandardWrapper

之前對Wrapper沒有做過講解,其實它是這樣一種東西。

我們在處理Request的時候,可以分成3種。

處理靜態的: org.apache.catalina.servlets.DefaultServlet   

處理jsp的: org.apache.jasper.servlet.JspServlet 

處理servlet的: org.apache.catalina.servlets.InvokerServlet

不同的request就用這3種不同的servlet去處理。

Wrapper就是對它們的一種簡單的封裝,有了Wrapper後,我們就可以輕鬆地攔截每次的Request。也可以容易地調用servlet的init()和destroy()方法, 便於管理嘛!

具體情況是這麼滴:

   如果request是找jsp文件,StandardWrapper裏面就會封裝一個org.apache.jasper.servlet.JspServlet去處理它。

   如果request是找 靜態資源 ,StandardWrapper裏面就會封裝一個org.apache.jasper.servlet.DefaultServlet  去處理它。

   如果request是找servlet ,StandardWrapper裏面就會封裝一個org.apache.jasper.servlet.InvokerServlet 去處理它。

StandardWrapper同樣也是容器,既然是容器, 那麼裏面一定留了一個管道給request去穿,管道低部肯定也有一個閥門(注1),用來做最後一道攔截工作.

在這最底部的閥門裏,其實就主要做了兩件事:

   一是啓動過濾器,讓request在N個過濾器裏面篩一通,如果OK! 那就PASS。 否則就跳到其他地方去了。

   二是servlet.service((HttpServletRequest) request,(HttpServletResponse) response); 這個方法.

     如果是 JspServlet, 那麼先把jsp文件編譯成servlet_xxx, 再invoke servlet_xxx的servie()方法。

     如果是 DefaultServlet, 就直接找到靜態資源,取出內容, 發送出去。

     如果是 InvokerServlet, 就調用那個具體的servlet的service()方法。

   ok! 完畢。

注1: StandardWrapper 裏面的閥門是最後一道關口了。 如果這個閥門欲意把request交給StandardWrapper 的子容器處理。 對不起, 在設計考慮的時候, Wrapper就被考慮成最末的一個容器, 壓根兒就不會給Wrapper添加子容器的機會! 如果硬是要調用addChild(), 立馬拋出IllegalArgumentException!

參考:

<http://jakarta.apache.org/tomcat/>
   <
http://www.onjava.com/pub/a/onjava/2003/05/14/java_webserver.html>

TOMCAT源碼分析(啓動框架)

自己最近在學習Java Web編程,先是讀了Sun公司的官方文檔Java EE 5的tutorial(http://java.sun.com/javaee/5/docs/tutorial/doc/)中的關鍵章節,然後學了一陣Java Passion裏的相關在線課程(見:http://www.javapassion.com/j2ee/),前幾天還看完了一本電子書<<Struts2 in Action>>,但總覺得缺少了什麼。這兩天突然想起爲什麼不把tomcat的源碼下載下來仔細研究研究呢?記得以前自己一直想學習著名的Java Build工具Ant(http://ant.apache.org/)的用法,但怎麼看文檔都理解不深,試了幾次效果都不好。最後終於把源碼下載下來讀了其中的主要源碼,頓時感覺不僅輕鬆學會了Ant的用法,而且知道爲什麼要那麼設計。這給我很大的啓發:對於開源軟件來說,比書籍更好的是在線文檔,比在線文檔更好的源碼本身。其實tomcat和Ant有很深的內在聯繫,tomcat的早期開發者James Duncan Davidson就是Ant工具的發明人,爲了解決開發tomcat過程中各種複雜的build流程他專門創造了Ant。顯然,瞭解Ant對讀懂tomcat會有極大的幫助。tomcat現在的架構師Craig McClanahan又是另一個著名的MVC框架struts的發明人。

步入正題吧。

首先到tomcat.apache.org裏下載最新的源碼包,現在最新的版本是6.0.20,下載後解壓縮,發現它的根目錄裏有一個build.xml,這是Ant的默認輸入文件。tomcat源碼本身是用Eclipse寫的,所以我們要做的第一步是在Eclipse裏把tomcat工程部署好,這其實很容易,在Eclipse裏選擇File>New>Project,選擇Java project with an existing Ant buildfile,選擇那個build.xml就可以了。
然後是下載tomcat的依賴包,這一步也很容易,只要執行ant download(可以打開Eclipse的Ant的view以方便操作)就可以了。它會把下載的依賴包放在一個名稱爲base.path的目錄,最好自己配置一下,它默認配置在build.xml.properties.default文件裏。下載完依賴包後再ant deploy後就在output/build目錄裏得到了可以使用的tomcat。Ant真方便!
Eclipse裏要想使得即時編譯沒有任何錯誤提示,需要在build path裏配置兩個變量:一個是ANT_HOME,另一個是保存依賴包的TOMCAT_LIBS_BASE,就是保存依賴包的那個目錄。
看一下tomcat的目錄結構,真的很簡單,啓動tomcat的腳本放在bin目錄裏,分別有對應windows和unix的兩種腳本。我在Ubuntu環境下,所以先來看一下它的unix腳本。啓動腳本是startup.sh,它實際上是另一個腳本catalina.sh的封裝,所以直接跳到catalina.sh,它會調用另一個腳本setclasspath.sh以設置java,catalina.sh設置好各種環境後會調用tomcat的啓動類:org.apache.catalina.startup.Bootstrap的start方法。
跟蹤一下代碼,Bootstrap類裏主要是設置幾個不同的ClassLoader和JMX Server,然後通過反射技術把任務移交給真正的啓動類:org.apache.catalina.startup.Catalina。
怎麼樣,這是不是很像啓動腳本一樣,先有個包裝,最後都調的是catalina,不管是shell還是Java的類。
Catalina裏的方法最主要是這個方法:protected Digester createStartDigester();它是讀取conf/server.xml配置文件進行動態加載配置組件的過程,用的是apache的另一個子項目:Digester,它也是struts讀取配置文件動態加載的機制,別忘了tomcat的架構師可是struts項目的作者啊。
加載完server的層次化組件後,通過調用 ((Lifecycle) server).start();整個server就自頂向下啓動起來了。另外,代碼中大量運用了observer這個設計模式,把各種狀態的變化通知給相應的listener。
在Bootstrap類的源碼中發現一處小的bug:

if (replace && log.isDebugEnabled())
log.debug("Expanded " + before + " to " + replace);

應該是:
if (replace && log.isDebugEnabled())
log.debug("Expanded " + before + " to " + repository); 

 

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