前言
不知不覺锭沟,這已經(jīng)是我們深入理解tomcat的第九篇文章了,我們?cè)诘诎似治隽藅omcat的連接器识补,分析了連接器的的Connector,Http11Protocol凭涂,Http11ConnectionHandler巢株,JIoEndpoint槐瑞,Acceptor 等等這些有關(guān)連接器的類和組件,當(dāng)時(shí)我們分析到Acceptor的run方法后就停止分析了阁苞,因?yàn)楹竺娴拇a與請(qǐng)求過程高度相關(guān)困檩,而且請(qǐng)求過程這段代碼時(shí)比較復(fù)雜的,需要很大的篇幅去講述那槽。廢話不多說悼沿,今天我們就開始分析 http://localhost:8080
在tomcat中是如何運(yùn)作的,是如何到達(dá)的Servlet的骚灸。
序列圖
首先來一張樓主畫的序列圖糟趾,然后,我們這篇文章基本就按照我們的這張圖來講述了。這張圖有47個(gè)層次的調(diào)用义郑,上傳到簡(jiǎn)書就變模糊了蝶柿,因此樓主將圖片放到了github上,大家可以看的清楚一點(diǎn)非驮。
樓主這次分析只锭,會(huì)先啟動(dòng)tomcat, 然后在
1. JIoEndpoint 分析
上次我們分析連接器的時(shí)候提到了一個(gè)干實(shí)事不摸魚的好員工院尔,JIoEndpoint蜻展,該類在創(chuàng)建Http11Protocol對(duì)象的時(shí)候會(huì)一起被創(chuàng)建,可以說他們是依賴關(guān)系邀摆。并且 JIoEndpoint 也包含一個(gè) Http11ConnectionHandler 協(xié)議連接處理器類纵顾,該類是 Http11Protocol 的靜態(tài)內(nèi)部類。而 Http11ConnectionHandler 由依賴 Http11Protocol栋盹,可以說三者是一種循環(huán)的關(guān)系施逾,Http11Protocol 依賴著 JIoEndpoint,Http11ConnectionHandler 依賴著 Http11Protocol 例获,JIoEndpoint 依賴著 Http11ConnectionHandler 汉额。而處理 HTTP BIO 模式的連接主要由 JIoEndpoint 和它的兩個(gè)(共4個(gè)內(nèi)部類和一個(gè)內(nèi)部接口)內(nèi)部類 Acceptor 和 SocketProcessor 完成的。下面是JIoEndpoint 的類結(jié)構(gòu)圖:
Acceptor 我們?cè)谏洗畏治鲞B接器的時(shí)候已經(jīng)分析過了榨汤,它其實(shí)是用于接收HTTP 請(qǐng)求的線程蠕搜。用于從 SocketServer 接受請(qǐng)求。
SocketProcessor 則是我們今天分析的源頭收壕,因?yàn)閺奈覀兊牡诎似恼轮兄兰斯啵珹cceptor 將請(qǐng)求處理之后會(huì)交給 SocketProcessor 進(jìn)行真正的處理。那么我們就來分析分析該類蜜宪。
2. SocketProcessor 分析
首先該類是一個(gè)繼承了 Runnable 的內(nèi)部類虫埂,還記得在連接器啟動(dòng)的時(shí)候,會(huì)啟動(dòng)一個(gè)線程池圃验,該連接池就是用于執(zhí)行該線程的掉伏。那么我們就看看該類的實(shí)現(xiàn):
/**
* This class is the equivalent of the Worker, but will simply use in an
* external Executor thread pool.
*
* 這個(gè)類相當(dāng)于工作人員,但是只會(huì)在一個(gè)外部執(zhí)行器線程池中使用
*/
protected class SocketProcessor implements Runnable {
protected SocketWrapper<Socket> socket = null;
protected SocketStatus status = null;
public SocketProcessor(SocketWrapper<Socket> socket) {
if (socket==null) throw new NullPointerException();
this.socket = socket;
}
public SocketProcessor(SocketWrapper<Socket> socket, SocketStatus status) {
this(socket);
this.status = status;
}
@Override
public void run() {
boolean launch = false;
synchronized (socket) {
try {
SocketState state = SocketState.OPEN;
try {
// SSL handshake
serverSocketFactory.handshake(socket.getSocket()); // 什么都不做
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.err.handshake"), t);
}
// Tell to close the socket
state = SocketState.CLOSED;
}
if ((state != SocketState.CLOSED)) {// open
if (status == null) { // status == null
state = handler.process(socket, SocketStatus.OPEN);// AbstractProtocol.process(); state 變?yōu)?close
} else { // handler == Http11Protocol$Http11ConnectionHandler
state = handler.process(socket,status); // state = closed
}
}
if (state == SocketState.CLOSED) {
// Close socket
if (log.isTraceEnabled()) {
log.trace("Closing socket:"+socket);
}
countDownConnection();// 進(jìn)入該方法
try {
socket.getSocket().close(); // 關(guān)閉流
} catch (IOException e) {
// Ignore
}
} else if (state == SocketState.OPEN ||
state == SocketState.UPGRADING ||
state == SocketState.UPGRADED){
socket.setKeptAlive(true);
socket.access();
launch = true; // 此時(shí)才走finally try 邏輯
} else if (state == SocketState.LONG) {
socket.access();
waitingRequests.add(socket);// 長(zhǎng)連接澳窑,
}
} finally {
if (launch) {
try {
getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN));
} catch (RejectedExecutionException x) {
log.warn("Socket reprocessing request was rejected for:"+socket,x);
try {
//unable to handle connection at this time
handler.process(socket, SocketStatus.DISCONNECT);
} finally {
countDownConnection();
}
} catch (NullPointerException npe) {
if (running) {
log.error(sm.getString("endpoint.launch.fail"),
npe);
}
}
}
}
}
socket = null; // 完成請(qǐng)求
// Finish up this request
}
}
首先該類由2個(gè)屬性斧散,一個(gè)是 SocketWrapper<Socket> ,看名字就知道他其實(shí)就是 Socket 的包裝類照捡,另一個(gè)是 SocketStatus颅湘, 也就是 Socket 的狀態(tài)。我們看看該類的 run 方法的執(zhí)行邏輯:
- 首先處理socket的SSL栗精。實(shí)際上 DefaultServerSocketFactory(也就是我們默認(rèn)的) 是空的,什么都不做。
- 判斷socket狀態(tài)悲立,如果是null鹿寨,則設(shè)置為open,執(zhí)行 Http11ConnectionHandler 的 process 方法薪夕。
- 執(zhí)行 Http11ConnectionHandler 的 process 方法會(huì)返回一個(gè) SocketState脚草,后面會(huì)根據(jù)該狀態(tài)執(zhí)行不同的邏輯,如果是關(guān)閉原献,則減去一個(gè)連接數(shù)馏慨,并且關(guān)閉流。如果是開或則升級(jí)狀態(tài)姑隅,則進(jìn)入finally塊繼續(xù)交給線程池執(zhí)行写隶。如果是長(zhǎng)連接,則放入ConcurrentLinkedQueue 隊(duì)列讲仰,供另一個(gè)線程 AsyncTimeout 執(zhí)行(最后還是交給 SocketProcessor 執(zhí)行 )慕趴;
可以看到這個(gè)方法不是很復(fù)雜,并且我們能感覺到主要邏輯會(huì)在第二步鄙陡,因此我們就進(jìn)入到第二步的 process 方法中查看冕房。
3. Http11ConnectionHandler process 方法剖析
進(jìn)入handler 的process 方法,實(shí)際上是進(jìn)入了 Http11ConnectionHandler 的父類 AbstractConnectionHandler 的 process 方法趁矾,該方法是個(gè)模板方法耙册,讓我們看看該方法:
public SocketState process(SocketWrapper<S> socket,
SocketStatus status) {
Processor<S> processor = connections.remove(socket.getSocket()); // connections 是用于緩存長(zhǎng)連接的socket
if (status == SocketStatus.DISCONNECT && processor == null) { // 如果是斷開連接狀態(tài)且協(xié)議處理器為null
//nothing more to be done endpoint requested a close
//and there are no object associated with this connection
return SocketState.CLOSED;
}
socket.setAsync(false);// 非異步
try {
if (processor == null) {
processor = recycledProcessors.poll();// 如果從緩存中沒取到,從可以循環(huán)使用的 ConcurrentLinkedQueue 獲取
}
if (processor == null) {
processor = createProcessor(); // 如果還沒有毫捣,則創(chuàng)建一個(gè)
}
initSsl(socket, processor); // 設(shè)置SSL 屬性觅玻,默認(rèn)為null,可以配置
SocketState state = SocketState.CLOSED;
do {
if (status == SocketStatus.DISCONNECT &&
!processor.isComet()) {
// Do nothing here, just wait for it to get recycled
// Don't do this for Comet we need to generate an end
// event (see BZ 54022)
} else if (processor.isAsync() ||
state == SocketState.ASYNC_END) {
state = processor.asyncDispatch(status); // 如果是異步的
} else if (processor.isComet()) {
state = processor.event(status); // 事件驅(qū)動(dòng)培漏?溪厘??
} else if (processor.isUpgrade()) {
state = processor.upgradeDispatch(); // 升級(jí)轉(zhuǎn)發(fā)牌柄?畸悬??
} else {
state = processor.process(socket); // 默認(rèn)的 AbstractHttp11Processor.process
}
if (state != SocketState.CLOSED && processor.isAsync()) {
state = processor.asyncPostProcess();
}
if (state == SocketState.UPGRADING) {
// Get the UpgradeInbound handler
UpgradeInbound inbound = processor.getUpgradeInbound();
// Release the Http11 processor to be re-used
release(socket, processor, false, false);
// Create the light-weight upgrade processor
processor = createUpgradeProcessor(socket, inbound);
inbound.onUpgradeComplete();
}
} while (state == SocketState.ASYNC_END ||
state == SocketState.UPGRADING);
if (state == SocketState.LONG) {
// In the middle of processing a request/response. Keep the
// socket associated with the processor. Exact requirements
// depend on type of long poll
longPoll(socket, processor);
} else if (state == SocketState.OPEN) {
// In keep-alive but between requests. OK to recycle
// processor. Continue to poll for the next request.
release(socket, processor, false, true);
} else if (state == SocketState.SENDFILE) {
// Sendfile in progress. If it fails, the socket will be
// closed. If it works, the socket will be re-added to the
// poller
release(socket, processor, false, false);
} else if (state == SocketState.UPGRADED) {
// Need to keep the connection associated with the processor
longPoll(socket, processor);
} else {
// Connection closed. OK to recycle the processor.
if (!(processor instanceof UpgradeProcessor)) {
release(socket, processor, true, false);
}
}
return state;
} catch(java.net.SocketException e) {
// SocketExceptions are normal
getLog().debug(sm.getString(
"abstractConnectionHandler.socketexception.debug"), e);
} catch (java.io.IOException e) {
// IOExceptions are normal
getLog().debug(sm.getString(
"abstractConnectionHandler.ioexception.debug"), e);
}
// Future developers: if you discover any other
// rare-but-nonfatal exceptions, catch them here, and log as
// above.
catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
// any other exception or error is odd. Here we log it
// with "ERROR" level, so it will show up even on
// less-than-verbose logs.
getLog().error(
sm.getString("abstractConnectionHandler.error"), e);
}
// Don't try to add upgrade processors back into the pool
if (!(processor instanceof UpgradeProcessor)) {
release(socket, processor, true, false);
}
return SocketState.CLOSED;
}
該方法很長(zhǎng)珊佣,我們簡(jiǎn)略的說一下主要邏輯:
- 從 ConcurrentHashMap 中獲取長(zhǎng)連接的封裝了soceket的處理類蹋宦。
- 如果沒有,則從循環(huán)使用的 ConcurrentLinkedQueue 隊(duì)列中獲取咒锻。如果還沒有冷冗,則調(diào)用自己的 createProcessor 直接創(chuàng)建一個(gè)。
- 調(diào)用子類的 initSsl 方法惑艇,初始化 SSL 屬性蒿辙。如果配置文件沒配置拇泛,則設(shè)置為null。
- 根據(jù)不同的屬性調(diào)用不同的方法思灌,優(yōu)先判斷是否異步俺叭,默認(rèn)使用同步,也就是 Http11Processor.process 方法泰偿。
- 執(zhí)行結(jié)束后熄守,根據(jù)返回的不同的狀態(tài)調(diào)用不同的方法,比如 longPoll耗跛,release裕照,參數(shù)也不同,默認(rèn)是
release(socket, processor, true, false)
调塌,放入ConcurrentLinkedQueue (recycledProcessors)隊(duì)列中晋南。
我們重點(diǎn)關(guān)注 AbstractHttp11Processor.process 方法,該方法是處理socket的主要邏輯烟阐。
4. Http11Processor.process 方法解析
該方法其實(shí)是其父類 AbstractHttp11Processor 的方法搬俊,特別的長(zhǎng),不知道為什么tomcat的大師們?yōu)槭裁床环庋b一下蜒茄,代碼這么長(zhǎng)真的不太好看唉擂。樓主認(rèn)為一個(gè)方法最好不要超過50行。越短越好檀葛。樓主無奈玩祟,只好貼出樓主簡(jiǎn)化的代碼,大家湊合著看屿聋,如果想看詳細(xì)的源碼空扎,可以留言也可以去我的github clone;
@Override
public SocketState process(SocketWrapper<S> socketWrapper) throws IOException {
// Setting up the I/O
setSocketWrapper(socketWrapper);
getInputBuffer().init(socketWrapper, endpoint);// 設(shè)置輸入流
getOutputBuffer().init(socketWrapper, endpoint);// 設(shè)置輸出流
prepareRequest();// 準(zhǔn)備請(qǐng)求內(nèi)容
adapter.service(request, response); // 真正處理的方法 CoyoteAdapter
return SocketState.OPEN;
}
該方法步驟:
- 設(shè)置socket润讥。
- 設(shè)置輸入流和輸出流转锈。
- 繼續(xù)向下執(zhí)行。執(zhí)行 CoyoteAdapter 的service 方法楚殿。
- 返回狀態(tài)(默認(rèn)返回 OPEN)供上層判斷撮慨。
我們重點(diǎn)關(guān)注 CoyoteAdapter 的service 方法;
5. CoyoteAdapter.service 方法解析
該方法同樣很長(zhǎng)脆粥,我們看看該方法的邏輯:
/**
* Service method.
*/
@Override
public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res)
throws Exception {
Request request = (Request) req.getNote(ADAPTER_NOTES); // 實(shí)現(xiàn)了 servlet 標(biāo)準(zhǔn)的 Request
Response response = (Response) res.getNote(ADAPTER_NOTES);
if (request == null) {
// Create objects
request = connector.createRequest();
request.setCoyoteRequest(req);
response = connector.createResponse();
response.setCoyoteResponse(res);
// Link objects
request.setResponse(response); // 互相關(guān)聯(lián)
response.setRequest(request);
// Set as notes
req.setNote(ADAPTER_NOTES, request);
res.setNote(ADAPTER_NOTES, response);
// Set query string encoding
req.getParameters().setQueryStringEncoding // 解析 uri
(connector.getURIEncoding());
}
if (connector.getXpoweredBy()) { // 網(wǎng)站安全狗IIS
response.addHeader("X-Powered-By", POWERED_BY);
}
boolean comet = false;
boolean async = false;
try {
// Parse and set Catalina and configuration specific
// request parameters
req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
boolean postParseSuccess = postParseRequest(req, request, res, response); // 解析請(qǐng)求內(nèi)容
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container 調(diào)用 容器
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response); // 一個(gè)復(fù)雜的調(diào)用
if (request.isComet()) {
if (!response.isClosed() && !response.isError()) {
if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
// Invoke a read event right away if there are available bytes
if (event(req, res, SocketStatus.OPEN)) {
comet = true;
res.action(ActionCode.COMET_BEGIN, null);
}
} else {
comet = true;
res.action(ActionCode.COMET_BEGIN, null);
}
} else {
// Clear the filter chain, as otherwise it will not be reset elsewhere
// since this is a Comet request
request.setFilterChain(null);
}
}
}
AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
if (asyncConImpl != null) {
async = true;
} else if (!comet) {
request.finishRequest();
response.finishResponse();
if (postParseSuccess &&
request.getMappingData().context != null) {
// Log only if processing was invoked.
// If postParseRequest() failed, it has already logged it.
// If context is null this was the start of a comet request
// that failed and has already been logged.
((Context) request.getMappingData().context).logAccess(
request, response,
System.currentTimeMillis() - req.getStartTime(),
false);
}
req.action(ActionCode.POST_REQUEST , null);
}
} catch (IOException e) {
// Ignore
} finally {
req.getRequestProcessor().setWorkerThreadName(null);
// Recycle the wrapper request and response
if (!comet && !async) {
request.recycle();
response.recycle();
} else {
// Clear converters so that the minimum amount of memory
// is used by this processor
request.clearEncoders();
response.clearEncoders();
}
}
}
我們分析一下該方法的邏輯:
- 創(chuàng)建實(shí)現(xiàn) Servlet 標(biāo)準(zhǔn)的 Request 和 Response砌溺。
- 將Request 和 Response 互相關(guān)聯(lián)。
- 執(zhí)行 postParseRequest 解析請(qǐng)求內(nèi)容变隔。
- 執(zhí)行最重要的步驟:connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)规伐;
- 執(zhí)行一些清理工作。
6. CoyoteAdapter.postParseRequest 方法解析
我們看看他是如何解析請(qǐng)求的內(nèi)容的匣缘,也就是 postParseRequest 方法的實(shí)現(xiàn):
protected boolean postParseRequest(org.apache.coyote.Request req,
Request request,
org.apache.coyote.Response res,
Response response)
throws Exception {
// XXX the processor may have set a correct scheme and port prior to this point,
// in ajp13 protocols dont make sense to get the port from the connector...
// otherwise, use connector configuration
if (! req.scheme().isNull()) {
// use processor specified scheme to determine secure state
request.setSecure(req.scheme().equals("https"));
} else {
// use connector scheme and secure configuration, (defaults to
// "http" and false respectively)
req.scheme().setString(connector.getScheme());
request.setSecure(connector.getSecure());
}
// FIXME: the code below doesnt belongs to here,
// this is only have sense
// in Http11, not in ajp13..
// At this point the Host header has been processed.
// Override if the proxyPort/proxyHost are set
String proxyName = connector.getProxyName();
int proxyPort = connector.getProxyPort();
if (proxyPort != 0) {
req.setServerPort(proxyPort);
}
if (proxyName != null) {
req.serverName().setString(proxyName);
}
// Copy the raw URI to the decodedURI
MessageBytes decodedURI = req.decodedURI();
decodedURI.duplicate(req.requestURI());
// Parse the path parameters. This will:
// - strip out the path parameters
// - convert the decodedURI to bytes
parsePathParameters(req, request);
// URI decoding
// %xx decoding of the URL
try {
req.getURLDecoder().convert(decodedURI, false);
} catch (IOException ioe) {
res.setStatus(400);
res.setMessage("Invalid URI: " + ioe.getMessage());
connector.getService().getContainer().logAccess(
request, response, 0, true);
return false;
}
// Normalization
if (!normalize(req.decodedURI())) {
res.setStatus(400);
res.setMessage("Invalid URI");
connector.getService().getContainer().logAccess(
request, response, 0, true);
return false;
}
// Character decoding
convertURI(decodedURI, request);
// Check that the URI is still normalized
if (!checkNormalize(req.decodedURI())) {
res.setStatus(400);
res.setMessage("Invalid URI character encoding");
connector.getService().getContainer().logAccess(
request, response, 0, true);
return false;
}
// Set the remote principal
String principal = req.getRemoteUser().toString();
if (principal != null) {
request.setUserPrincipal(new CoyotePrincipal(principal));
}
// Set the authorization type
String authtype = req.getAuthType().toString();
if (authtype != null) {
request.setAuthType(authtype);
}
// Request mapping.
MessageBytes serverName;
if (connector.getUseIPVHosts()) {
serverName = req.localName();
if (serverName.isNull()) {
// well, they did ask for it
res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
}
} else {
serverName = req.serverName();
}
if (request.isAsyncStarted()) {
//TODO SERVLET3 - async
//reset mapping data, should prolly be done elsewhere
request.getMappingData().recycle();
}
boolean mapRequired = true;
String version = null;
while (mapRequired) {
if (version != null) {
// Once we have a version - that is it
mapRequired = false;
}
// This will map the the latest version by default
connector.getMapper().map(serverName, decodedURI, version,
request.getMappingData());
request.setContext((Context) request.getMappingData().context);
request.setWrapper((Wrapper) request.getMappingData().wrapper);
// Single contextVersion therefore no possibility of remap
if (request.getMappingData().contexts == null) {
mapRequired = false;
}
// If there is no context at this point, it is likely no ROOT context
// has been deployed
if (request.getContext() == null) {
res.setStatus(404);
res.setMessage("Not found");
// No context, so use host
Host host = request.getHost();
// Make sure there is a host (might not be during shutdown)
if (host != null) {
host.logAccess(request, response, 0, true);
}
return false;
}
// Now we have the context, we can parse the session ID from the URL
// (if any). Need to do this before we redirect in case we need to
// include the session id in the redirect
String sessionID = null;
if (request.getServletContext().getEffectiveSessionTrackingModes()
.contains(SessionTrackingMode.URL)) {
// Get the session ID if there was one
sessionID = request.getPathParameter(
SessionConfig.getSessionUriParamName(
request.getContext()));
if (sessionID != null) {
request.setRequestedSessionId(sessionID);
request.setRequestedSessionURL(true);
}
}
// Look for session ID in cookies and SSL session
parseSessionCookiesId(req, request);
parseSessionSslId(request);
sessionID = request.getRequestedSessionId();
if (mapRequired) {
if (sessionID == null) {
// No session means no possibility of needing to remap
mapRequired = false;
} else {
// Find the context associated with the session
Object[] objs = request.getMappingData().contexts;
for (int i = (objs.length); i > 0; i--) {
Context ctxt = (Context) objs[i - 1];
if (ctxt.getManager().findSession(sessionID) != null) {
// Was the correct context already mapped?
if (ctxt.equals(request.getMappingData().context)) {
mapRequired = false;
} else {
// Set version so second time through mapping the
// correct context is found
version = ctxt.getWebappVersion();
// Reset mapping
request.getMappingData().recycle();
break;
}
}
}
if (version == null) {
// No matching context found. No need to re-map
mapRequired = false;
}
}
}
if (!mapRequired && request.getContext().getPaused()) {
// Found a matching context but it is paused. Mapping data will
// be wrong since some Wrappers may not be registered at this
// point.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Should never happen
}
// Reset mapping
request.getMappingData().recycle();
mapRequired = true;
}
}
// Possible redirect
MessageBytes redirectPathMB = request.getMappingData().redirectPath;
if (!redirectPathMB.isNull()) {
String redirectPath = urlEncoder.encode(redirectPathMB.toString());
String query = request.getQueryString();
if (request.isRequestedSessionIdFromURL()) {
// This is not optimal, but as this is not very common, it
// shouldn't matter
redirectPath = redirectPath + ";" +
SessionConfig.getSessionUriParamName(
request.getContext()) +
"=" + request.getRequestedSessionId();
}
if (query != null) {
// This is not optimal, but as this is not very common, it
// shouldn't matter
redirectPath = redirectPath + "?" + query;
}
response.sendRedirect(redirectPath);
request.getContext().logAccess(request, response, 0, true);
return false;
}
// Filter trace method
if (!connector.getAllowTrace()
&& req.method().equalsIgnoreCase("TRACE")) {
Wrapper wrapper = request.getWrapper();
String header = null;
if (wrapper != null) {
String[] methods = wrapper.getServletMethods();
if (methods != null) {
for (int i=0; i<methods.length; i++) {
if ("TRACE".equals(methods[i])) {
continue;
}
if (header == null) {
header = methods[i];
} else {
header += ", " + methods[i];
}
}
}
}
res.setStatus(405);
res.addHeader("Allow", header);
res.setMessage("TRACE method is not allowed");
request.getContext().logAccess(request, response, 0, true);
return false;
}
return true;
}
- 設(shè)置 請(qǐng)求的消息類型猖闪。
- 設(shè)置代理名稱和代理端口(如果配置文件有的話)鲜棠。
- 解析URL路徑參數(shù)。
- 轉(zhuǎn)換 URI 的編碼萧朝。
- 從Connector 容器中的Mapper 中取出對(duì)應(yīng)的 Context 和 Servlet, 設(shè)置 Request 的 WebApp應(yīng)用和Servlet岔留。如果Context不存在夏哭,則返回404检柬。
- 解析cookie,解析sessionId竖配,設(shè)置 SessionId何址,
- 判斷方法類型是否是 TRACE 類型的方法,如果是进胯,則返回405.不允許該方法進(jìn)入服務(wù)器用爪。
7.connector.getService().getContainer().getPipeline().getFirst().invoke(request, response) 中的管道與閥門解析
好,解析完 postParseRequest 之后胁镐,我們?cè)倏纯聪旅娴牟襟E::connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)套像,這個(gè)鏈?zhǔn)秸{(diào)用可以說很自信鹦筹,一點(diǎn)不怕 NPE。很明顯,他們很自信销部,因?yàn)樵賳?dòng)過程中就已經(jīng)設(shè)置這些屬性了。而這段代碼也引出了2個(gè)概念吁脱,管道和閥尤溜,getPipeline 獲取管道 Pipeline,getFirst 獲取第一個(gè)閥竭宰。我們先說說什么是管道空郊?什么是閥門?
我們知道切揭,Tomcat 是由容器組成的狞甚,容器從大到小的排列依次是:Server-->Service---->Engine--->Host--->Context--->Wrapper,那么當(dāng)一個(gè)請(qǐng)求過來廓旬,從大容器到小容器哼审,他們一個(gè)一個(gè)的傳遞,像接力比賽嗤谚,如果是我們?cè)O(shè)計(jì)棺蛛,我們可能只需要將讓大容器持有小容器就好了,即能夠傳遞了巩步,但是旁赊,如果在傳遞的過程中我們需要做一些事情呢?比如校驗(yàn)椅野,比如記錄日志终畅,比如記錄時(shí)間籍胯,比如權(quán)限,并且我們要保證不能耦合离福,隨時(shí)可去除一個(gè)功能杖狼。我們?cè)撛趺崔k?相信由經(jīng)驗(yàn)的同學(xué)已經(jīng)想到了妖爷。
那就是使用過濾器模式蝶涩。
眾多的過濾器如何管理?使用管道絮识,將過濾器都放在管道中绿聘,簡(jiǎn)直完美!4紊唷熄攘!
那么Tomcat中 Pipeline 就是剛剛說的管道,過濾器就是閥門 Valve彼念,每個(gè) Pipeline 只持有第一個(gè)閥門挪圾,后面的就不管了,因?yàn)榈谝粋€(gè)會(huì)指向第二個(gè)逐沙,第二個(gè)會(huì)指向第三個(gè)哲思,便于拆卸。
那么現(xiàn)在是時(shí)候看看我們的時(shí)序圖了酱吝,我們先請(qǐng)出一部分:
從圖中我們可以看出:請(qǐng)求從SocketProcessor 進(jìn)來也殖,然后交給 Http11ConnectionHandler,最后交給 CoyoteAdapter务热,開始觸及容器的管道忆嗜。CoyoteAdapter 持有一個(gè) Connector 實(shí)例,我們?cè)俪跏蓟臅r(shí)候已經(jīng)知道了崎岂,Connector 會(huì)獲取他對(duì)應(yīng)的容器(一個(gè)容器對(duì)應(yīng)多個(gè)連接器)StandardService捆毫,StandardService會(huì)獲取他的下級(jí)容器 StandardEngine,這個(gè)時(shí)候冲甘,就該獲取管道了绩卤,管道管理著閥門。閥門接口 Pipeline 只有一個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn) StandardPipeline江醇,每個(gè)容器都持有該管道實(shí)例(在初始化的時(shí)候就創(chuàng)建好了)濒憋。管道會(huì)獲取第一個(gè)閥門,如果不存在陶夜,就返回一個(gè)基礎(chǔ)閥門(每個(gè)容器創(chuàng)建的時(shí)候都會(huì)放入一個(gè)標(biāo)準(zhǔn)的對(duì)應(yīng)的容器閥門作為其基礎(chǔ)閥門)凛驮。再調(diào)用閥門的 invoke 方法,該方法在執(zhí)行完自己的邏輯之后条辟,便調(diào)用子容器的管道中的閥門的 invoke 方法黔夭。依次遞歸宏胯。
現(xiàn)在我們圖也看了,原理也說了本姥,現(xiàn)在該看代碼了肩袍,也就是 StandardEnglineValve 閥門的 invoke 方法:
/**
* Select the appropriate child Host to process this request,
* based on the requested server name. If no matching Host can
* be found, return an appropriate HTTP error.
*
* 根據(jù)所請(qǐng)求的服務(wù)器名稱選擇適當(dāng)?shù)淖又鳈C(jī)來處理這個(gè)請(qǐng)求。如果找不到匹配的主機(jī)婚惫,則返回一個(gè)適當(dāng)?shù)腍TTP錯(cuò)誤氛赐。
*
*/
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Host to be used for this Request
Host host = request.getHost();
if (host == null) {
response.sendError
(HttpServletResponse.SC_BAD_REQUEST,
sm.getString("standardEngine.noHost",
request.getServerName()));
return;
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(host.getPipeline().isAsyncSupported());
}
// Ask this Host to process this request
host.getPipeline().getFirst().invoke(request, response);
}
該方法很簡(jiǎn)單,校驗(yàn)該Engline 容器是否含有Host容器辰妙,如果不存在鹰祸,返回400錯(cuò)誤甫窟,否則繼續(xù)執(zhí)行 host.getPipeline().getFirst().invoke(request, response)
密浑,可以看到 Host 容器先獲取自己的管道,再獲取第一個(gè)閥門粗井,我們?cè)倏纯丛撻y門的 invoke 方法尔破。
8. host.getPipeline().getFirst().invoke 解析
該鏈?zhǔn)秸{(diào)用獲取的不是Basic 閥門,因?yàn)樗O(shè)置了第一個(gè)閥門:
@Override
public Valve getFirst() {
if (first != null) {
return first;
}
return basic;
}
返回的是 AccessLogValve 實(shí)例浇衬,我們進(jìn)入 AccessLogValve 的 invoke 方法查看:
@Override
public void invoke(Request request, Response response) throws IOException,
ServletException {
getNext().invoke(request, response);
}
啥也沒做懒构,只是將請(qǐng)求交給了下一個(gè)閥門,執(zhí)行g(shù)etNext耘擂,該方法是其父類 ValveBase 的方法胆剧。下一個(gè)閥門是誰呢? ErrorReportValve,我們看看該閥門的 invoke 方法:
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
// Perform the request
getNext().invoke(request, response);
if (response.isCommitted()) {
return;
}
Throwable throwable =
(Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
if (request.isAsyncStarted() && response.getStatus() < 400 &&
throwable == null) {
return;
}
if (throwable != null) {
// The response is an error
response.setError();
// Reset the response (if possible)
try {
response.reset();
} catch (IllegalStateException e) {
// Ignore
}
response.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
response.setSuspended(false);
try {
report(request, response, throwable);
} catch (Throwable tt) {
ExceptionUtils.handleThrowable(tt);
}
if (request.isAsyncStarted()) {
request.getAsyncContext().complete();
}
}
該方法首先執(zhí)行了下個(gè)閥門的 invoke 方法醉冤。然后根據(jù)返回的Request 屬性設(shè)置一些錯(cuò)誤信息秩霍。那么下個(gè)閥門是誰呢?其實(shí)就是基礎(chǔ)閥門了:StandardHostValve蚁阳,該閥門的 invoke 的方法是如何實(shí)現(xiàn)的呢铃绒?
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Context to be used for this Request
Context context = request.getContext();
if (context == null) {
response.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
sm.getString("standardHost.noContext"));
return;
}
// Bind the context CL to the current thread
if( context.getLoader() != null ) {
// Not started - it should check for availability first
// This should eventually move to Engine, it's generic.
if (Globals.IS_SECURITY_ENABLED) {
PrivilegedAction<Void> pa = new PrivilegedSetTccl(
context.getLoader().getClassLoader());
AccessController.doPrivileged(pa);
} else {
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
}
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(context.getPipeline().isAsyncSupported());
}
// Don't fire listeners during async processing
// If a request init listener throws an exception, the request is
// aborted
boolean asyncAtStart = request.isAsync();
// An async error page may dispatch to another resource. This flag helps
// ensure an infinite error handling loop is not entered
boolean errorAtStart = response.isError();
if (asyncAtStart || context.fireRequestInitEvent(request)) {
// Ask this Context to process this request
try {
context.getPipeline().getFirst().invoke(request, response);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
if (errorAtStart) {
container.getLogger().error("Exception Processing " +
request.getRequestURI(), t);
} else {
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
throwable(request, response, t);
}
}
// If the request was async at the start and an error occurred then
// the async error handling will kick-in and that will fire the
// request destroyed event *after* the error handling has taken
// place
if (!(request.isAsync() || (asyncAtStart &&
request.getAttribute(
RequestDispatcher.ERROR_EXCEPTION) != null))) {
// Protect against NPEs if context was destroyed during a
// long running request.
if (context.getState().isAvailable()) {
if (!errorAtStart) {
// Error page processing
response.setSuspended(false);
Throwable t = (Throwable) request.getAttribute(
RequestDispatcher.ERROR_EXCEPTION);
if (t != null) {
throwable(request, response, t);
} else {
status(request, response);
}
}
context.fireRequestDestroyEvent(request);
}
}
}
// Access a session (if present) to update last accessed time, based on a
// strict interpretation of the specification
if (ACCESS_SESSION) {
request.getSession(false);
}
// Restore the context classloader
if (Globals.IS_SECURITY_ENABLED) {
PrivilegedAction<Void> pa = new PrivilegedSetTccl(
StandardHostValve.class.getClassLoader());
AccessController.doPrivileged(pa);
} else {
Thread.currentThread().setContextClassLoader
(StandardHostValve.class.getClassLoader());
}
}
首先校驗(yàn)了Request 是否存在 Context,其實(shí)在執(zhí)行 CoyoteAdapter.postParseRequest 方法的時(shí)候就設(shè)置了螺捐,如果Context 不存在颠悬,就返回500,接著還是老套路:context.getPipeline().getFirst().invoke定血,該管道獲取的是基礎(chǔ)閥門:StandardContextValve赔癌,我們還是關(guān)注他的 invoke 方法。
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Disallow any direct access to resources under WEB-INF or META-INF
MessageBytes requestPathMB = request.getRequestPathMB();
if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
|| (requestPathMB.equalsIgnoreCase("/META-INF"))
|| (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
|| (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// Select the Wrapper to be used for this Request
Wrapper wrapper = request.getWrapper();
if (wrapper == null || wrapper.isUnavailable()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// Acknowledge the request
try {
response.sendAcknowledgement();
} catch (IOException ioe) {
container.getLogger().error(sm.getString(
"standardContextValve.acknowledgeException"), ioe);
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
}
wrapper.getPipeline().getFirst().invoke(request, response);
}
該方法也只是一些校驗(yàn)澜沟,最后卡是調(diào)用 wrapper.getPipeline().getFirst().invoke
灾票,獲取到的也是基礎(chǔ)閥門,該閥門是StandardWeapperValve 倔喂,我們看看該方法铝条。該方法很重要靖苇。
9. wrapper.getPipeline().getFirst().invoke 解析
該方法超長(zhǎng):
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Initialize local variables we may need
boolean unavailable = false;
Throwable throwable = null;
// This should be a Request attribute...
long t1=System.currentTimeMillis();
requestCount++;
StandardWrapper wrapper = (StandardWrapper) getContainer();
Servlet servlet = null;
Context context = (Context) wrapper.getParent();
// Check for the application being marked unavailable
if (!context.getState().isAvailable()) {
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardContext.isUnavailable"));
unavailable = true;
}
// Check for the servlet being marked unavailable
if (!unavailable && wrapper.isUnavailable()) {
container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
unavailable = true;
}
// Allocate a servlet instance to process this request
try {
if (!unavailable) {
servlet = wrapper.allocate();
}
} catch (UnavailableException e) {
container.getLogger().error(
sm.getString("standardWrapper.allocateException",
wrapper.getName()), e);
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
} catch (ServletException e) {
container.getLogger().error(sm.getString("standardWrapper.allocateException",
wrapper.getName()), StandardWrapper.getRootCause(e));
throwable = e;
exception(request, response, e);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.allocateException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
servlet = null;
}
// Identify if the request is Comet related now that the servlet has been allocated
boolean comet = false;
if (servlet instanceof CometProcessor && request.getAttribute(
Globals.COMET_SUPPORTED_ATTR) == Boolean.TRUE) {
comet = true;
request.setComet(true);
}
MessageBytes requestPathMB = request.getRequestPathMB();
DispatcherType dispatcherType = DispatcherType.REQUEST;
if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC;
request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
requestPathMB);
// Create the filter chain for this request
ApplicationFilterFactory factory =
ApplicationFilterFactory.getInstance();
ApplicationFilterChain filterChain =
factory.createFilterChain(request, wrapper, servlet);
// Reset comet flag value after creating the filter chain
request.setComet(false);
// Call the filter chain for this request
// NOTE: This also calls the servlet's service() method
try {
if ((servlet != null) && (filterChain != null)) {
// Swallow output if needed
if (context.getSwallowOutput()) {
try {
SystemLogHandler.startCapture();
if (request.isAsyncDispatching()) {
//TODO SERVLET3 - async
((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch();
} else if (comet) {
filterChain.doFilterEvent(request.getEvent());
request.setComet(true);
} else {
filterChain.doFilter(request.getRequest(),
response.getResponse());
}
} finally {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
context.getLogger().info(log);
}
}
} else {
if (request.isAsyncDispatching()) {
//TODO SERVLET3 - async
((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch();
} else if (comet) {
request.setComet(true);
filterChain.doFilterEvent(request.getEvent());
} else {
filterChain.doFilter
(request.getRequest(), response.getResponse());
}
}
}
} catch (ClientAbortException e) {
throwable = e;
exception(request, response, e);
} catch (IOException e) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
throwable = e;
exception(request, response, e);
} catch (UnavailableException e) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
// throwable = e;
// exception(request, response, e);
wrapper.unavailable(e);
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
// Do not save exception in 'throwable', because we
// do not want to do exception(request, response, e) processing
} catch (ServletException e) {
Throwable rootCause = StandardWrapper.getRootCause(e);
if (!(rootCause instanceof ClientAbortException)) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceExceptionRoot",
wrapper.getName(), context.getName(), e.getMessage()),
rootCause);
}
throwable = e;
exception(request, response, e);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
throwable = e;
exception(request, response, e);
}
// Release the filter chain (if any) for this request
if (filterChain != null) {
if (request.isComet()) {
// If this is a Comet request, then the same chain will be used for the
// processing of all subsequent events.
filterChain.reuse();
} else {
filterChain.release();
}
}
// Deallocate the allocated servlet instance
try {
if (servlet != null) {
wrapper.deallocate(servlet);
}
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.deallocateException",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
}
// If this servlet has been marked permanently unavailable,
// unload it and release this instance
try {
if ((servlet != null) &&
(wrapper.getAvailable() == Long.MAX_VALUE)) {
wrapper.unload();
}
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.unloadException",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
}
long t2=System.currentTimeMillis();
long time=t2-t1;
processingTime += time;
if( time > maxTime) maxTime=time;
if( time < minTime) minTime=time;
}
我們分析一下該方法的重要步驟:
- 獲取 StandardWrapper(封裝了Servlet) 實(shí)例調(diào)用 allocate 方法獲取 Stack 中的 Servlet 實(shí)例;
- 判斷servlet 是否實(shí)現(xiàn)了 CometProcessor 接口班缰,如果實(shí)現(xiàn)了則設(shè)置 request 的comet(Comet:基于 HTTP 長(zhǎng)連接的“服務(wù)器推”技術(shù)) 屬性為 true贤壁。
- 獲取 ApplicationFilterFactory 單例(注意:這個(gè)獲取單例的代碼是有線程安全問題的),調(diào)用該單例的 createFilterChain 方法獲取 ApplicationFilterChain 過濾器鏈實(shí)例埠忘。
- 執(zhí)行過濾器鏈 filterChain 的 doFilter 方法脾拆。該方法會(huì)循環(huán)執(zhí)行所有的過濾器,最終執(zhí)行 servlet 的 servie 方法莹妒。
我們分析一下 allocate 方法名船。
10. StandardWrapper.allcate 獲取 Servlet 實(shí)例方法解析
源碼:
@Override
public Servlet allocate() throws ServletException {
// If we are currently unloading this servlet, throw an exception
if (unloading)
throw new ServletException
(sm.getString("standardWrapper.unloading", getName()));
boolean newInstance = false;
// If not SingleThreadedModel, return the same instance every time
if (!singleThreadModel) {
// Load and initialize our instance if necessary
if (instance == null) {
synchronized (this) {
if (instance == null) {
try {
if (log.isDebugEnabled())
log.debug("Allocating non-STM instance");
instance = loadServlet();
if (!singleThreadModel) {
// For non-STM, increment here to prevent a race
// condition with unload. Bug 43683, test case
// #3
newInstance = true;
countAllocated.incrementAndGet();
}
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException
(sm.getString("standardWrapper.allocate"), e);
}
}
}
}
if (!instanceInitialized) {
initServlet(instance);
}
if (singleThreadModel) {
if (newInstance) {
// Have to do this outside of the sync above to prevent a
// possible deadlock
synchronized (instancePool) {
instancePool.push(instance);
nInstances++;
}
}
} else {
if (log.isTraceEnabled())
log.trace(" Returning non-STM instance");
// For new instances, count will have been incremented at the
// time of creation
if (!newInstance) {
countAllocated.incrementAndGet();
}
return (instance);
}
}
synchronized (instancePool) {
while (countAllocated.get() >= nInstances) {
// Allocate a new instance if possible, or else wait
if (nInstances < maxInstances) {
try {
instancePool.push(loadServlet());
nInstances++;
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException
(sm.getString("standardWrapper.allocate"), e);
}
} else {
try {
instancePool.wait();
} catch (InterruptedException e) {
// Ignore
}
}
}
if (log.isTraceEnabled())
log.trace(" Returning allocated STM instance");
countAllocated.incrementAndGet();
return instancePool.pop();
}
}
該方法很長(zhǎng),我們來看看該方法步驟: 判斷該類(StandardWrapper)中的 Servlet 實(shí)例是否為null旨怠,默認(rèn)不為null渠驼,該實(shí)例在初始化的時(shí)候就已經(jīng)注入,如果沒有注入鉴腻,則調(diào)用 loadServlet 方法迷扇,反射加載實(shí)例(注意,如果這個(gè)servlet 實(shí)現(xiàn)了 singleThreadModel 接口爽哎,該StandardWrapper 就是多個(gè)servlet 實(shí)例的蜓席,默認(rèn)是單個(gè)實(shí)例,多個(gè)實(shí)例會(huì)放入一個(gè)Stack(這個(gè)棧不是早就不建議使用了嗎) 類型的棧中)课锌。
11. ApplicationFilterFactory 解析(tomcat 7 會(huì)有并發(fā)問題)
獲取創(chuàng)建過濾器鏈工廠的單例厨内,但樓主看了代碼,發(fā)現(xiàn)該代碼一定會(huì)有問題渺贤。
public static ApplicationFilterFactory getInstance() {
if (factory == null) {
factory = new ApplicationFilterFactory();
}
return factory;
}
讀過樓主 深入解析單例模式
的文章應(yīng)該知道雏胃,這種寫法并發(fā)的時(shí)候一定是有問題的,會(huì)創(chuàng)建多個(gè)實(shí)例癣亚。但是樓主下載了最新的 tomcat 源碼丑掺,已經(jīng)解決了該 bug,新的 tomcat 已經(jīng)把 getInstance 去除了述雾,將 createFilterChain 方法改為靜態(tài)方法街州。
獲取過濾器工廠鏈后,創(chuàng)建過濾器鏈實(shí)例玻孟,從該Wrapper 中獲取父容器唆缴,從父容器 StandardContext 中獲取實(shí)例,從該實(shí)例中獲取 filterMaps黍翎,該 filterMaps 在初始化容器時(shí)從web.xml 中創(chuàng)建面徽。
12. 執(zhí)行過濾器鏈的 doFilter 方法
該方法主要執(zhí)行 ApplicationFilterChain.internalDoFilter() 方法,那么 方法 internalDoFilter 內(nèi)部又是如何實(shí)現(xiàn)的呢?下面時(shí)該方法的主要邏輯(主要時(shí)樓主字?jǐn)?shù)受限了):
// Call the next filter if there is one
if (pos < n) {
filter.doFilter(request, response, this);
}
servlet.service(request, response);
可以看到趟紊,ApplicationFilterChain 中維護(hù)了2個(gè)變量氮双,當(dāng)前位置 pos 和 過濾器數(shù)量,因此要執(zhí)行完所有的過濾器霎匈,而過濾器最終又會(huì)執(zhí)行 doFilter 方法戴差,就會(huì)又回到該方法,直到執(zhí)行完所有的過濾器铛嘱,最后執(zhí)行 servlet 的 service 方法暖释。到這里,一個(gè)完整的http請(qǐng)求就從socket 到了我們編寫的servlet中了墨吓。
13. 總結(jié)
終于球匕,我們完成了對(duì)tomcat請(qǐng)求過程的剖析,詳細(xì)理解了tomcat是如何處理一個(gè)http請(qǐng)求帖烘,也完了我們最初的計(jì)劃亮曹,下一篇就是總結(jié)我們的前9篇文章,敬請(qǐng)期待蚓让。