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 中為請求處理的準備工作已經完成。