概述
Tomcat雖然具有傳統Web服務器(如Apache)的功能(處理Html頁面,css文件等靜態資源的能力),但更多的,Tomcat以Servlet容器著稱(處理Jsp和Servlet等動態資源的應用服務器)。由Tomcat的總體架構可知(參見:Tomcat6.x架構概述),Servlet容器由2個主要組件構成:Connector(連接器)和Container(容器)。Connector負責接收客戶端的請求,而Container處理並響應該請求。
由JavaEE規範可知,Servlet容器內部只處理HTTP協議的請求,但是對於連接器的設計來說,它可以接收任何協議(如HTTP,AJP等)的請求,因此,在連接器接收到客戶端的請求(Socket)後,需要將該請求包裝成容器可以處理的對象,然後再傳遞給容器處理,同時,連接器也要創建一個響應對象一併傳給容器,好讓容器響應請求。如此一來,容器就與具體傳輸協議解耦了,而這正是Connector架構所要達到的目的。Connector架構
基於上面的分析,我們可以將連接器和容器分別抽象爲Connector和Container接口,將請求和響應抽象爲Request和Response接口。
Connector接口中的重要方法有:
public Request createRequest() ;
public Response createResponse();
public Container getContainer();
Tomcat6.x的設計
下面我們看看Tomcat6.x是如何設計的。我們知道,Tomcat的Servlet容器有個好聽的名字叫Catalina,在其內部,Catalina將容器抽象爲org.apache.catalina.Container接口,將連接器抽象爲org.apache.catalina.connector.Connector類,將請求和響應抽象爲org.apache.catalina.connector.Request和org.apache.catalina.connector.Response類。
org.apache.catalina.connector.Connector類也有以下方法:
public Request createRequest() ;
public Response createResponse();
public Container getContainer();
Connector接收到客戶端的Socket請求,調用createRequest方法創建請求對象(org.apache.catalina.connector.Request),調用createResponse方法創建響應對象(org.apache.catalina.connector.Response),然後調用getContainer方法獲得容器,最後傳遞Request對象和Response對象並調用Container的invoke方法處理請求。
Connector支持的協議
對於Apache(或其它Web服務器)來說,它可以加載用C語言寫的Apache模塊(mod_jk , mod_jk2 ,或mod_proxy)作爲連接器(Connector),這些連接器(Connector)的工作原理一致,可以根據配置支持不同協議的連接器。對於Tomcat來說,其Servlet容器也有用java語言實現的連接器(Connector)模塊,這些模塊的工作原理也應該一致並且可以根據配置支持不同的協議。爲了實現連接器處理工作的一致性和協議的可配置、可擴展性,在Tomcat6.x中,將一致性的工作抽象到org.apache.catalina.connector.Connector類,而將具體協議的差異抽象到org.apache.coyote.ProtocolHandler接口之中。Tomcat6.x通過在配置文件中設置不同的協議類型來初始化不同的ProtocolHandler實例,從而實現協議的可配置、可擴展性。
在Tomcat6.x中,默認支持2種協議類型的連接器:HTTP/HTTPS協議的HTTP/1.1和AJP協議的AJP/1.3。Tomcat6.x使用Coyote模塊來實現Connector框架,因此這些協議處理器(ProtocolHandler)在包org.apache.coyote下可以找到。
注:Coyote是Connector框架實現的名字,在該模塊下提供了2種協議類型的ProtocolHandler。
如果想擴展連接器,可以在server.xml文件中指定協議和協議處理器的類名。如下:
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
然而,HTTP協議和AJP協議幾乎符合所有的需求,因此,我們很少會擴展連接器。
在構造連接器時,連接器根據是否支持Apache Portable Runtime (APR),選擇不同的類支持HTTP協議和AJP協議,其對應關係如下:
l 支持APR
HTTP/1.1協議對應org.apache.coyote.http11.Http11AprProtocol類
AJP/1.3協議對應org.apache.coyote.ajp.AjpAprProtocol類
l 不支持APR
HTTP/1.1協議對應org.apache.coyote.http11.Http11Protocol類
AJP/1.3協議對應org.apache.jk.server.JkCoyoteHandler類
注:APR(Apache portable Run-time libraries,Apache可移植運行庫)的目的如其名稱一樣,主要爲上層的應用程序提供一個可以跨越多操作系統平臺使用的底層支持接口庫。在早期 的Apache版本中,應用程序本身必須能夠處理各種具體操作系統平臺的細節,並針對不同的平臺調用不同的處理函數。啓用APR將大大提升Tomcat對靜態文件的處理性能。
下面看看如何構建不同的連接器:
public Connector(String protocol) throws Exception {
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));
}
}
public void setProtocol(String protocol) {
// Test APR support
initializeAPR();
if (aprInitialized) {
if ("HTTP/1.1".equals(protocol)) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
} else if ("AJP/1.3".equals(protocol)) {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
} else {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
}
} else {
if ("HTTP/1.1".equals(protocol)) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol");
} else if ("AJP/1.3".equals(protocol)) {
setProtocolHandlerClassName("org.apache.jk.server.JkCoyoteHandler");
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
}
}
}
Connector構造函數接收一字符串參數protocol,並根據該字符串加載不同的ProtocolHandler。
ProtocolHandler
每個ProtocolHandler代表着一種協議的支持,如Tomcat6.x默認支持的協議http1.1和ajp,相應的處理器爲:Http11AprProtocol,AjpAprProtocol,Http11Protocol,AjpProtocol。ProtocolHandler的職責是,接收客戶端Socket請求,處理Socket請求,將請求包裝之後傳給容器來處理。
Coyote架構下,每個ProtocolHandler使用相應的端點類XXXEndpoint(如org.apache.tomcat.util.net.AprEndpoint, org.apache.tomcat.util.net.NioEndpoint,org.apache.tomcat.util.net.JIoEndpoint)監聽指定端口並接收Socket請求。在請求傳給容器之前,我們需要對請求做一些處理(如是否超時,是否保持連接等),而這些工作都是交給具體協議處理器(ProtocolHandler)的相關類來實現,最後由Adapter包裝請求並調用容器的invoke方法。
ProtocolHandler相關類
爲了實現協議處理器的相關功能,Coyote按功能職責抽象了協議處理器的相關類:
l XXXEndpoint,監聽指定端口,接收Socket請求
l Handler,管理具體協議的Socket請求連接
l Processor,真正的具體協議處理器
l Adapter,包裝請求,並調用容器的invoke方法(準確的說是Pipeline的第一個Valve)
在ProtocolHandler相關類的配合下,其處理請求的過程如下:
根據支持的協議,通常ProtocolHandler包含一個實現相應協議的Handler實例和XXXEndpoint實例,Handler實例接收XXXEndpoint 獲得的Socket對象,然後創建相應協議的Processor實例,並將Socket請求交給它來處理,最後Processor調用Adapter的service方法,該方法把請求包裝後調用容器的invoke方法。
Adapter的service方法簽名如下:
public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res)
通過以上的分析,我們發現,Adapter是所有請求進入容器的一個閥。在Tomcat的設計中,閥的使用無處不在。
示例分析
下面具體分析一下Tomcat6.x是如何支持HTTP/1.1協議的連接器。創建測試代碼如下。
代碼片段1,創建支持HTTP/1.1的連接器並啓動:
public static void main(String[] args) {
try {
Connector connector = new Connector("HTTP/1.1");
connector.setPort(8080);
connector.setContainer(new SimpleContainer());
connector.start();
Thread.sleep(3000000);
} catch (Exception e) {
e.printStackTrace();
}
}
代碼片段2,SimpleContainer的實現,省略了部分沒有實現的代碼:
public class SimpleContainer implements Container {
private Container container;
public SimpleContainer() {
this.container = this;
}
public SimpleContainer(Container container) {
this.container = container;
}
@Override
public Pipeline getPipeline() {
// TODO Auto-generated method stub
return new Pipeline() {
@Override
public Valve getFirst() {
return new Valve() {
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
container.invoke(request, response);
}
};
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
response.setContentType("text/html;charset=gbk");
PrintWriter w = response.getWriter();
Class c = request.getConnector().
getProtocolHandler().getClass();
w.print("當前連接器支持的協議爲:" +
request.getConnector().getProtocol());
w.print("<br>");
w.print("當前連接器的類型爲:" + c.getName());
w.print("<br>");
w.print("容器方法[invoke]被調用");
}
}
執行該程序,在地址欄輸入:http://localhost:8080/index.html,你將看到如下畫面:
Connector接收請求的過程如下:
Accepter線程接到客戶端的Socket請求後,調用JIoEndpoint的processSocket方法分發請求給worker線程來處理。
Connector處理請求的過程如下:
worker線程接收到Socket請求後,將請求委託給Http11ConnectionHandler處理。Http11ConnectionHandler創建Http11Processor並調用其process方法,該方法解析協議內容幷包裝請求信息爲org.apache.coyote.Request和org.apache.coyote.Response,之後調用CoyoteAdapter的service方法。
Connector傳遞請求的過程如下:
CoyoteAdapter的service方法內部,將org.apache.coyote.Request和org.apache.coyote.Response轉換爲org.apache.catalina.connector.Request和org.apache.catalina.connector.Response。然後傳遞請求給容器。