Tomcat7.0原始碼分析——請求原理分析(上)
前言
談起Tomcat的誕生,最早可以追溯到1995年。近20年來,Tomcat始終是使用最廣泛的Web伺服器,由於其使用Java語言開發,所以廣為Java程式設計師所熟悉。很多早期的J2EE專案,由程式設計師自己實現Jsp頁面或者Servlet接受請求,後來藉助Struts1、Struts2、Spring等中介軟體後,實際也是利用Filter或者Servlet處理請求,大家肯定要問了,這些Servlet處理的請求來自哪裡?Tomcat作為Web伺服器是怎樣將HTTP請求交給Servlet的呢?
本文就Tomcat對HTTP的請求處理細節進行分析。
Connector的初始化
根據《Tomcat7.0原始碼分析——生命週期管理》一文的內容,我們知道Tomcat中有很多容器,包括Server、Service、Connector等。其中Connector正是與HTTP請求處理相關的容器。Service是Server的子容器,而Connector又是Service的子容器。那麼這三個容器的初始化順序為:Server->Service->Connector。Connector的實現分為以下幾種:
- Http Connector:基於HTTP協議,負責建立HTTP連線。它又分為BIO Http Connector(是Tomcat的預設Connector)與NIO Http Connector兩種,後者提供對非阻塞IO與長連線Comet的支援。
- AJP Connector:基於AJP協議,AJP是專門設計用於Tomcat與HTTP伺服器通訊定製的協議,能提供較高的通訊速度和效率。如與Apache伺服器整合時,採用這個協議。
- APR HTTP Connector:用C實現,通過JNI呼叫的。主要提升對靜態資源(如HTML、圖片、CSS、JS等)的訪問效能。現在這個庫已獨立出來可用在任何專案中。APR效能較前兩類有很大提升。
程式碼清單1
- @Override
-
protectedvoid initInternal()
- super.initInternal();
- // Initialize adapter
- adapter = new CoyoteAdapter(this);
- protocolHandler.setAdapter(adapter);
- IntrospectionUtils.setProperty(protocolHandler, "jkHome",
- System.getProperty("catalina.base"));
- onameProtocolHandler = register(protocolHandler,
- createObjectNameKeyProperties("ProtocolHandler"));
- mapperListener.setDomain(getDomain());
- onameMapper = register(mapperListener,
- createObjectNameKeyProperties("Mapper"));
- }
程式碼清單1說明了Connector的初始化步驟如下:
步驟一 構造網路協議處理的CoyoteAdapter
程式碼清單1構造了CoyoteAdapter物件,並且將其設定為ProtocolHandler的Adapter。ProtocolHandler是做什麼的呢?Tomcat處理HTTP請求,需要有一個ServerSocket監聽網路埠來完成任務。介面ProtocolHandler被設計成控制網路埠監聽元件執行,負責元件的生命週期控制,這個介面實際並沒有定義網路埠監聽功能的規範,而是用於負責維護元件的生命週期。從ProtocolHandler的名字來看,它應該是網路協議的處理者,但它實際不負責這個功能,而是將其交給org.apache.coyote.Adapter來完成,這麼設計估計是為了方便維護和拓展新功能。Http11Protocol是ProtocolHandler介面的一個實現(是Connector的預設處理協議),被設計用來處理HTTP1.1網路協議的請求,通過該類可以完成在某個網路埠上面的監聽,同時以HTTP1.1的協議來解析請求內容,然後將請求傳遞到Connector所寄居的Container容器pipeline流水工作線上處理。此處的ProtocolHandler是何時生成的呢?還記得《Tomcat7.0原始碼分析——SERVER.XML檔案的載入與解析》一文中的Digester和Rule嗎?Digester在解析到標籤的時候,會執行startElement方法,startElement中會呼叫Rule的begin(String namespace, String name, Attributes attributes)方法,Connector對應的Rule就包括了ConnectorCreateRule。ConnectorCreateRule的begin方法的實現見程式碼清單2。
程式碼清單2
- @Override
- publicvoid begin(String namespace, String name, Attributes attributes)
- throws Exception {
- Service svc = (Service)digester.peek();
- Executor ex = null;
- if ( attributes.getValue("executor")!=null ) {
- ex = svc.getExecutor(attributes.getValue("executor"));
- }
- Connector con = new Connector(attributes.getValue("protocol"));
- if ( ex != null ) _setExecutor(con,ex);
- digester.push(con);
- }
- <Connectorport="8080"protocol="HTTP/1.1"
- connectionTimeout="20000"
- redirectPort="8443"/>
- <!-- Define an AJP 1.3 Connector on port 8009 -->
- <Connectorport="8009"protocol="AJP/1.3"redirectPort="8443"/>
從server.xml可以看到兩個Connector都有屬性protocol。
Connector的構造器實現,見程式碼清單3。
程式碼清單3
- public Connector(String protocol) {
- setProtocol(protocol);
- // Instantiate protocol handler
- try {
- Class<?> clazz = Class.forName(protocolHandlerClassName);
- this.protocolHandler = (ProtocolHandler) clazz.newInstance();
- } catch (Exception e) {
- log.error
- (sm.getString
- ("coyoteConnector.protocolHandlerInstantiationFailed", e));
- }
- }
setProtocol方法(見程式碼清單4)根據protocol引數的不同,呼叫setProtocolHandlerClassName方法(見程式碼清單5)設定protocolHandlerClassName屬性。以HTTP/1.1為例,由於預設情況下Apr不可用,所以protocolHandlerClassName會被設定為org.apache.coyote.http11.Http11Protocol,那麼反射生成的protocolHandler就是Http11Protocol例項。Tomcat預設還會配置協議是AJP/1.3的Connector,那麼此Connector的protocolHandler就是org.apache.coyote.ajp.AjpProtocol。
程式碼清單4
- /**
- * Set the Coyote protocol which will be used by the connector.
- *
- * @param protocol The Coyote protocol name
- */
- publicvoid setProtocol(String protocol) {
- if (AprLifecycleListener.isAprAvailable()) {
- if ("HTTP/1.1".equals(protocol)) {
- setProtocolHandlerClassName
- ("org.apache.coyote.http11.Http11AprProtocol");
- } elseif ("AJP/1.3".equals(protocol)) {
- setProtocolHandlerClassName
- ("org.apache.coyote.ajp.AjpAprProtocol");
- } elseif (protocol != null) {
- setProtocolHandlerClassName(protocol);
- } else {
- setProtocolHandlerClassName
- ("org.apache.coyote.http11.Http11AprProtocol");
- }
- } else {
- if ("HTTP/1.1".equals(protocol)) {
- setProtocolHandlerClassName
- ("org.apache.coyote.http11.Http11Protocol");
- } elseif ("AJP/1.3".equals(protocol)) {
- setProtocolHandlerClassName
- ("org.apache.coyote.ajp.AjpProtocol");
- } elseif (protocol != null) {
- setProtocolHandlerClassName(protocol);
- }
- }
- }
程式碼清單5
- publicvoid setProtocolHandlerClassName(String protocolHandlerClassName) {
- this.protocolHandlerClassName = protocolHandlerClassName;
- }
除此之外,ProtocolHandler還有其它實現,如圖1所示。
圖1 ProtocolHandler類繼承體系
圖1中有關ProtocolHandler的實現類都在org.apache.coyote包中 。前面所說的BIO Http Connector實際就是Http11Protocol,NIO Http Connector實際就是Http11NioProtocol,AJP Connector包括AjpProtocol和AjpAprProtocol,APR HTTP Connector包括AjpAprProtocol、Http11AprProtocol,此外還有一個MemoryProtocolHandler(這個是做什麼的,目前沒搞清楚,有知道的同學告訴我下啊!)。
步驟二 將ProtocolHandler、MapperListener註冊到JMX
BIO Http Connector的ProtocolHandler(即Http11Protocol)的JMX註冊名為Catalina:type=ProtocolHandler,port=8080。BIO Http Connector的MapperListener的註冊名為Catalina:type=Mapper,port=8080。AJP Connector的ProtocolHandler(即AjpProtocol)的JMX註冊名為Catalina:type=ProtocolHandler,port=8009。AJP Connector的MapperListener的註冊名為Catalina:type=Mapper,port=8009。有關Tomcat中JMX註冊的內容,請閱讀《Tomcat7.0原始碼分析——生命週期管理》一文。
Connector的啟動
根據《Tomcat7.0原始碼分析——生命週期管理》一文的內容,我們知道Tomcat中有很多容器。ProtocolHandler的初始化稍微有些特殊,Server、Service、Connector這三個容器的初始化順序為:Server->Service->Connector。值得注意的是,ProtocolHandler作為Connector的子容器,其初始化過程並不是由Connector的initInternal方法呼叫的,而是與啟動過程一道被Connector的startInternal方法所呼叫。由於本文的目的是分析請求,所以直接從Connector的startInternal方法(見程式碼清單6)開始。
程式碼清單6