聲明:源碼版本為Tomcat 6.0.35
前面的文章中介紹了Tomcat初始化的過程,本文将會介紹Tomcat對HTTP請求的處理的整體流程,更細節的。
在上一篇文章中,介紹到JIoEndpoint 中的内部類Acceptor用來接受Socket請求,并調用processSocket方法來進行請求的處理,是以會從本文這個方法開始進行講解。
protected boolean processSocket(Socket socket) {
try {
if (executor == null) {
getWorkerThread().assign(socket);
} else {
executor.execute(new SocketProcessor(socket));
}
} catch (Throwable t) {
//……此處略去若幹代碼
}
return true;
}
在以上的代碼中,首先會判斷是否在server.xml配置了程序池,如果配置了的話,将會使用該線程池進行請求的處理,如果沒有配置的話将會使用JIoEndpoint中自己實作的線程池WorkerStack來進行請求的處理,我們将會介紹WorkerStack的請求處理方式。
protected Worker getWorkerThread() {
// Allocate a new worker thread
synchronized (workers) {
Worker workerThread;
while ((workerThread = createWorkerThread()) == null) {
try {
workers.wait();
} catch (InterruptedException e) {
// Ignore
}
}
return workerThread;
}
}
在以上的代碼中,最終傳回了一個Worker的執行個體,有其來進行請求的處理,在這裡,我們再看一下createWorkerThread方法,該方法會生成或者線上程池中取到一個線程。
protected Worker createWorkerThread() {
synchronized (workers) {
if (workers.size() > 0) {
//如果線程池中有空閑的線程,取一個
curThreadsBusy++;
return workers.pop();
}
if ((maxThreads > 0) && (curThreads < maxThreads)) {
//如果還沒有超過最大線程數,會建立一個線程
curThreadsBusy++;
return (newWorkerThread());
} else {
if (maxThreads < 0) {
curThreadsBusy++;
return (newWorkerThread());
} else {
return (null);
}
}
}
}
到此,線程已經擷取了,接下來,最關鍵的是調用線程實作Worker的run方法:
public void run() {
// Process requests until we receive a shutdown signal
while (running) {
// Wait for the next socket to be assigned
Socket socket = await();
if (socket == null)
continue;
if (!setSocketOptions(socket) || !handler.process(socket)) {
try {
socket.close();
} catch (IOException e) {
}
}
socket = null;
recycleWorkerThread(this);
}
}
這裡跟請求處理密切相關的是handler.process(socket)這一句代碼,此處handle對應的類是Http11Protocol中的内部類Http11ConnectionHandler,在此後的進行中,會有一些請求的預處理,我們用一個時序圖來表示一下:
在這個過程中,會對原始的socket進行一些處理,到CoyoteAdapter時,接受的參數已經是org.apache.coyote.Request和org.apache.coyote.Response了,但是要注意的是,此時這兩個類并不是我們常用的HttpServletRequest和HttpServletResponse的實作類,而是Tomcat内部的資料結構,存儲了和輸入、輸出相關的資訊。值得注意的是,在CoyoteAdapter的service方法中,會調用名為postParseRequest的方法,在這個方法中,會解析請求,調用Mapper的Map方法來确定該請求該由哪個Engine、Host和Context來處理。
在以上的資訊處理完畢後,在CoyoteAdapter的service方法中,會調用這樣一個方法:
connector.getContainer().getPipeline().getFirst().invoke(request, response);
這個方法會涉及到Tomcat元件中的Container實作類的重要組成結構,即每個容器類元件都有一個pipeline屬性,這個屬性控制請求的處理過程,在pipeline上可以添加Valve,進而可以控制請求的處理流程。可以用下面的圖來表示,請求是如何流動的:
可以将請求想象成水的流動,請求需要在各個元件之間流動,中間經過若幹的水管和水閥,等所有的水閥走完,請求也就處理完了,而每個元件都會有一個預設的水閥(以Standard作為類的字首)來進行請求的處理,如果業務需要的話,可以自定義Valve,将其安裝到容器中。
後面的處理過程就比較類似了,會按照解析出來的Engine、Host、Context的順序來進行處理。這裡用了兩張算不上标準的時序圖來描述這一過程:
在以上的流程中,會一層層地調用各個容器元件的Valve的invoke方法,其中StandardWrapperValve這個标準閥門将會調用StandardWrapper的allocate方法來擷取真正要執行的Servlet(在Tomcat中所有的請求最終都會映射到一個Servlet,靜态資源和JSP也是如此),并按照請求的位址來建構過濾器鍊,按照順序執行各個過濾器并最終調用目标Servlet的service方法,來完成業務的真正處理。
以上的處理過程中,涉及到很多重要的代碼,後續的文章會擇期要者進行解析,如:
- Mapper中的internalMapWrapper方法(用來比對對應的Servlet)
- ApplicationFilterFactory的createFilterChain方法(用來建立該請求的過濾器鍊)
- ApplicationFilterChain的internalDoFilter方法(用來執行過濾器方法以及最後的Servlet)