1. 引言
既然是在講 Tomcat ,那么一個 HTTP 請求的請求流程是無論如何也繞不開的。
首先拋開所有,使用我們現有的知識面,猜測一下一個請求被 Tomcat 處理的過程:
1. 客戶端(瀏覽器)發送一個請求(HTTP)
2. 建立 Socket 連接
3. 通過 Socket 讀取數據
4. 根據協議(HTTP)解析請求
5. 調用對應的代碼完成響應
上面這套流程,我相信任何一個 Java 碼農都能想得到,當 Tomcat 接受到請求后,經過一系列的基礎處理,最終會調用到我們自己的業務程序上,或者說是 Servlet 上,在早期,這些請求會由我們自己實現的 jsp 或者是 Servlet 進行接收,隨着時代的發展以及演進,出現了 Struts 和 Spring 等中間件來幫助我們完成基礎的請求處理,使得開發人員更加關注具體的業務。
我想很多人都很好奇, Tomcat 是如何將這些 HTTP 請求轉交給我們的 Servlet 的?
2. Connector 初始化
上一篇我們在聊 Tomcat 啟動流程的時候,最后執行初始化的是 org.apache.catalina.connector.Connector#initInternal()
,這時整個初始化流程到了 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);
}
}
這段代碼中主要做了兩件事情:
- 構造了 CoyoteAdapter 對象,並且將其設置為 ProtocolHandler 的 Adapter 。
- 調用了
org.apache.coyote.ProtocolHandler#init()
的方法。
先說第二件事情,調用了 org.apache.coyote.ProtocolHandler#init()
,ProtocolHandler
是在構造方法中進行的初始化,這里的核心代碼是:
setProtocol(protocol)
再看下 setProtocol()
這個方法做了啥:
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
協議,另一塊是使用了 AJP/1.3
的協議,這里通過協議的不同,最終初始化了不同的類。
如果是使用 HTTP/1.1
的協議,則采用了 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
繼承關系圖:
可以看到這兩個類都繼承自 org.apache.coyote.AbstractProtocol
,通過查看 org.apache.coyote.AbstractProtocol#init()
方法,可以看到是調用了 org.apache.tomcat.util.net.AbstractEndpoint#init()
,而 AbstractEndpoint
的實例化操作是在實例化 AjpProtocol
和 Http11Protocol
的時候在其構造函數中實例化的,而在 AjpProtocol
和 Http11Protocol
的構造函數中,實際上是都初始化了 org.apache.tomcat.util.net.JIoEndpoint
,只不過根據不同的 HTTP 或者是 AJP 協議,它們具有不同的連接處理類。其中 Http11Protocol
的連接處理類為 org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler
,而連接處理類為 org.apache.coyote.ajp.AjpProtocol.AjpConnectionHandler
。
除此之外, ProtocolHandler
還有其他實現,都在 org.apache.coyote
這個包中,一些常見的類圖如下:
因此到這里我們基本清楚了 Connector
的初始化流程,總結如下:
//1 HTTP/1.1協議連接器
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協議連接器
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 啟動
ProtocolHandler 的初始化稍微有些特殊,Server、Service、Connector 這三個容器的初始化順序為: Server -> Service -> Connector 。值得注意的是, ProtocolHandler 作為 Connector 的子容器,其初始化過程並不是由 Connector 的 initInternal 方法調用的,而是與啟動過程一道被 Connector 的 startInternal 方法所調用。
@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 容器的狀態更改為啟動中(LifecycleState.STARTING) 。
- 啟動 ProtocolHandler 。
簡單起見,以 Http11Protocol 為例介紹 ProtocolHandler 的 start 方法:
由於 ProtocolHandler
是一個接口,它的 start
方法有兩個抽象類進行實現:
- 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.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
priority = Thread.NORM_PRIORITY;
}
timeoutThread.setPriority(priority);
timeoutThread.setDaemon(true);
timeoutThread.start();
}
這里最核心的一句代碼是調用了 endpoint.start()
,這里的 endpoint
是抽象類 AbstractEndpoint#start()
:
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bind();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
這一段也做了兩件事兒:
- 判斷當前綁定狀態,如果沒有綁定,則會先去綁定,調用
bind()
。 - 然后調用
startInternal()
進行初始化。
AbstractEndpoint
有三個子類:
我們專注於 AprEndpoint
這個子類,上面 AbstractEndpoint
抽象類中的兩個方法 bind()
和 startInternal()
都會在這個類中進行實現。
我們先看 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"));
}
}
}
}
這個方法上面的注釋已經寫的比較清楚了,首先第一個方法注釋就告訴我們這個方法是用來初始化 endpoint
的,大體做了這么幾件事兒:
- 創建了 APR 的 rootPool ,從命名上看這應該是一個根連接池。
- 創建一個 serverSockPool ,使用剛才創建的 rootPool 進行創建,這個命名大家就都看得懂了。
- 創建用來做綁定的 APR 的地址。
- 創建一個 APR server socket -> serverSock ,這里開啟了 socket 。
- 將剛才創建的 server 和 socket 進行綁定。
- 開啟 server socket 上面的監聽。
- 一些系統層面的設置。
- 如果需要的話,還會進行一些 SSL 的相關設置。
接着看下 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();
}
}
這個方法所有的前提條件都在於如果 AprEndpoint
尚未出於運行中,即 running == true
,首先如果沒有創建線程池 getExecutor() == null
,則需要調用 createExecutor()
方法創建線程池和任務隊列 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);
}
剩下兩個是創建了兩個線程,分別是 poller 和 sendfile ,這兩個都是 AprEndpoint
的內部類,這兩個線程一個是用來做輪詢,另一個是用來做數據發送。
至此, Tomcat 中為請求處理的准備工作已經完成。