花了一個禮拜的時間閱讀了 how tomcat works
,本文基於此書,整理了一下 Tomcat 5
的基本架構,其實也沒什麼多複雜的東西,無非是解析 Http
請求,然後呼叫相應的 Servlet
。另推薦看 CSAPP
的網路程式設計那一章
本文不以別人能看懂為目的,僅作自己備忘筆記只用。
基本架構
Tomcat
由兩個模組協同合作
connector
container
connector
負責解析處理 HTTP
請求,比如說 請求頭
, 查詢字串
, 請求引數
之類的。生成 HttpRequest
和HttpResponse
之後交給 container
,由它負責呼叫相應的 Servlet
。
Connector
Tomcat
預設的 Connector
為 HttpConnector
。作為 Connector
必須要實現 Connector
這個介面。
Tomcat
啟動以後會開啟一個執行緒,做一個死迴圈,通過 ServerSocket
來等待請求。一旦得到請求,生成 Socket
,注意這裡 HttpConnector
並不會自己處理 Socket
,而是把它交給 HttpProcessor
。詳細看下面程式碼,這裡我只保留了關鍵程式碼。
public void run() {
// Loop until we receive a shutdown command
while (!stopped) {
Socket socket = null;
try {
socket = serverSocket.accept(); //等待連結
} catch (AccessControlException ace) {
log("socket accept security exception", ace);
continue;
}
// Hand this socket off to an appropriate processor
HttpProcessor processor = createProcessor();
processor.assign(socket); //這裡是立刻返回的
// The processor will recycle itself when it finishes
}
}複製程式碼
注意一點,上面的 processor.assign(socket);
是立刻返回的,並不會阻塞在那裡等待。因為Tomcat不可能一次只能處理一個請求,所以是非同步的,每個 processor
處理都是一個單獨的執行緒。
HttpProcessor
上面的程式碼並沒有顯示呼叫 HttpProcessor
的 process
方法,那這個方法是怎麼呼叫的呢?我們來看一下 HttpProcessor
的 run
方法。
public void run() {
// Process requests until we receive a shutdown signal
while (!stopped) {
// Wait for the next socket to be assigned
Socket socket = await();
if (socket == null)
continue;
// Process the request from this socket
try {
process(socket);
} catch (Throwable t) {
log("process.invoke", t);
}
// Finish up this request
connector.recycle(this);
}
}複製程式碼
我們發現他是呼叫 await
方法來阻塞等待獲得 socket
方法。而之前 Connector
是呼叫 assign
分配的,這是什麼原因?
下面仔細看 await
和 assign
方法。這兩個方法協同合作,當 assign
獲取 socket
時會通知 await
然後返回 socket
。
synchronized void assign(Socket socket) {
// Wait for the Processor to get the previous Socket
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
notifyAll();
}
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
Socket socket = this.socket;
available = false;
notifyAll();
return (socket);
}複製程式碼
預設 available
為 false
。
接下來就是剩下的事情就是解析請求,填充 HttpRequest
和 HttpResponse
物件,然後交給 container
負責。
這裡我不過多贅述如何解析
private void process(Socket socket) {
//parse
....
connector.getContainer().invoke(request, response);
....
}複製程式碼
Container
A Container is an object that can execute requests received from a client, and return responses based on those requests
Container
是一個介面,實現了這個介面的類的例項,可以處理接收的請求,呼叫對應的 Servlet
。
總共有四類 Container
,這四個 Container
之間並不是平行關係,而是父子關係
Engine
– 最頂層的容器,可以包含多個Host
Host
– 代表一個虛擬主機,可以包含多個Context
Context
– 代表一個web應用
,也就是ServletContext
,可以包含多個Wrappers
Wrapper
– 代表一個Servlet
,不能包含別的容器了,這是最底層
Container的呼叫
容器好比是一個加工廠,加工接受的 request
,加工方式和流水線也很像,但又有點區別。這裡會用到一個叫做 Pipeline
的 東西,中文翻譯為 管道
, request
就放在管道里順序加工,進行加工的工具叫做 Valve
,好比手術刀, Pipeline
可新增多個 Valve
,最後加工的工具稱為 BaseValve
上面可能講的比較抽象,接下來我們來看程式碼。 Engine
是頂層容器,所以上面 invoke
,執行的就是 Engine
的方法。StandardEngine
是 Engine
的預設實現,注意它也同時實現了 Pipeline
介面,且包含了 Pipeline
。
它的構造方法同時指定了 baseValve
,也就是管道最後一個呼叫的 Valve
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
}複製程式碼
好,接著我們看 invoke
,這個方法是繼承自 ContainerBase
。只有一行,之間交給 pipeline
,進行加工。
public void invoke(Request request, Response response)
throws IOException, ServletException {
pipeline.invoke(request, response);
}複製程式碼
下面是 StandardPipeline
的 invoke
實現,也就是預設的 pipeline
實現。
public void invoke(Request request, Response response)
throws IOException, ServletException {
// Invoke the first Valve in this pipeline for this request
(new StandardPipelineValveContext()).invokeNext(request, response);
}複製程式碼
也只有一行!呼叫 StandardPipelineValveContext
的 invokeNext
方法,這是一個 pipeline
的內部類。讓我們來看
具體程式碼
public void invokeNext(Request request, Response response)
throws IOException, ServletException {
int subscript = stage;
stage = stage + 1;
// Invoke the requested Valve for the current request thread
if (subscript < valves.length) {
valves[subscript].invoke(request, response, this); //加工
} else if ((subscript == valves.length) && (basic != null)) {
basic.invoke(request, response, this);
} else {
throw new ServletException
(sm.getString("standardPipeline.noValve"));
}
}複製程式碼
它呼叫了 pipeline
所用的 Valve
來對 request
做加工,當Valve執行完,會呼叫 BaseValve
,也就是上面的StandardEngineValve
,
我們再來看看它的 invoke
方法
// Select the Host to be used for this Request
StandardEngine engine = (StandardEngine) getContainer();
Host host = (Host) engine.map(request, true);
if (host == null) {
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_BAD_REQUEST,
sm.getString("standardEngine.noHost",
request.getRequest().getServerName()));
return;
}
// Ask this Host to process this request
host.invoke(request, response);複製程式碼
它通過 (Host) engine.map(request, true);
獲取所對應的 Host
,然後進入到下一層容器中繼續執行。後面的執行順序
和 Engine
相同,我不過多贅述
執行順序小結
經過一長串的 invoke
終於講完了第一層容器的執行順序。估計你們看的有點暈,我這裡小結一下。
Connector -> HttpProcessor.process() -> StandardEngine.invoke() -> StandardPipeline.invoke() ->
StandardPipelineValveContext.invokeNext() -> valves.invoke() -> StandardEngineValve.invoke() ->
StandardHost.invoke()
到這裡位置 Engine
這一層結束。接下來進行 Host
,步驟完全一致
StandardHost.invoke() -> StandardPipeline.invoke() ->
StandardPipelineValveContext.invokeNext() -> valves.invoke() -> StandardHostValve.invoke() ->
StandardContext.invoke()
然後再進行 Context
這一層的處理,到最後選擇對應的 Wrapping
執行。
Wrapper
Wrapper
相當於一個 Servlet
例項, StandardContext
會更根據的 request
來選擇對應的 Wrapper
呼叫。我們直接來看看
Wrapper
的 basevalve
是如果呼叫 Servlet
的 service
方法的。下面是 StandardWrapperValve
的 invoke
方法,我省略了很多,
只看關鍵。
public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException {
// Allocate a servlet instance to process this request
if (!unavailable) {
servlet = wrapper.allocate();
}
// Create the filter chain for this request
ApplicationFilterChain filterChain =
createFilterChain(request, servlet);
// Call the filter chain for this request
// NOTE: This also calls the servlet's service() method
String jspFile = wrapper.getJspFile(); //是否是jsp
if (jspFile != null)
sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
else
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
if ((servlet != null) && (filterChain != null)) {
filterChain.doFilter(sreq, sres);
}
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
}複製程式碼
首先呼叫 wrapper.allocate()
,這個方法很關鍵,它會通過 反射
找到對應 servlet
的 class
檔案,構造出例項返回給我們。然後建立一個 FilterChain
,熟悉 j2ee
的各位應該對這個不陌生把?這就是我們在開發 web app
時使用的 filter
。然後就執行 doFilter
方法了,它又會呼叫 internalDoFilter
,我們來看這個方法
private void internalDoFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (this.iterator.hasNext()) {
ApplicationFilterConfig filterConfig =
(ApplicationFilterConfig) iterator.next();
Filter filter = null;
filter = filterConfig.getFilter();
filter.doFilter(request, response, this);
return;
}
// We fell off the end of the chain -- call the servlet instance
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse)) {
servlet.service((HttpServletRequest) request,
(HttpServletResponse) response);
} else {
servlet.service(request, response);
}
}複製程式碼
終於,在這個方法裡看到了 service
方法,現在你知道在使用 filter
的時候如果不執行 doFilter
, service
就不會執行的原因了把。
小結
Tomcat
的重要過程應該都在這裡了,還值得一提的是 LifeCycle
介面,這裡所有類幾乎都實現了 LifeCycle
, Tomcat
通過它來統一管理容器的生命流程,大量運用觀察者模式。有興趣的同學可以自己看書
Referance
How Tomcat works
原文地址:www.liuhaihua.cn/archives/37…