第四章:Tomcat 默认的Connector
概览
第三章中的connector运行的很好,并且已经出色的完成了很多任务。但是,它被设计只是作为一个教学工具,只是对Tomcat默认connector的一个入门。理解第三章的connector是理解Tomcat4默认connector的关键。第四章将讨论通过剖析Tomcat4的默认的connector走向构建一个真正的Tomcat connector。
注:本章的默认connector参考Tomcat 4 的默认connector。虽然这个默认的connector现在是遭反对的,而其已经被一个更高效的connector Coyote所取代,但是它仍是一个非常好的学习工具。
一个Tomcat的connector是一个独立的模块,它可以被插入到一个servlet Container中。现在已经存在很多的connector,例如coyote,mod_jk,mod_jk2及mod_webapp。一个Tomcat connector一定要满足如下的必要条件:
1. 它必须实现org.apache.catalina.Connector接口
2. 它必须创建request对象,该类实现了org.apache.catalina.Request 接口
3. 它必须创建response对象,该类实现了org.apache.catalina.Response接口
Tomcat 4的默认connector的工作机制类似于第三章的简单connector。它等待HTTP 请求,创建request和response对象,并传给Container。一个connector传递request和response对象给Container通过调用org.apache.catalina.Container接口的invoke方法,它的声明如下:
public void invoke(org.apache.catalina.Request request,
org.apache.catalina.Response response);
在invoke方法内部,Container加载servlet类,调用它的service方法吗,管理session,日志错误信息等。
默认connector也使用了一些第三章的connector没使用的最佳化的东西。其一是提供了一个变量池以消除对象创建的开销。其二,很多地方使用了char数组来代替String。
本章应用程序中使用的是一个简单Container,它将与默认connector关联。但是,这章的重点不是这个简单Container而是默认connector。Container将在第五章讨论。然而,简单Container将在本章末尾“简单Container 应用程序”部分讨论,以说明怎样应用默认connector。
另一个需要注意的重点是默认connector实现了所有HTTP1.1的新特性,也可以服务于HTTP0.9和HTTP1.0客户端。要理解HTTP1.1的所有新特性,你首先需要理解这些特性,我们将在本章的第一章节做出解释。其后,我们将讨论the org.apache.catalina.Connector,接口和如何创建request和response对象。如果你理解了第三章的connector是如何工作的,你会发现理解默认connector也不是问题。
本章从HTTP1.1的三大新特性开始。理解了他们对于理解默认connector的内部工作机制是至关重要的。然后,介绍org.apache.catalina.Connector,这个接口是所有connector都必须实现的。然后你会发现你在第三章遇到的类,如HttpConnector,HttpProcessor等。但是他们比第三章的类更优化。
HTTP 1.1新特性
这部分解释HTTP1.1的三大新特性。理解他们对于理解默认connector如何处理HTTP请求至关重要。
持久化连接
在HTTP1.1以前,只要浏览器连接到一个web服务器,在请求资源被发送完毕后,连接就会被服务器关闭。然而,一个Internet页面可以包含其他资源,像是图片文件,java程序等。因此,当一个页面被请求时,浏览器也需要下载与这个页面关联的这些资源。如果页面和它所关联的所有资源使用不同的连接被下载,那么这个处理将是非常慢的。那就是为什么HTTP1.1引入持久化连接。有了持久化连接,当一个页面被下载时,服务器不直接关闭连接,而是等待web客户端请求所有关于这个页面的资源。这样,这个页面和它的关联资源就可以使用相同的连接被下载了。这为web服务器、客户端和网络节省了很多工作和时间,想想看建立和断开 HTTP连接是多么费力的操作。
持久化连接时HTTP1.1的默认连接方式。同样,为了使它更明晰,浏览器可以发送request header “connection”,它的值是“keep-alive”:。
connection: keep-alive
分块编码
建立持久化连接的结果是服务器可以发送多种资源的字节流,并且客户端可以使用同一连接发送多个请求。结果,发送方必须发送每个request header或者response的内容的长度,以便接收者可以知道怎样解释这些字节。然而通常情况是发送者不知道将发送多少个字节。例如,一个servlet Container当有一些字节可用并且不等到所有都准备好时就开始发送response。这意味着,必须有一个方法告诉接收者在content-length header不能提早知道的情况下如何解释字节流。
不是必须发送多个request或多个response,服务器或客户端就没有必要知道发送了多少数据。在HTTP1.0中,服务器不考虑content-length header并持续向连接写数据。完成后,就简单的关掉连接。这种情况下,客户端必须保持读状态直到返回表示到达文件末尾的-1为止。
HTTP1.1使用一种特殊的header,叫transfer-encoding,用于表示在块中将被发送的字节流。对于每一个块,跟在CR/LF之后的长度(16进制)优先于数据被发送。一个事务是被标记为长度为0的块。假设你想要发送两个块中的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的16进制,表示第一个块包括29个字节,0\r\n表示事务的结束。
使用100(continue)status
HTTP1.1客户端可能发送:100-continue header 到服务器,它在发送request body并等待来自服务器的确认之前发送。这通常发生在客户端要发送很长的request body但又不确定服务器是否会接收的情况下。如果客户端发送很长的body只是为了确认服务器是否拒绝接收是一种浪费的做法。
收到100-continue header,如果服务器乐意或可以处理这个请求,服务器将以如下的100-continue header作为响应,接着是两对CRLF(还记得吗,第一章解释过,CRLF就是空白行) 字符
HTTP/1.1 100 Continue
服务器会继续读input stream。
Connector 接口
Tomcat connector必须实现org.apache.catalina.Connector接口。这个接口中所有方法中最重要的是getContainer, setContainer, createRequest和createResponse。
setContainer用来关联该connector和一个Container。getContainer方法返回与之关联的Container。createRequest方法为HTTP请求创建一个request对象,createResponse方法创建一个response对象。
org.apache.catalina.connector.http.HttpConnector类是connector接口的一个实现类,我们在下一部分“HttpConnector 类”讨论。现在,让我们仔细看一下图4.1,它是默认connector的UML类图。注意为了使图简单化,request和response接口的实现已经被省略了。除了SimpleContainer类,org.apache.catalina前缀也被从类型名中省略了。
图4.1 默认connector类图
因此,connector必须读取org.apache.catalina.Connector、util.StringManager、
org.apache.catalina.util.StringManager等。
Connector与Container是一对一的关系。实线箭头表示出了这种关系,揭示出connector知道Container不是其他方法。再看一下,不像第三章,HttpConnector和HttpProcessor是一对多的关系。
HttpConnector类
你已经知道了该类是如何工作的了,因为在第三章,解释了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如何创建一个server socket,如何维护HttpProcessor池,和如何为HTTP请求服务。
创建server socket
HttpConnector的initialize方法调用私有方法open,返回一个java.net.ServerSocket 实例并赋值给serverSocket。open方法从一个socket服务器工厂获取一个ServerSocket实例代替调用java.net.ServerSocket 构造方法。如果你想要知道这个工厂类的详细内容,读一下org.apache.catalina.net包下的ServerSocketFactory接口和DefaultServerSocketFactory类。它们很容易理解。
维护HttpProcessor实例
在第三章中,HttpConnector实例同一时间仅有一个HttpProcessor实例,因此同一时间它只能处理一个HTTP请求。在默认connector中,HttpConnector有一个HttpProcessor对象池并且每个HttpProcessor都有一个自己的线程。因此,HttpConnector可以同时服务于多个HTTP请求。
HttpConnector维护一个HttpProcessor实例池以避免总是创建HttpProcessor对象。HttpProcessor实例储存在一个叫做processors的java.io.Stack中。
private Stack processors = new Stack();
在HttpConnector中,创建的HttpProcessor实例的数量决定于两个变量:minProcessors 和 maxProcessors。默认情况下,minProcessors为5,maxProcessors为20,但是你可以通过setMinProcessors 和 setMaxProcessors方法更改它们的值。
protected int minProcessors = 5;
private int maxProcessors = 20;
最初,HttpConnector对象创建minProcessors个HttpProcessor实例。如果同一时间有多于HttpProcessor实例个数的请求,HttpConnector创建更多的HttpProcessor实例直到数量达到maxProcessors为止。在达到这个数量并且仍旧没有足够的HttpProcessor实例后,接下来的HTTP请求将被忽略。如果你想让HttpConnector持续创建HttpProcessor实例,把maxProcessors设置成负数即可。另外,curProcessors变量记录了当前的HttpProcessor实例的个数。
下面是HttpConnector类的start方法中创建初始化数量个的HttpProcessor实例的代码。
while (curProcessors < minProcessors) {
if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
break;
HttpProcessor processor = newProcessor();
recycle(processor);
}
newProcessor方法创建一个新的HttpProcessor对象,并增加curProcessors。Recycle方法将HttpProcessor放回到stack中。
每个HttpProcessor实例都要负责解析HTTP request line 和header并组装成一个request对象。因此,每个实例关联一个request对象和一个response对象。HttpProcessor类的构造方法包含了调用HttpConnector类的createRequest和createResponse方法。
服务于HTTP 请求
HttpConnector类的主要逻辑在他的run方法中,类似第三章。run方法包含一个while循环,Server socket等待HTTP请求直到HttpConnector被停掉。
while (!stopped) {
Socket socket = null;
try {
socket = serverSocket.accept();
...
对于每一个HTTP请求,通过调用私有的createProcessor方法获取一个HttpProcessor实例。
HttpProcessor processor = createProcessor();
但是,大多数情况下createProcessor方法并不创建一个新的HttpProcessor对象,而是,从池中取出一个。如果在stack中仍有HttpProcessor,createProcessor就取出一个。如果stack是空的并且没有超过maximum个HttpProcessor实例,createProcessor就会创建一个。但是,如果已经达到了maximum个,createProcessor将返回null。如果发生这种情况,socket将被关闭,并且HTTP请求将不会被处理。
if (processor == null) {
try {
log(sm.getString("httpConnector.noProcessor"));
socket.close();
}
...
continue;
如果createProcessor不返回null,客户端socket被传给HttpProcessor类的assign方法。
processor.assign(socket);
现在,HttpProcessor实例的工作就是读socket的input stream并解析HTTP请求。要非常注意的是,assign方法必须直接返回而不是等到HttpProcessor完成解析之后,因此,下一个到来的HTTP请求可以被服务。因为每个HttpProcessor实例都有一个自己的用于解析的线程,这个不难完成。你将在下一节“HttpProcessor类”知道这是如何完成的。
HttpProcessor类
默认connector中的HttpProcessor类是一个完整的版本。你已经学会了它是如何工作的,这章我们对了解HttpProcessor类如何使他的assign方法异步而使HttpConnector实例可以同时服务于多个HTTP请求更感兴趣。
注:HttpProcessor的另外一个重要的方法是私有的process方法,它用于解析HTTP请求并调用Container的invoke方法。我们将在“处理request”章节中解释。
在第三章中,HttpConnector在它自己的线程中运行。但是,它必须等待当前的HTTP请求被处理完成,才能处理下一个请求。下面是第三章HttpConnector类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);
}
}
第三章中的HttpProcessor类的process方法是同步的。因此,他的run方法等待process方法完成才能接受另一个请求。
在默认的connector中,HttpProcessor类实现了java.lang.Runnable接口,并且HttpProcessor的每个实例都运行在自己的线程中,我们叫它为“processor thread”。对于HttpConnector创建的每一个HttpProcessor实例,其start方法都被调用,高效地启动HttpProcessor实例的“processor thread”,清单4.1展示了默认connector中的HttpProcessor的run方法。
Listing 4.1: HttpProcessor 类的 run 方法
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();
}
}
run方法中的while循环以这样的顺序持续运行:获取一个socket,处理它,调用connector的recycle方法将当前的HttpProcessor实例放回到stack中。下面是HttpConnector类的recycle方法:
void recycle(HttpProcessor processor) {
processors.push(processor);
}
注意,run方法中的while循环在await方法处停止。await方法持有“processor thread”的控制流直到他从HttpConnector获得一个新的socket。换句话说,直到HttpConnector调用HttpProcessor实例的assign方法。然而,await方法与assign方法运行在不同的线程上。assign方法是在HttpConnector的run方法中被调用的。我们把HttpConnector实例的run方法上涉及的线程叫做“connector thread”。assign方法是如何告知await方法它已经被调用了呢?通过使用一个叫available的布尔型变量,和java.lang.Object的wait及notifyAll方法。
注:wait方法导致当前线程处于等待状态知道另一个线程调用了这个对象的notify或者notifyAll方法。
下面是HttpProcessor类的assign方法和await方法:
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);
}
表4.1总结了各个方法的程序流程。
表 4.1: await 和 assign 方法总结
processor thread (await 方法) connector thread ( assign 方法)
while (!available) {
wait();
}
Socket socket = this.socket;
available = false;
notifyAll();
return socket; // to the run
// method
while (available) {
wait();
}
this.socket = socket;
available = true;
notifyAll();
...
起初,“processor thread”刚刚开始,available为false,因此thread在while循环中等待(看表4.1的第一列)。直到有另外一个线程调用了notify或notifyAll方法。这就是说,调用await方法将引起“processor thread”的暂停,直到“connector thread”为HttpProcessor实例调用notifyAll方法。
现在,在看看表的第二列。当一个新的socket被指派后,“connector thread”调用HttpProcessor的assign方法。available的值是false,因此while循环被跳过,并且socket被赋值给HttpProcessor实例的socket变量:
this.socket = socket;
然后“connector thread”将available设置成true并调用notifyAll。这将激活processor线程且此时available的值是true,因此程序跳过while循环:将socket实例赋值给一个局部变量,并将available设置成false,同时调用notifyAll,并返回该socket,这将最终引起socket被处理。
为什么await方法要使用一个本局部变量(socket)而且不返回这个socket实例呢?这是为了在当前socket没有被完全处理完之前,这个socket实例可以被赋值给下一个socket。
为什么await方法需要调用notifyAll呢?这是以防当available值为true时另一个socket到达。这种情况下,“connector thread”将在assign方法的while循环内停止直到接到“processor thread”调用notifyAll。
Request对象
默认connector中HTTP request对象由org.apache.catalina.Request接口展示。这个接口直接被RequestBase类实现,RequestBase是HtttpRequestBase的父类。最终的实现是HttpRequestImpl,它继承了HttpRequestBase。类似第三章,也有façade类:RequestFacade和HttpRequestFacade。Request接口及其实现类的UML类图在图4.2中给出。除类型属于javax.servlet和javax.servlet.http包外,org.apache.catalina前缀被省略了。
图4.2 Request接口及关联类型
如果你理解了第三章中的Request对象,那么理解这张图就没什么问题。
Response对象
Response接口及其实现类的UML类图在图4.3中给出。
图4.3:response接口及其实现类
处理Request
现在,你已经理解了Request和response对象以及HttpConnector对象是如何创建它们的。现在只剩下处理了。在这部分,我们集中于HttpProcessor类的process方法,它是在一个socket指派给该HttpProcessor之后被HttpProcessor类的run方法调用的。process方法做如下操作:
解析connection
解析request
解析headers
每个操作都将在解释完process方法后在本节的子章节讨论。
process方法使用布尔型变量ok来表明在处理过程中没有错误,布尔型变量finishResponse来表明Response接口的finishResponse方法应该被调用。
boolean ok = true;
boolean finishResponse = true;
另外,process方法还使用布尔型变量keepAlive,stopped。Http11.keepAlive表明该连接是持久化的,stopped表示HttpProcessor实例已经被connector停掉了,因此process方法也应停止,http11表示HTTP请求来自支持HTTP1.1的web客户端。
类似于第三章,SocketInputStream实例用来包装socket的输入流。注意,SocketInputStream的构造方法中要传一个buffer大小,这个大小是从connector得到的,而不是从HttpProcessor类的局部变量。这是因为HttpProcessor是无法被默认connector的用户访问的。通过放一个buffer的大小在connector接口中,这就允许任何人使用该connector来设置buffer的大小。
SocketInputStream input = null;
OutputStream output = null;
// Construct and initialize the objects we will need
try {
input = new SocketInputStream(socket.getInputstream(),
connector.getBufferSize());
}
catch (Exception e) {
ok = false;
}
然后,有一个while循环持续读取输入流直到HttpProcessor被停掉,或者抛出了一个异常,或者连接被关闭了。
keepAlive = true;
while (!stopped && ok && keepAlive) {
...
}
在while循环内部,process方法从设置finishResponse为true开始,并获取输出流,且做一些Request和Response对象的初始化工作。
finishResponse = true;
try {
request.setStream(input);
request.setResponse(response);
output = socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse) response.getResponse()).setHeader
("Server", SERVER_INFO);
}
catch (Exception e) {
log("process.create", e); //logging is discussed in Chapter 7
ok = false;
}
然后,process方法通过调用parseConnection,parseRequest,parseHeader方法开始解析HTTP请求,以上方法将在本节的子章节讨论。
try {
if (ok) {
parseConnection(socket);
parseRequest(input, output);
if (!request.getRequest().getProtocol() .startsWith("HTTP/0"))
parseHeaders(input);
parseConnection 方法获取协议的值,可以是HTTP0.9,HTTP1.0或HTTP1.1.如果协议时HTTP1.0,布尔型变量keepAlive被设置成false,因为HTTP1.0不支持持久连接。如果在HTTP请求中发现了100-continue headers,parseHeader方法将设置布尔型变量sendAck为true。
如果协议时HTTP1.1,将作出如下响应:100-continue headers,如果web客户端调用ackRequest方法发送这个header。它还检查是否允许块。
if (http11) {
// Sending a request acknowledge back to the client if
// requested.
ackRequest(output);
// If the protocol is HTTP/1.1, chunking is allowed.
if (connector.isChunkingAllowed())
response.setAllowChunking(true);
}
ackRequest方法检查sendAck的值并且如果sendAck为true则发送如下的字符串:
HTTP/1.1 100 Continue\r\n\r\n
在解析HTTP请求期间,也许多个异常中的一个会被抛出。抛出任何的异常都会将ok和finishResponse变量设置成false。解析完成后,process方法将request和response对象传给Container的invoke方法。
try {
((HttpServletResponse) response).setHeader
("Date", FastHttpDateFormat.getCurrentDate());
if (ok) {
connector.getContainer().invoke(request, response);
}
}
然后,如果finishResponse仍然是true,response对象的finishResponse方法和request对象的finishRequest方法被调用,并flush输出。
if (finishResponse) {
...
response.finishResponse();
...
request.finishRequest();
...
output.flush();
while循环的最后一部分检查是否response的connection headers已经从servlet内部被关闭或者是否协议时HTTP1.0。如果是这种情况,keepAlive设置成false。当然,request和response对象也都被再循环。
if ( "close".equals(response.getHeader("Connection")) ) {
keepAlive = false;
}
// End of request processing
status = Constants.PROCESSOR_IDLE;
// Recycling the request and the response objects
request.recycle();
response.recycle();
}
眼下,如果keepAlive为true,且在先前的解析过程中及Container的invoke方法中没有错误,且HttpProcessor实例没有被停止的话,while循环将开始。否则,shutdownInput方法被调用且socket被关闭。
try {
shutdownInput(input);
socket.close();
}
...
shutdownInput方法检查是否有为读取的字节,如果有,将跳过那些字节。
解析connection
parseConnection方法从Socket获取Internet地址并指派给HttpRequestImpl对象。它也检查是否使用了代理并将socket指派给request对象。parseConnection方法在4.2中给出。
清单4.2:parseConnection方法
private void parseConnection(Socket socket)
throws IOException, ServletException {
if (debug >= 2)
log(" parseConnection: address=" + socket.getInetAddress() +
", port=" + connector.getPort());
((HttpRequestImpl) request).setInet(socket.getInetAddress()); if (proxyPort != 0)
request.setServerPort(proxyPort);
else
request.setServerPort(serverPort);
request.setSocket(socket);
}
解析request
parseRequest方法是第三章中类似方法的完整版本。如果你很好的理解了第三章,通过阅读你应该可以理解这个方法是如何工作的。
解析headers
默认connector中的parseHeaders方法使用org.apache.catalina.connector.http包中的HttpHeader和DefaultHeaders类。HttpHeader类展示了一个HTTP request headers。不像第三章一样使用字符串,HttpHeader类使用字符数组来避免字符串操作的开销。DefaultHeader类是一个final类,字符数组中包含标准的HTTP request header :
static final char[] AUTHORIZATION_NAME =
"authorization".toCharArray();
static final char[] ACCEPT_LANGUAGE_NAME =
"accept-language".toCharArray();
static final char[] COOKIE_NAME = "cookie".toCharArray();
...
parseHeaders方法包含一个while循环持续读取HTTP请求直到没有header可读为止。While循环从调用request对象的allocateHeader方法获取一个空的HttpHeader实例开始,这个实例被传给SocketInputStream的readHeader方法。
HttpHeader header = request.allocateHeader();
// Read the next header
input.readHeader(header);
如果所有的headers都被读取完,readHeader方法将给HttpHeader实例赋值为no name,此时就是parseHeaders方法返回的时候了。
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
}
else {
throw new ServletException
(sm.getString("httpProcessor.parseHeaders.colon"));
}
}
如果有header name,一定有header value:
String value = new String(header.value, 0, header.valueEnd);
接下来,类似第三章,parseHeaders方法比较header name与DefaultHeaders中的标准name。注意,比较时在两个字符数组中进行的,而不是两个字符串。
if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) {
request.setAuthorization(value);
}
else if (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {
parseAcceptLanguage(value);
}
else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
// parse cookie
}
else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {
// get content length
}
else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {
request.setContentType(value);
}
else if (header.equals(DefaultHeaders.HOST_NAME)) {
// get host name
}
else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {
if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {
keepAlive = false;
response.setHeader("Connection", "close");
} }
else if (header.equals(DefaultHeaders.EXPECT_NAME)) {
if (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE))
sendAck = true;
else
throw new ServletException(sm.getstring
("httpProcessor.parseHeaders.unknownExpectation"));
}
else if (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {
//request.setTransferEncoding(header);
}
request.nextHeader();
简单的Container应用程序
本章的应用程序的主要目的是展现如何使用默认connector。它包含两个类:
ex04.pyrmont.core.SimpleContainer 和 ex04 pyrmont.startup.Bootstrap。SimpleContainer类实现了org.apache.catalina.container,因此它可以与connector连接起来。Bootstrap类用来启动应用程序,我们已经移除了第三章程序中的connector模块和ServletProcessor类及StaticResourceProcessor类,因此你不能请求一个静态页面。
SimpleContainer类再清代4.3中给出。
清单4.3:SimpleContainer类
package ex04.pyrmont.core;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.File;
import java.io.IOException;
import javax.naming.directory.DirContext;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Cluster;
import org.apache.catalina.Container;
import org.apache.catalina.ContainerListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Logger;
import org.apache.catalina.Manager;
import org.apache.catalina.Mapper;
import org.apache.catalina.Realm;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
public class SimpleContainer implements Container {
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
public SimpleContainer() { }
public String getInfo() {
return null;
}
public Loader getLoader() {
return null;
}
public void setLoader(Loader loader) { }
public Logger getLogger() {
return null;
}
public void setLogger(Logger logger) { }
public Manager getManager() {
return null;
}
public void setManager(Manager manager) { }
public Cluster getCluster() {
return null;
}
public void setCluster(Cluster cluster) { }
public String getName() {
return null;
}
public void setName(String name) { }
public Container getParent() {
return null;
}
public void setParent(Container container) { }
public ClassLoader getParentClassLoader() {
return null;
}
public void setParentClassLoader(ClassLoader parent) { }
public Realm getRealm() {
return null;
}
public void setRealm(Realm realm) { }
public DirContext getResources() {
return null;
}
public void setResources(DirContext resources) { }
public void addChild(Container child) { }
public void addContainerListener(ContainerListener listener) { }
public void addMapper(Mapper mapper) { }
public void addPropertyChangeListener(
PropertyChangeListener listener) { }
public Container findchild(String name) {
return null;
}
public Container[] findChildren() {
return null;
}
public ContainerListener[] findContainerListeners() {
return null;
}
public Mapper findMapper(String protocol) {
return null;
}
public Mapper[] findMappers() {
return null;
}
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());
}
}
public Container map(Request request, boolean update) {
return null;
}
public void removeChild(Container child) { }
public void removeContainerListener(ContainerListener listener) { }
public void removeMapper(Mapper mapper) { }
public void removoPropertyChangeListener(
PropertyChangeListener listener) {
}
}
我仅提供了SimpleContainer类的invoke方法的实现,因为默认connector将调用这个方法。invoke方法创建一个class loader,加载servlet class,并调用service方法。这个方法类似于第三章ServletProcessor类的process方法。
Bootstrap类在清单4.4中给出
清单4.4:ex04.pyrmont.startup.Bootstrap类
package ex04.pyrmont.startup;
import ex04.pyrmont.core.simplecontainer;
import org.apache.catalina.connector.http.HttpConnector;
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();
}
}
}
Bootstrap类的main方法创建了一个org.apache.catalina.connector.http.HttpConnector实例和一个SimpleContainer实例。然后通过调用connector的setContainer方法将connector与Container连接起来,传递的参数为该container。接下来,调用connector的initialize和start方法。这将使该connector准备处理8080端口上的任何HTTP请求。
你可以通过在控制台按任意键终止该程序。
运行程序
要在windows上运行该程序,从工作路径键入如下命令:
java -classpath ./lib/servlet.jar;./ ex04.pyrmont.startup.Bootstrap
对于Linux,使用冒号分割开两个包。
java -classpath ./lib/servlet.jar:./ ex04.pyrmont.startup.Bootstrap
你可以用第三章的方式调用PrimitiveServlet和ModernServlet。注意,你不能请求index.html文件,因为没有处理静态资源的processor
总结
这章讲解了构建一个可以与Catalina交互的Tomcat connector。分割开了Tomcat 4的默认connector的代码,并构建了一个小程序应用该connector。下面章节的所有程序都使用该默认connector。
运维网声明
1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网 享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com