public void invoke(
org.apache.catalina.Request request,
org.apache.catalina.Response response);
}
在invoke方法里面,连接器加载servlet类,调用service方法,管理session,记录log错误信息等。
默认的连接器也一些优化策略是与第三章的连接器不一样的。首先提供了不同的对象池来避免创建对象的开销。第二,在很多地方使用字符数组代替字符串。
这章的应用程序的连接器可以和默认连接器联系起来。默认的连接器实现了HTTP1.1到HTTP 0.9和1.0的所有特性。本章会讲解HTTP1.1的新特性。
HTTP 1.1 New Features
Persistent Connections (持久连接)
在HTTP1.1之前,一个浏览器不管什么时候连接到web服务器,这个连接会在服务器响应了请求资源后立马关闭。但是,一个网页可以包含其他资源:图片文件,applet等。当一个页面被请求,浏览器也需要通过这个页面下载相关资源。(创建新的连接请求该网页的其他相关资源)如果一个页面和这个页面相关的所有资源的下载使用不同的链接,这种处理方式会变得很慢。这就是为什么HTTP1.1提出的持久连接。有了持久连接,当一个页面被下载,这个服务器不会立马关闭连接。它会等待web客户端请求所有与该页面相关的资源。这种方式,一个网页和该网页相关的资源可以使用同一个连接下载。这样对于服务器,客户端和网络来所都节省了许多工作和时间。因为建立和拆除连接是开销很大的操作。
持久连接是HTTP1.1默认的连接。也可以明确告诉浏览器发送头信息的connection值:
connection: keep-alive
Chunked Encoding(块编码)
建立一个持久连接的结果是:服务器可以通过多媒体资源发送字节流,客户端可以接收使用同样的链接接收者多媒体资源。结果,发送者必须在每一个request和response的头部信息中发送内容的长度以便接收者知道怎么来解释这些字节。但是,通常情况是发送者不知道究竟要发送多少字节。例如:一个servlet容器在当持有一些可以(available)使用的字节就可以开始发送response,而不是在等到所有的都准备好了之后再一起发送。这种方式,必须有一种方法可以告诉接收者在不能提前知道content-length头部信息的情况下怎么解释字节流。
就算没有多媒体资源请求或许多响应,一个服务器或客户端不必要它要发送知道多少数据。在HTTP1.0。一个服务器可以忽略content-length这个头部信息,保持等待连接。当完成了,它就关闭连接。这种情况下,客户端在读取到以-l作为到达文件结束的标识符之前一直保持读取的状态。
HTTP1.1有一个叫做transfer-encoding的特殊头部信息表明字节流将会以块状形式发送。对于每一个块:长度(16进制数表示)后面紧跟CR/LF且位于要发送的数据之前。一个事务被用一个0(zero)标示的长度块。假设你发送下面的38个字节在两个块里面。第一个块长度为29,第二个块长度为9
I'm as helpless as a kitten up a tree.
你是按下面的格式发送出去:
1D\r\n
I'm as helpless as a kitten u
9\r\n
p a tree.
0\r\n
1D,是29的十六进制标示数,表明第一块是由29个字节组成。0\r\n表明是事务的结束。
Use of the 100 (Continue) Status (使用100状态号)
HTTP1.1客户端可以在发送请求体之前发送Expect:100-continue头部信息到服务器,然后等待服务器的确认。这是很正常的,如果客户端打算发送一个长请求体,但是不确定服务器是否会接受。如果客户端发送长请求体到服务器仅仅只是为了确认服务器是绝接收长请求体本身就是浪费。
在接受到Expect: 100-continue头部信息,服务器如果可以处理请求,就回应一个100-continue再加上两对CRLF字符的头部信息。
HTTP/1.1 100 Continue
这服务器就可以继续读取输入流。
The Connector Interface
一个Tomcat连接器必须实现org.apache.catalina.Connector接口。这个接口中最重要的方法就是:getContainer, setContainer, createRequest和createResponse.
getContainer方法用来把连接器和容器联系起来。GetContainer方法返回被联系的容器。CreateRequest给到来的HTTP请求构建一个request对象,createReponse创建response对象。
org.apache.catalina.connector.http.HttpConnector是一个实现了Connector接口的类,它将在下一节讨论。
一个Connector和一个Container是一对一的关系。
The HttpConnector Class
你在第三章中的简单版本的org.apache.catalina.connector.http.HttpConnector已经知道这个类是怎么工作的。它实现了org.apache.catalina.Connector接口(更合适与Catalina工作),java.lang.Runnable接口(这样它的实例可以工作在自己的线程里面),org.apache.catalina.Lifecycle接口。Lifecycle接口用来维护每一个实现了该接口的Catalina组件的生命周期。
Lifecycle在第六章讲解。现在你只需要了解:通过实现Lifecycle接口,你创建了一个HttpConnector实例后,你可以调用它的initialize核start方法。两个方法在组件的生命期间都只必须调用一次。你可以看到和第三章中HttpConnector的不同的地方:HttpConnector怎么创建一个服务器socket,怎么维护一个HttpProcessor池和为HTTP请求服务。
Creating a Server Socket
HttpConnector的initialize方法调用一个private修饰的的open方法。Open方法返回一个java.net.ServerSocket实例,把它指配给serverSocket。但是,不是调用java.net.ServerSocket的构造函数,open方法从一个服务器socket工厂中获取一个ServerSocket实例。如果你想知道这个工厂的细节,读在org.apache.catalina.net package包中的ServerSocketFactory接口和DefaultServerSocketFactory类。
Maintaining HttpProcessor Instances
HttpConnector实例在同一时刻只有一个HttpProcessor实例,所以它在一个时刻只能处理一个HTTP请求。在默认连接器,HttpConnector有一个HttpProcessor池对象。每一个HttpProcessor实例都有自己的线程。所以HttpConnector可以在同一时刻为多个HTTP请求服务。
HttpConnector维护一个HttpProcessor实例池来避免一直创建HttpProcessor对象。HttpProcessor实例被存储在一个叫做processors的java.io.Stack里面:
如果createProcessor不返回null,客户端socket被传递给HttpProcessor类的assign方法:
processor.assign(socket);
现在就该HttpProcessor实例读取socket的输入流和解析HTTP请求。很重要的一点是。assign方法必须马上返回,不能等到HttpProcessor处理完解析工作才返回。这样才能让下一个来访的HTTP请求得到服务。每一个HttpProcessor实例有它自己的线程来解析,这也很容易办到。
The HttpProcessor Class
HttpProcessor类在默认连接器是一个完整的版本。你可以知道它是怎么工作,HttpProcessor类让它的assign方法实现异步,这样就可以让HttpConnector实例在同一时刻服务许多的HTTP请求。
注意:HttpProcessor类另外一个很重要的方法:process方法。将诶西HTTP请求和调用容器的invoke方法。
第三章,HttpConnector运行在自己的线程中。但是,它还必须等待当前处理的HTTP请求完成后才能处理下一个请求。下面是第三章中的run方法:
public void run() {
...
while (!stopped) {
Socket socket = null;
try {
socket = serversocket.accept();
} catch (Exception e) {
continue;
}
// Hand this socket off to an Httpprocessor
HttpProcessor processor = new Httpprocessor(this);
processor.process(socket);
}
}
Listing 4.1: The HttpProcessor class's run method.
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();
}
}
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();
...
}
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);
}
所有方法的程序流在下面总结出来
The processor thread (the await method) The connector thread (the assign method)
while (!available) { while (available) {
wait(); wait();
} }
Socket socket = this.socket; this.socket = socket;
available = false; available = true;
notifyAll(); notifyAll();
...
return socket;
// to the run
// method