Tomcat4默認連接器
Tomcat的連接器(Connector)採用模塊化思想設計,作爲一個獨立的模塊與Servlet Container相連接。目前已經有多種連接器,像Coyote、mod_jk、mod_jk2、mod_webapp等,Tomcat4中的默認連接器目前已經被速度更快的Coyote代替。但是Tomcat4的默認連接器仍然是一個很好的學習工具,有助於理解Tomcat連接器的原理,所有的Tomcat連接器只需滿足一下三大:
1、 實現org.apache.catalina.Connector接口;
2、 創建實現了org.apache.catalina.Request接口的request對象;
3、 創建實現了org.apache.catalina.Response接口的response對象。
默認連接器會調用偵聽HTTP請求,創建request和response並通過調用org.apache.catalina.Container接口的invoke方法把request和response傳給Container:
public void invoke(org.apache.catalina.Request request, org.apache.catalina.Response response);
同時默認連接器提供了一個池用來緩存變量,並把很多String改成了char數組。
默認連接器全面支持HTTP1.1,同時兼任HTTP0.9和HTTP1.0.
HTTP1.1的新特性
1. 支持持久化TCP連接。在以前版本中一旦服務端收到請求相關資源,馬上就被關閉,但是一個請求頁面中可能會引用很多其它信息,如圖片等,如果每個資源都用一個連接會比較耗時,完全可以在一個連接裏面處理。HTTP1.1中增加了支持化連接,解決了上述問題。通過request header中指定connection:keep-alive實現
2. 支持塊編碼,分塊傳輸。
3. 傳輸一個繼續狀態(100)。
Connector接口
所有的Tomcat連接器都必須實現org.apache.catalina.Connector接口,其中提供了很多需要實現的方法,最重要的有getContainer、setContainer、createRequest和createResponse方法。
setContainer用來爲連接器分配一個servlet容器,getContainer獲取分配給連接器的容器,createRequest爲來自客戶端的請求創建一個request對象,createResponse爲請求創建響應(Response)。Connector的類圖如下:
其中Connector與Container的關係式一對一的,而HttpConnector與HttpProcessor對象是一對多的。
HttpConnector類
HttpConnector是Connector的實現類,即Tomcat默認的連接器類,作爲組件(Component)必須實現org.apache.catalina.Lifecycle,具有生命週期,要在Tomcat中創建一個連接器必須先調用它的init方法進行初始化,然後調用start方法啓動。
創建ServerSocket
在調用HttpConnector的啓動方法的時候,會創建一個ServerSocket對象,用來偵聽客戶端的請求。在創建ServerSocket對象的時候使用了工廠模式,提供了一個工廠接口ServerSocketFactory與工廠實現類DefaultServerSocketFactory,我先在這裏應該是爲了簡化ServerSocket創建的過程,便於處理多個端口的情況。
緩存HttpProcessor實例
爲了併發處理Request,提高服務器的處理效率,這裏用Stack提供了對HttpProcessor進行緩衝,同時通過minProcessors和maxProcessors來控制併發數,一旦請求超過maxProcessors將會被忽略。
爲HTTP Request服務
對於偵聽到的每一個HTTP請求(Socket),HttpConnector都會去從緩衝棧中獲取一個處理器HttpProcessor,並把獲取到的Socket分配剛剛獲取到的HttpProcessor。
HttpProcessor對象
Http 請求處理器是一個線程,不斷調用await方法獲取socket,一旦取到以後就調用process方法進行處理,處理完成以後會把當前處理器放入緩衝棧裏面。
run方法循環調用await獲取socket進行處理,處理完成以後把處理器放回緩衝棧:
public void run() {
// Process requests until we receive a shutdown signal
while (!stopped) {
// Wait for the next socket to be assigned
Socket socket = await();
if (socket == null)
continue;
// Process the request from this socket
try {
process(socket);
} catch (Throwable t) {
log("process.invoke", t);
}
// Finish up this request
connector.recycle(this);
}
// Tell threadStop() we have shut ourselves down successfully
synchronized (threadSync) {
threadSync.notifyAll();
}
}
await方法獲取socket,如果不能獲取調用wait方法把當前線程掛起:
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
Socket socket = this.socket;
available = false;
notifyAll();
if ((debug >= 1) && (socket != null))
log(" The incoming request has been awaited");
return (socket);
}
assign方法爲處理器分配需要處理的socket,並喚醒處理器中的線程處理socket:
synchronized void assign(Socket socket) {
// Wait for the Processor to get the previous Socket
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
notifyAll();
if ((debug >= 1) && (socket != null))
log(" An incoming request is being assigned");
}
Request對象
在默認連接器中Http請求用org.apache.catalina.Request接口表示具體類關係見下圖:
Response對象
Response接口及其相關類如下圖:
處理請求
一旦HttpProcessor組件中線程獲取到socket以後,會調用process方法對request請求進行處理,主要做下面三步:
1、 解析connection
2、 解析request
3、 解析request header
簡單servlet容器
要測試連接器還需要提供兩個類Bootstray和SimpleContainer,Bootstray用來啓動應用,SimpleContainer是servlet容器,實現org.ahache.catalina.Container接口,其中有一個關鍵方法inveke回去加載servlet並調用其的service方法:
public void invoke(Request request, Response response)
throws IOException, ServletException {
String servletName = ( (HttpServletRequest) request).getRequestURI();
servletName = servletName.substring(servletName.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(WEB_ROOT);
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
Class myClass = null;
try {
myClass = loader.loadClass(servletName);
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((HttpServletRequest) request, (HttpServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
}
Bootstray會構造連接器和容器,把容器分配給連接器,先調用連接器的init方法進行初始化,然後調用連接器的start方法啓動連接器,如下:
public final class Bootstrap {
public static void main(String[] args) {
HttpConnector connector = new HttpConnector();
SimpleContainer container = new SimpleContainer();
connector.setContainer(container);
try {
connector.initialize();
connector.start();
// make the application wait until we press any key.
System.in.read();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
運行應用
要運行應用,需要導入servlet-api.jar(servlet2.4以後)或servlet.jar(servlet2.3),同時在需要把PrimitiveServlet.java編譯後放在webroot下,在頁面瀏覽器中輸入
http://localhost:8080/servlet/PrimitiveServlet
將會在頁面輸出:
Hello.Roses are red. Violets are blue.