【Tomcat源碼分析】從零開始理解 HTTP 請求處理
前言
終于步入 Connector 的解析階段,這無疑是 Tomcat 架構(gòu)中最為復雜的一環(huán)。作為連接器,它的職責顯而易見——連接。那么,它連接的究竟是什么呢?
Connector 宛如一座橋梁,將來自客戶端的請求,經(jīng)過精心封裝成 Request 和 Response 對象,傳遞給 Container 進行處理。Container 完成業(yè)務邏輯后,Connector 再將處理后的結(jié)果,通過 Response 對象返回給遠方的客戶端。
要深入理解 Connector 的精髓,需要我們從四個關鍵問題出發(fā),逐一探索。
- Connector 如何接收來自遠方的請求?
- 如何將這呼喚化作 Request 和 Response 的身影?
- 封裝后的 Request 和 Response 如何被遞交給 Container 處理?
- Container 處理完畢后,如何將結(jié)果托付給 Connector,并最終送回客戶端手中?
為了更好地理解 Connector 的內(nèi)部運作,讓我們先來欣賞一幅 Connector 結(jié)構(gòu)圖,它將幫助我們更直觀地感受其內(nèi)部的精妙設計。
圖片
【注意】:不同的協(xié)議和通信方式,將催生出不同的 ProtocolHandler 實現(xiàn)。在 Tomcat 8.5 版本中,ProtocolHandler 的類繼承關系圖譜如下:
圖片
針對這幅類繼承層級圖,我們可以做如下解讀:
ajp 和 http11 代表著兩種截然不同的協(xié)議,而 nio、nio2 和 apr 則分別代表著三種不同的通信方式。值得注意的是,協(xié)議與通信方式并非相互獨立,它們可以靈活組合,以適應不同的場景需求。
ProtocolHandler 內(nèi)部,包含著三個核心部件:Endpoint、Processor 和 Adapter,它們共同協(xié)作,完成請求的接收、處理和響應。
- Endpoint 負責處理底層的 Socket 網(wǎng)絡連接,它就像是一位網(wǎng)絡守衛(wèi),負責迎接來自網(wǎng)絡的呼喚,并將其轉(zhuǎn)化為可供處理的 Socket 連接。Processor 則肩負著將 Endpoint 接收到的 Socket 封裝成 Request 對象的重任,它就像一位翻譯官,將網(wǎng)絡語言轉(zhuǎn)化為服務器可以理解的語言。Adapter 則充當著連接器,它將 Request 對象傳遞給 Container,以便 Container 進行具體的處理。
- 由于 Endpoint 負責處理底層的 Socket 網(wǎng)絡連接,因此它需要實現(xiàn) TCP/IP 協(xié)議,而 Processor 則需要實現(xiàn) HTTP 協(xié)議,以解析 HTTP 請求。Adapter 則將請求適配到 Servlet 容器,使其能夠理解并處理來自外部的請求。
- Endpoint 的抽象實現(xiàn)類 AbstractEndpoint 定義了 Acceptor、AsyncTimeout 兩個內(nèi)部類和一個 Handler 接口。Acceptor 負責監(jiān)聽來自網(wǎng)絡的請求,一旦有新的請求到來,便會將其捕獲。AsyncTimeout 則負責檢查異步 Request 的超時,確保請求在合理的時間內(nèi)得到處理。Handler 則負責處理接收到的 Socket,它將調(diào)用 Processor 進行處理,將 Socket 轉(zhuǎn)換為 Request 對象,并最終傳遞給 Container。
至此,我們已經(jīng)解開了 Connector 如何接收請求、如何將請求封裝成 Request 和 Response,以及封裝后的 Request 和 Response 如何被傳遞給 Container 進行處理這三個關鍵問題。而對于最后一個問題,即 Container 處理完后如何將結(jié)果返回給客戶端,我們將在深入了解 Container 的運作機制后自然明了,前面章節(jié)已對此進行了詳細的分析。
Connector 源碼分析入口
在 Service 的標準實現(xiàn) StandardService 的源碼中,我們發(fā)現(xiàn)其 init()、start()、stop() 和 destroy() 方法分別會對 Connectors 的同名方法進行調(diào)用。值得注意的是,一個 Service 通常會對應多個 Connector,這意味著 Service 的生命周期管理會影響到所有與其關聯(lián)的 Connector。
Service.initInternal()
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
if (engine != null) {
engine.init();
}
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize mapper listener
mapperListener.init();
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}
Service.startInternal()
@Override
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);
// Start our defined Container first
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
mapperListener.start();
// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
正如我們所知,Connector 實現(xiàn)了 Lifecycle 接口,這使得它成為一個擁有生命周期的組件。因此,Connector 的啟動邏輯入口自然而然地落在 init() 和 start() 方法之中。
Connector 構(gòu)造方法
在深入分析 Connector 的啟動邏輯之前,不妨先來觀摩一下 server.xml 文件。這份文件如同 Tomcat 架構(gòu)的藍圖,清晰地展現(xiàn)了各個組件之間的聯(lián)系和布局,為我們理解 Connector 的運作提供了一個宏觀的視角。
<?xml versinotallow='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
在 server.xml 文件中,我們發(fā)現(xiàn) Connector 擁有多個關鍵屬性,其中 port 和 protocol 尤為重要。默認情況下,server.xml 支持兩種協(xié)議:HTTP/1.1 和 AJP/1.3。HTTP/1.1 用于支持傳統(tǒng)的 HTTP 1.1 協(xié)議,而 AJP/1.3 則專門用于支持與 Apache 服務器的通信,為 Apache 服務器提供一個與 Tomcat 交互的橋梁。
現(xiàn)在,讓我們將目光轉(zhuǎn)向 Connector 的構(gòu)造方法:
public Connector() {
this(null); // 1. 無參構(gòu)造方法,傳入?yún)?shù)為空協(xié)議,會默認使用`HTTP/1.1`
}
public Connector(String protocol) {
setProtocol(protocol);
// Instantiate protocol handler
// 5. 使用protocolHandler的類名構(gòu)造ProtocolHandler的實例
ProtocolHandler p = null;
try {
Class<?> clazz = Class.forName(protocolHandlerClassName);
p = (ProtocolHandler) clazz.getConstructor().newInstance();
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
} finally {
this.protocolHandler = p;
}
if (Globals.STRICT_SERVLET_COMPLIANCE) {
uriCharset = StandardCharsets.ISO_8859_1;
} else {
uriCharset = StandardCharsets.UTF_8;
}
}
@Deprecated
public void setProtocol(String protocol) {
boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
AprLifecycleListener.getUseAprConnector();
// 2. `HTTP/1.1`或`null`,protocolHandler使用`org.apache.coyote.http11.Http11NioProtocol`,不考慮apr
if ("HTTP/1.1".equals(protocol) || protocol == null) {
if (aprConnector) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
} else {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
}
}
// 3. `AJP/1.3`,protocolHandler使用`org.apache.coyote.ajp.AjpNioProtocol`,不考慮apr
else if ("AJP/1.3".equals(protocol)) {
if (aprConnector) {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
} else {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
}
}
// 4. 其他情況,使用傳入的protocol作為protocolHandler的類名
else {
setProtocolHandlerClassName(protocol);
}
}
在 Connector 的構(gòu)造方法中,我們發(fā)現(xiàn)它主要完成了以下幾項工作:
- 當傳入的參數(shù)為空協(xié)議時,它會默認使用 HTTP/1.1 協(xié)議。
- 當傳入的協(xié)議為 HTTP/1.1 或 null 時,它會選擇 org.apache.coyote.http11.Http11NioProtocol 作為 ProtocolHandler,并忽略 apr 選項。
- 當傳入的協(xié)議為 AJP/1.3 時,它會選擇 org.apache.coyote.ajp.AjpNioProtocol 作為 ProtocolHandler,同樣忽略 apr 選項。
- 對于其他情況,它會直接使用傳入的 protocol 作為 ProtocolHandler 的類名。
- 最后,它會使用 ProtocolHandler 的類名來構(gòu)造 ProtocolHandler 的實例。
Connector.initInternal()
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize adapter
// 1. 初始化adapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
// 2. 設置接受body的method列表,默認為POST
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",
getProtocolHandlerClassName()));
}
if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
protocolHandler instanceof AbstractHttp11JsseProtocol) {
AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
(AbstractHttp11JsseProtocol<?>) protocolHandler;
if (jsseProtocolHandler.isSSLEnabled() &&
jsseProtocolHandler.getSslImplementationName() == null) {
// OpenSSL is compatible with the JSSE configuration, so use it if APR is available
jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
}
}
// 3. 初始化protocolHandler
try {
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
Connector 的 init() 方法主要完成了三項重要的初始化工作:
- 初始化 adapter:Adapter 負責將請求傳遞給 Container,因此需要在 init() 方法中完成初始化,以便后續(xù)能夠正常地將請求傳遞給 Container 進行處理。
- 設置接受 body 的 method 列表:默認情況下,Connector 只允許 POST 方法提交 body 數(shù)據(jù),但在某些情況下,可能需要允許其他方法提交 body 數(shù)據(jù),因此需要在 init() 方法中設置允許提交 body 的方法列表。
- 初始化 protocolHandler:ProtocolHandler 是 Connector 的核心組件,負責處理請求和響應,因此需要在 init() 方法中完成 protocolHandler 的初始化,以便后續(xù)能夠正常地處理請求和響應。
從 ProtocolHandler 的類繼承層級關系圖 中,我們可以看到 ProtocolHandler 的子類都必須實現(xiàn) AbstractProtocol 抽象類。而 protocolHandler.init(); 方法的具體實現(xiàn)則取決于具體的 ProtocolHandler 子類,它會根據(jù)不同的協(xié)議和通信方式進行相應的初始化操作。
代碼正是在這個抽象類里面。我們來分析一下。
@Override
public void init() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
}
if (oname == null) {
// Component not pre-registered so register it
oname = createObjectName();
if (oname != null) {
Registry.getRegistry(null, null).registerComponent(this, oname, null);
}
}
if (this.domain != null) {
rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
Registry.getRegistry(null, null).registerComponent(
getHandler().getGlobal(), rgOname, null);
}
// 1. 設置endpoint的名字,默認為:http-nio-{port}
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
// 2. 初始化endpoint
endpoint.init();
}
接下來,讓我們一同探究 Endpoint.init() 方法的內(nèi)部。它位于 AbstractEndpoint 抽象類中,采用模板方法模式,巧妙地將核心邏輯委托給子類的 bind() 方法。
public abstract void bind() throws Exception;
public abstract void unbind() throws Exception;
public abstract void startInternal() throws Exception;
public abstract void stopInternal() throws Exception;
public void init() throws Exception {
// 執(zhí)行bind()方法
if (bindOnInit) {
bind();
bindState = BindState.BOUND_ON_INIT;
}
if (this.domain != null) {
// Register endpoint (as ThreadPool - historical name)
oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
Registry.getRegistry(null, null).registerComponent(this, oname, null);
ObjectName socketPropertiesOname = new ObjectName(domain +
":type=ThreadPool,name=\"" + getName() + "\",subType=SocketProperties");
socketProperties.setObjectName(socketPropertiesOname);
Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);
for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {
registerJmx(sslHostConfig);
}
}
}
繼續(xù)追尋著代碼的蹤跡,我們終于來到了 bind() 方法,它揭示了 Connector 初始化的精髓所在。關鍵的代碼片段 serverSock.socket().bind(addr, getAcceptCount());用于 將 ServerSocket 綁定到指定的 IP 地址和端口
@Override
public void bind() throws Exception {
if (!getUseInheritedChannel()) {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
//綁定ServerSocket到指定的IP和端口
serverSock.socket().bind(addr,getAcceptCount());
} else {
// Retrieve the channel provided by the OS
Channel ic = System.inheritedChannel();
if (ic instanceof ServerSocketChannel) {
serverSock = (ServerSocketChannel) ic;
}
if (serverSock == null) {
throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
}
}
serverSock.configureBlocking(true); //mimic APR behavior
// Initialize thread count defaults for acceptor, poller
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
if (pollerThreadCount <= 0) {
//minimum one poller thread
pollerThreadCount = 1;
}
setStopLatch(new CountDownLatch(pollerThreadCount));
// Initialize SSL if needed
initialiseSsl();
selectorPool.open();
}
至此,我們已將 Connector 的 init() 方法剖析完畢,接下來,讓我們將目光轉(zhuǎn)向 start() 方法。start() 方法的核心邏輯,僅僅是簡潔的一行代碼:調(diào)用 ProtocolHandler.start() 方法,將 Connector 的啟動大任委托給 ProtocolHandler。
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);
}
}
現(xiàn)在,讓我們深入 ProtocolHandler.start() 方法,探索啟動過程中的關鍵步驟。它首先會調(diào)用 Endpoint.start() 方法,啟動 Endpoint,以便監(jiān)聽來自網(wǎng)絡的請求。接著,它會開啟異步超時線程,負責監(jiān)控異步請求的超時情況。該線程的執(zhí)行單元為 AsyncTimeout。
@Override
public void start() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
}
// 1. 調(diào)用`Endpoint.start()`方法
endpoint.start();
// Start async timeout thread
// 2. 開啟異步超時線程,線程執(zhí)行單元為`Asynctimeout`
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();
}
現(xiàn)在,我們將注意力集中在 Endpoint.start() 方法,它負責啟動 Endpoint,為 Connector 迎接來自網(wǎng)絡的請求做好準備。
public final void start() throws Exception {
// 1. `bind()`已經(jīng)在`init()`中分析過了
if (bindState == BindState.UNBOUND) {
bind();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
// Create worker collection
// 2. 創(chuàng)建工作者線程池
if ( getExecutor() == null ) {
createExecutor();
}
// 3. 初始化連接latch,用于限制請求的并發(fā)量
initializeConnectionLatch();
// Start poller threads
// 4. 開啟poller線程。poller用于對接受者線程生產(chǎn)的消息(或事件)進行處理,poller最終調(diào)用的是Handler的代碼
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
// 5. 開啟acceptor線程
startAcceptorThreads();
}
}
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
}
在 Endpoint.start() 方法中,我們首先會調(diào)用 bind() 方法,完成 Socket 的綁定,確保 Connector 能夠監(jiān)聽來自網(wǎng)絡的請求。接著,我們會創(chuàng)建工作者線程池,為后續(xù)處理請求提供充足的線程資源。隨后,我們會初始化連接 latch,用于限制請求的并發(fā)量,避免過多的請求涌入,造成系統(tǒng)崩潰。
接下來,我們會創(chuàng)建一個輪詢 Poller 線程,負責處理來自 Acceptor 線程的事件,并將處理后的事件傳遞給 Handler。Poller 線程會調(diào)用 Handler 的代碼進行處理,最終完成對請求的處理。最后,我們會創(chuàng)建一個 Acceptor 線程,專門負責監(jiān)聽網(wǎng)絡請求,并將接收到的請求傳遞給 Poller 線程進行處理。
至此,我們已將 Connector 源碼入口的分析告一段落,揭開了 Connector 啟動過程的神秘面紗。接下來我們將繼續(xù)深入探索 Connector 的請求邏輯,深入理解 Connector 如何接收請求,如何將請求封裝成 Request 和 Response 對象,以及如何將這些對象傳遞給 Container 進行處理。讓我們一起探索 Tomcat 的內(nèi)部世界。