我們知道了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()
?????就可以實現(xiàn)消息的來來往往了。
????
但是如果把Stream給應用層看,顯然操作起來不方便。
????
所以,在tomcat 的Connector里面, socket被封裝成了Request和Response這兩個對象。
????
我們可以簡單地把Request看成管發(fā)到服務器來的數(shù)據(jù),把Response看成想發(fā)出服務器的數(shù)據(jù)。
???
?
????
但是這樣又有其他問題了??? Request這個對象是把socket封裝起來了, 但是他提供的又東西太多了。
????
諸如Request.getAuthorization(), Request.getSocket()。
?
像 Authorization這種東西開發(fā)人員拿來基本上用不太著,而像socket這種東西,暴露給開發(fā)人員又有潛在的危險。 而且啊, 在Servlet Specification里面標準的通信類是ServletRequest和HttpServletRequest,而非這個Request類。 So, So, So. Tomcat必須得搗持搗持Request才行。 最后tomcat選擇了使用搗持模式(應該叫適配器模式)來解決這個問題。它把org.apache.catalina.Request 搗持成了 org.apache.coyote.tomcat4.CoyoteRequest。 而CoyoteRequest又實現(xiàn)了ServletRequest和HttpServletRequest 這兩種接口。 這樣就提供給開發(fā)人員需要且剛剛需要的方法了。
?
???
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協(xié)議封裝的數(shù)據(jù),解析成Request和Response對象。
???
5. 使用
Http11Processor
來處理request
???
6. 在
Http11Processor
里面, 又會call
CoyoteAdapter
來進行適配處理,把Request適配成實現(xiàn)了ServletRequest和HttpServletRequest接口的
CoyoteRequest.
7
. 到了這里,前期的去毛拔皮工作就基本上搞定,可以交給
StandardEngin
做核心的處理工作了。
?
3. 中期。 在各個容器間的穿梭。
???
Request在各個容器里面的穿梭大致是這樣一種方式:
???
每個容器里面都有一個管道(pipline), 專門用來傳送Request用的。
???
管道里面又有好幾個閥門(valve), 專門用來過濾Request用的。
???
在管道的低部通常都會放上一個默認的閥們。 這個閥們至少會做一件事情,就是把Request交給子容器。
???
讓我們來想象一下:
????
當一個Request進入一個容器后, 它就在管道里面流動,波羅~ 波羅~ 波羅~ 地穿過各個閥門。在流到最后一個閥門的時候,吧唧~ 那個該死的閥門就把它扔給了子容器。 然后又開始 波羅~ 波羅~ 波羅~ ... 吧唧~....
?
波羅~
?
波羅~ 波羅~ ....吧唧~....
???
就是通過這種方式, Request 走完了所有的容器。( 感覺有點像消化系統(tǒng),最后一個地方有點像那里~
?
)
???
OK, 讓我們具體看看都有些什么容器, 各個容器里面又都有些什么閥門,這些閥們都對我們的Request做了些什么吧:
?
3.1 StandardEngin
的pipeline里面放的是:
StandardEnginValve
在這里,
VALVE
做了三件事:
1.
??
驗證傳遞過來的
request
是不是
httpservletRequest.
2
???
驗證傳遞過來的
request
是否攜帶了
host header
信息
.
3
???
選擇相應的
host
去處理它。(一般我們都只有一個host:
localhost
,也就是
127.0.0.1
)。
到了這個地方,
我們的
request
就已經(jīng)完成了在
Engin
這個部分的歷史使命,
通向前途未卜的下一站:
host
了。
?
3.2 StandardHost
的pipline里面放的是:
StandardHostValve
1.
??
驗證傳遞過來的
request
是不是
httpservletRequest.
2.
??
根據(jù)
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 容器去繼續(xù)處理Request.
6. Context處理完畢了,把
classloader
還回來。
?
3.3 StandardContext
的pipline里面放的是:
StandardContextValve
1.
??
驗證傳遞過來的
request
是不是
httpservletRequest.
2.
??
如果
request
意圖不軌,想要訪問
/meta-inf, /web-inf
這些目錄下的東西,呵呵,沒有用D!
3.
??
這個時候就會根據(jù)
Request
到底是
Servlet
,
還是
jsp
,
還是靜態(tài)資源來決定到底用哪種
Wrapper
來處理這個
Reqeust
了。
4.
??
一旦決定了到底用哪種
Wrapper
,
OK
,交給那個
Wrapper
處理。
?
4. 末期。 不同的需求是怎么處理的.
StandardWrapper
之前對Wrapper沒有做過講解,其實它是這樣一種東西。
我們在處理Request的時候,可以分成3種。
處理靜態(tài)的:
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是找 靜態(tài)資源 ,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, 就直接找到靜態(tài)資源,取出內容, 發(fā)送出去。
????
如果是 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
>
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
