Tomcat 第四篇:請(qǐng)求處理流程(上)

1. 引言
既然是在講 Tomcat ,那么一個(gè) HTTP 請(qǐng)求的請(qǐng)求流程是無論如何也繞不開的。
首先拋開所有,使用我們現(xiàn)有的知識(shí)面,猜測(cè)一下一個(gè)請(qǐng)求被 Tomcat 處理的過程:
1.?客戶端(瀏覽器)發(fā)送一個(gè)請(qǐng)求(HTTP)
2.?建立?Socket?連接
3.?通過?Socket?讀取數(shù)據(jù)
4.?根據(jù)協(xié)議(HTTP)解析請(qǐng)求
5.?調(diào)用對(duì)應(yīng)的代碼完成響應(yīng)
上面這套流程,我相信任何一個(gè) Java 碼農(nóng)都能想得到,當(dāng) Tomcat 接受到請(qǐng)求后,經(jīng)過一系列的基礎(chǔ)處理,最終會(huì)調(diào)用到我們自己的業(yè)務(wù)程序上,或者說是 Servlet 上,在早期,這些請(qǐng)求會(huì)由我們自己實(shí)現(xiàn)的 jsp 或者是 Servlet 進(jìn)行接收,隨著時(shí)代的發(fā)展以及演進(jìn),出現(xiàn)了 Struts 和 Spring 等中間件來幫助我們完成基礎(chǔ)的請(qǐng)求處理,使得開發(fā)人員更加關(guān)注具體的業(yè)務(wù)。
我想很多人都很好奇, Tomcat 是如何將這些 HTTP 請(qǐng)求轉(zhuǎn)交給我們的 Servlet 的?
2. Connector 初始化
上一篇我們?cè)诹?Tomcat 啟動(dòng)流程的時(shí)候,最后執(zhí)行初始化的是 org.apache.catalina.connector.Connector#initInternal() ,這時(shí)整個(gè)初始化流程到了 Connector ,看一下這段代碼:
//?去除部分代碼
protected?void?initInternal()?throws?LifecycleException?{
????super.initInternal();
????//?Initialize?adapter
????adapter?=?new?CoyoteAdapter(this);
????protocolHandler.setAdapter(adapter);
????//?......
????try?{
????????protocolHandler.init();
????}?catch?(Exception?e)?{
????????throw?new?LifecycleException(
????????????????sm.getString("coyoteConnector.protocolHandlerInitializationFailed"),?e);
????}
}
這段代碼中主要做了兩件事情:
構(gòu)造了 CoyoteAdapter 對(duì)象,并且將其設(shè)置為 ProtocolHandler 的 Adapter 。 調(diào)用了 org.apache.coyote.ProtocolHandler#init()的方法。
先說第二件事情,調(diào)用了 org.apache.coyote.ProtocolHandler#init() ,ProtocolHandler 是在構(gòu)造方法中進(jìn)行的初始化,這里的核心代碼是:
setProtocol(protocol)
再看下 setProtocol() 這個(gè)方法做了啥:
public?void?setProtocol(String?protocol)?{
????boolean?aprConnector?=?AprLifecycleListener.isAprAvailable()?&&
????????????AprLifecycleListener.getUseAprConnector();
????if?("HTTP/1.1".equals(protocol)?||?protocol?==?null)?{
????????if?(aprConnector)?{
????????????setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
????????}?else?{
????????????setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
????????}
????}?else?if?("AJP/1.3".equals(protocol))?{
????????if?(aprConnector)?{
????????????setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
????????}?else?{
????????????setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
????????}
????}?else?{
????????setProtocolHandlerClassName(protocol);
????}
}
看到這里可以知道,主邏輯分成了兩塊,一塊是使用 HTTP/1.1 協(xié)議,另一塊是使用了 AJP/1.3 的協(xié)議,這里通過協(xié)議的不同,最終初始化了不同的類。
如果是使用 HTTP/1.1 的協(xié)議,則采用了 org.apache.coyote.http11.Http11AprProtocol 或者 org.apache.coyote.http11.Http11NioProtocol,如果是采用 AJP/1.3 則采用 org.apache.coyote.ajp.AjpAprProtocol 或者是 org.apache.coyote.ajp.AjpNioProtocol 。
看下 org.apache.coyote.http11.Http11AprProtocol 和 org.apache.coyote.ajp.AjpAprProtocol 繼承關(guān)系圖:


可以看到這兩個(gè)類都繼承自 org.apache.coyote.AbstractProtocol ,通過查看 org.apache.coyote.AbstractProtocol#init() 方法,可以看到是調(diào)用了 org.apache.tomcat.util.net.AbstractEndpoint#init() ,而 AbstractEndpoint 的實(shí)例化操作是在實(shí)例化 AjpProtocol 和 Http11Protocol 的時(shí)候在其構(gòu)造函數(shù)中實(shí)例化的,而在 AjpProtocol 和 Http11Protocol 的構(gòu)造函數(shù)中,實(shí)際上是都初始化了 org.apache.tomcat.util.net.JIoEndpoint ,只不過根據(jù)不同的 HTTP 或者是 AJP 協(xié)議,它們具有不同的連接處理類。其中 Http11Protocol 的連接處理類為 org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler ,而連接處理類為 org.apache.coyote.ajp.AjpProtocol.AjpConnectionHandler 。
除此之外, ProtocolHandler 還有其他實(shí)現(xiàn),都在 org.apache.coyote 這個(gè)包中,一些常見的類圖如下:

因此到這里我們基本清楚了 Connector 的初始化流程,總結(jié)如下:
//1?HTTP/1.1協(xié)議連接器
org.apache.catalina.connector.Connector#init
->org.apache.coyote.http11.Http11AprProtocol#init
-->org.apache.tomcat.util.net.AprEndpoint#init
(org.apache.coyote.http11.Http11AprProtocol.Http11ConnectionHandler)
//?2?AJP/1.3協(xié)議連接器
org.apache.catalina.connector.Connector#init
->org.apache.coyote.ajp.AjpAprProtocol#init
-->org.apache.tomcat.util.net.AprEndpoint#init
(org.apache.coyote.ajp.AjpAprProtocol.AjpConnectionHandler)
3. Connector 啟動(dòng)
ProtocolHandler 的初始化稍微有些特殊,Server、Service、Connector 這三個(gè)容器的初始化順序?yàn)椋篠erver -> Service -> Connector 。值得注意的是, ProtocolHandler 作為 Connector 的子容器,其初始化過程并不是由 Connector 的 initInternal 方法調(diào)用的,而是與啟動(dòng)過程一道被 Connector 的 startInternal 方法所調(diào)用。
@Override
protected?void?startInternal()?throws?LifecycleException?{
????//?Validate?settings?before?starting
????if?(getPort()?0)?{
????????throw?new?LifecycleException(sm.getString(
????????????????"coyoteConnector.invalidPort",?Integer.valueOf(getPort())));
????}
????setState(LifecycleState.STARTING);
????try?{
????????protocolHandler.start();
????}?catch?(Exception?e)?{
????????throw?new?LifecycleException(
????????????????sm.getString("coyoteConnector.protocolHandlerStartFailed"),?e);
????}
}
這里也總共做了兩件事兒:
將 Connector 容器的狀態(tài)更改為啟動(dòng)中(LifecycleState.STARTING) 。 啟動(dòng) ProtocolHandler 。
簡(jiǎn)單起見,以 Http11Protocol 為例介紹 ProtocolHandler 的 start 方法:
由于 ProtocolHandler 是一個(gè)接口,它的 start 方法有兩個(gè)抽象類進(jìn)行實(shí)現(xiàn):
org.apache.coyote.AbstractAjpProtocol org.apache.coyote.ajp.AbstractProtocol
這里我們僅討論 org.apache.coyote.ajp.AbstractProtocol ,看下它的 start 的方法:
@Override
public?void?start()?throws?Exception?{
????if?(getLog().isInfoEnabled())?{
????????getLog().info(sm.getString("abstractProtocolHandler.start",?getName()));
????}
????endpoint.start();
????//?Start?timeout?thread
????asyncTimeout?=?new?AsyncTimeout();
????Thread?timeoutThread?=?new?Thread(asyncTimeout,?getNameInternal()?+?"-AsyncTimeout");
????int?priority?=?endpoint.getThreadPriority();
????if?(priority??Thread.MAX_PRIORITY)?{
????????priority?=?Thread.NORM_PRIORITY;
????}
????timeoutThread.setPriority(priority);
????timeoutThread.setDaemon(true);
????timeoutThread.start();
}
這里最核心的一句代碼是調(diào)用了 endpoint.start() ,這里的 endpoint 是抽象類 AbstractEndpoint#start() :
public?final?void?start()?throws?Exception?{
????if?(bindState?==?BindState.UNBOUND)?{
????????bind();
????????bindState?=?BindState.BOUND_ON_START;
????}
????startInternal();
}
這一段也做了兩件事兒:
判斷當(dāng)前綁定狀態(tài),如果沒有綁定,則會(huì)先去綁定,調(diào)用 bind()。然后調(diào)用 startInternal()進(jìn)行初始化。
AbstractEndpoint 有三個(gè)子類:

我們專注于 AprEndpoint 這個(gè)子類,上面 AbstractEndpoint 抽象類中的兩個(gè)方法 bind() 和 startInternal() 都會(huì)在這個(gè)類中進(jìn)行實(shí)現(xiàn)。
我們先看 bind() 方法:
/**
????*?Initialize?the?endpoint.
????*/
@Override
public?void?bind()?throws?Exception?{
????//?Create?the?root?APR?memory?pool
????try?{
????????rootPool?=?Pool.create(0);
????}?catch?(UnsatisfiedLinkError?e)?{
????????throw?new?Exception(sm.getString("endpoint.init.notavail"));
????}
????//?Create?the?pool?for?the?server?socket
????serverSockPool?=?Pool.create(rootPool);
????//?Create?the?APR?address?that?will?be?bound
????String?addressStr?=?null;
????if?(getAddress()?!=?null)?{
????????addressStr?=?getAddress().getHostAddress();
????}
????int?family?=?Socket.APR_INET;
????if?(Library.APR_HAVE_IPV6)?{
????????if?(addressStr?==?null)?{
????????????if?(!OS.IS_BSD)?{
????????????????family?=?Socket.APR_UNSPEC;
????????????}
????????}?else?if?(addressStr.indexOf(':')?>=?0)?{
????????????family?=?Socket.APR_UNSPEC;
????????}
????????}
????long?inetAddress?=?Address.info(addressStr,?family,
????????????getPort(),?0,?rootPool);
????//?Create?the?APR?server?socket
????serverSock?=?Socket.create(Address.getInfo(inetAddress).family,
????????????Socket.SOCK_STREAM,
????????????Socket.APR_PROTO_TCP,?rootPool);
????if?(OS.IS_UNIX)?{
????????Socket.optSet(serverSock,?Socket.APR_SO_REUSEADDR,?1);
????}
????if?(Library.APR_HAVE_IPV6)?{
????????if?(getIpv6v6only())?{
????????????Socket.optSet(serverSock,?Socket.APR_IPV6_V6ONLY,?1);
????????}?else?{
????????????Socket.optSet(serverSock,?Socket.APR_IPV6_V6ONLY,?0);
????????}
????}
????//?Deal?with?the?firewalls?that?tend?to?drop?the?inactive?sockets
????Socket.optSet(serverSock,?Socket.APR_SO_KEEPALIVE,?1);
????//?Bind?the?server?socket
????int?ret?=?Socket.bind(serverSock,?inetAddress);
????if?(ret?!=?0)?{
????????throw?new?Exception(sm.getString("endpoint.init.bind",?""?+?ret,?Error.strerror(ret)));
????}
????//?Start?listening?on?the?server?socket
????ret?=?Socket.listen(serverSock,?getAcceptCount());
????if?(ret?!=?0)?{
????????throw?new?Exception(sm.getString("endpoint.init.listen",?""?+?ret,?Error.strerror(ret)));
????}
????if?(OS.IS_WIN32?||?OS.IS_WIN64)?{
????????//?On?Windows?set?the?reuseaddr?flag?after?the?bind/listen
????????Socket.optSet(serverSock,?Socket.APR_SO_REUSEADDR,?1);
????}
????//?Enable?Sendfile?by?default?if?it?has?not?been?configured?but?usage?on
????//?systems?which?don't?support?it?cause?major?problems
????if?(!useSendFileSet)?{
????????setUseSendfileInternal(Library.APR_HAS_SENDFILE);
????}?else?if?(getUseSendfile()?&&?!Library.APR_HAS_SENDFILE)?{
????????setUseSendfileInternal(false);
????}
????//?Initialize?thread?count?default?for?acceptor
????if?(acceptorThreadCount?==?0)?{
????????//?FIXME:?Doesn't?seem?to?work?that?well?with?multiple?accept?threads
????????acceptorThreadCount?=?1;
????}
????//?Delay?accepting?of?new?connections?until?data?is?available
????//?Only?Linux?kernels?2.4?+?have?that?implemented
????//?on?other?platforms?this?call?is?noop?and?will?return?APR_ENOTIMPL.
????if?(deferAccept)?{
????????if?(Socket.optSet(serverSock,?Socket.APR_TCP_DEFER_ACCEPT,?1)?==?Status.APR_ENOTIMPL)?{
????????????deferAccept?=?false;
????????}
????}
????//?Initialize?SSL?if?needed
????if?(isSSLEnabled())?{
????????for?(SSLHostConfig?sslHostConfig?:?sslHostConfigs.values())?{
????????????createSSLContext(sslHostConfig);
????????}
????????SSLHostConfig?defaultSSLHostConfig?=?sslHostConfigs.get(getDefaultSSLHostConfigName());
????????if?(defaultSSLHostConfig?==?null)?{
????????????throw?new?IllegalArgumentException(sm.getString("endpoint.noSslHostConfig",
????????????????????getDefaultSSLHostConfigName(),?getName()));
????????}
????????Long?defaultSSLContext?=?defaultSSLHostConfig.getOpenSslContext();
????????sslContext?=?defaultSSLContext.longValue();
????????SSLContext.registerDefault(defaultSSLContext,?this);
????????//?For?now,?sendfile?is?not?supported?with?SSL
????????if?(getUseSendfile())?{
????????????setUseSendfileInternal(false);
????????????if?(useSendFileSet)?{
????????????????log.warn(sm.getString("endpoint.apr.noSendfileWithSSL"));
????????????}
????????}
????}
}
這個(gè)方法上面的注釋已經(jīng)寫的比較清楚了,首先第一個(gè)方法注釋就告訴我們這個(gè)方法是用來初始化 endpoint 的,大體做了這么幾件事兒:
創(chuàng)建了 APR 的 rootPool ,從命名上看這應(yīng)該是一個(gè)根連接池。 創(chuàng)建一個(gè) serverSockPool ,使用剛才創(chuàng)建的 rootPool 進(jìn)行創(chuàng)建,這個(gè)命名大家就都看得懂了。 創(chuàng)建用來做綁定的 APR 的地址。 創(chuàng)建一個(gè) APR server socket -> serverSock ,這里開啟了 socket 。 將剛才創(chuàng)建的 server 和 socket 進(jìn)行綁定。 開啟 server socket 上面的監(jiān)聽。 一些系統(tǒng)層面的設(shè)置。 如果需要的話,還會(huì)進(jìn)行一些 SSL 的相關(guān)設(shè)置。
接著看下 startInternal() 方法:
/**
??*?Start?the?APR?endpoint,?creating?acceptor,?poller?and?sendfile?threads.
??*/
@Override
public?void?startInternal()?throws?Exception?{
????if?(!running)?{
????????running?=?true;
????????paused?=?false;
????????processorCache?=?new?SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
????????????????socketProperties.getProcessorCache());
????????//?Create?worker?collection
????????if?(getExecutor()?==?null)?{
????????????createExecutor();
????????}
????????initializeConnectionLatch();
????????//?Start?poller?thread
????????poller?=?new?Poller();
????????poller.init();
????????Thread?pollerThread?=?new?Thread(poller,?getName()?+?"-Poller");
????????pollerThread.setPriority(threadPriority);
????????pollerThread.setDaemon(true);
????????pollerThread.start();
????????//?Start?sendfile?thread
????????if?(getUseSendfile())?{
????????????sendfile?=?new?Sendfile();
????????????sendfile.init();
????????????Thread?sendfileThread?=
????????????????????new?Thread(sendfile,?getName()?+?"-Sendfile");
????????????sendfileThread.setPriority(threadPriority);
????????????sendfileThread.setDaemon(true);
????????????sendfileThread.start();
????????}
????????startAcceptorThreads();
????}
}
這個(gè)方法所有的前提條件都在于如果 AprEndpoint 尚未出于運(yùn)行中,即 running == true ,首先如果沒有創(chuàng)建線程池 getExecutor() == null ,則需要調(diào)用 createExecutor() 方法創(chuàng)建線程池和任務(wù)隊(duì)列 TaskQueue 。
public?void?createExecutor()?{
????internalExecutor?=?true;
????TaskQueue?taskqueue?=?new?TaskQueue();
????TaskThreadFactory?tf?=?new?TaskThreadFactory(getName()?+?"-exec-",?daemon,?getThreadPriority());
????executor?=?new?ThreadPoolExecutor(getMinSpareThreads(),?getMaxThreads(),?60,?TimeUnit.SECONDS,taskqueue,?tf);
????taskqueue.setParent(?(ThreadPoolExecutor)?executor);
}
剩下兩個(gè)是創(chuàng)建了兩個(gè)線程,分別是 poller 和 sendfile ,這兩個(gè)都是 AprEndpoint 的內(nèi)部類,這兩個(gè)線程一個(gè)是用來做輪詢,另一個(gè)是用來做數(shù)據(jù)發(fā)送。
至此, Tomcat 中為請(qǐng)求處理的準(zhǔn)備工作已經(jīng)完成。

