[How Tomcat Works]第4章 Tomcat默认连接器
2010年06月29日
第3 章的连接器工作得很好,而且本可以设计地更好。但是,我们只是将它设计成教学工具,来介绍Tomcat 4 的默认连接器。理解第3 章的连接器,是理解Tomcat 4 默认连 接器的关键。第4 章将会通过解剖(dissect )Tomcat 4 默认 连接器的代码,来讨论如何构建真正的Tomcat 连接器。
提示:本章的"默认连接器" 就是指Tomcat 4 默认连接器。尽管默认连接器已经废弃(deprecated ),被更快的Coyote 连接器代替了,但是它仍然是一个很好的学习工具。
Tomcat 连接器是一个可以插入Servlet 容 器的独立模块。现在已经有很多连接器,例如Coyote 、mod_jk 、mod_jk2 和mod_webapp 。一个Tomcat 连 接器满足下面的需求:
必须实现org.apache.catalina.Connector 接口
必须创建请求对象,该对象的类必须实现org.apache.catalina.Request 接口
必须创建响应对象,该对象的类必须实现org.apache.catalina.Response 接口
Tomcat 4 默认连接器和第3 章中简单连接器的工作原理类似。它等待接受HTTP 请 求,创建请求对象和响应对象,然后将这两个对象传给容器。连接器通过调用org.apache.catalina.Container 接 口的invoke 方法,将请求对象和响应对象传递给容器。invoke 方法的原型如下: 在invoke 方法内部,容器加载servlet 类, 调用其service 方法,管理会话,打印错误日志等等。默认连接器还利用了一些第3 章连接器没有的优化措施。首先,默认连接器提供了对象池来避免昂贵的对象创建。第二, 默认连接器在很多地方使用字符数组来代替字符串。
本章的应用,是一个与默认连接器关联的、简单的容器。不过,本章的焦点不是这个容器,而是默认连接器。容器将在第5 章被讨论。不管怎么样,我们还是在本章的最后一节"简单的容器程序"讨论该容器,以演示如何使用默认连接器。
另一个需要注意的地方是,默认连接器实现了那些在HTTP 1.1 中新加 的、同样可以服务HTTP 0.9 和HTTP 1.0 客户的特性。为了理解HTTP 1.1 的新特性,你首先需要理解这些特性,我们将在本章第一节解释它们。在此之后,我们讨论org.apache.catalina.Connector ,如何创建请求对象和响应对象。如果你理解第3 章的连接器是如何工作的,你也不难理解默认连接器。
本章以HTTP 1.1 的3 个 新特性作为开始。理解它们是理解默认连接器内部原理的关键。然后,本章介绍了所有连接器都必须是实现的org.apache.catalina.Connector 接 口。你会发现第3章中已经遇到的一些类,像HttpConnector 、HttpProcessor 等等。不过,现在这些类比第3 章要高级的多。 本节介绍HTTP 1.1 的三个新特性。理解这些特性,对于理解默认连接器如何处理HTTP 请求,是非常关键的。 在HTTP 1.1 之 前,无论浏览器什么时候连接上Web 服务器,服务器在发送完被请求资源之后立 刻关闭连接。但是,一个网页可以包含其他资源,例如图片文件、applets 等。 因此,当一个页面被请求时,浏览器也需要下载该页面引用的资源。如果页面及其引用的所有资源都通过不同的连接下载,那么整个处理过程会很慢。这就是HTTP 1.1 引入持久化连接的原因。对于持久化连接,页面下载完成后,服务器不会直 接关闭连接,而是等待客户端请求该页面引用的所有资源。这样,页面及其引用的所有资源均使用同一个连接下载。考虑到建立和关闭HTTP 连接都是昂贵的操作,这种方式将大大节省了Web 服务器、客户端及网络的负载和时间。
持久化连接是HTTP 1.1 的默认连接。浏览器也可以通过发送下面的connection 头部,显式地告诉服务器使用持久化连接:
connection: keep-alive 建立持久化连接的一个结果就是,服务器可以在同一个连接上发送多个资源的字节流,客户端也可以在同一个连接上发送多个请求。因此,发送者必须发送每个请求 或响应的content-length 头部,这样接收者才知道如何解析接收到字节流。 但是通常情况下,发送者并不知道要发送多少字节。举个例子,servlet 容 器可以在部分字节准备好时就开始发送响应,而不要等到所有字节都准备好。这意味着,必须有一种方法告诉接收者:在不能提前知道content- length头部的情况下,如何解析字节流。
即使没有发送多个请求或响应,服务器或客户端也没有必要知道,究竟多少数据将被发送。在HTTP 1.0 中,服务器可以省略content-length 头部,直接向 连接写如数据。当写入完成时,服务器会简单地关闭连接。在这种情况下,客户端持续读取,直到返回标识字节流结束的-1 。
HTTP 1.1 利 用了一个名为transfer-encoding 的特殊头部,来指示字节流将按照chunk 的形式被发送。每个chunk 的格式是:首先是十六进制的长度,后面跟着一个CR/LF , 然后是数据。零长度的chunk 标识了一个传输单元(transaction )。假设在某个传输中,你想以2 个chunk 的形式发送下面的38 个字节,第一个chunk 长 度为29 ,第二个chunk 长 度为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 的 十六进制形式,指示第一个chunk 包括29 个字节。0\r\n 标识该传输单元的结 束。 如果HTTP 1.1 客户端打算发送很长的请 求,但是不确定服务器是否愿意接收,那么,它可以在发送请求体之前先发送Expect: 100-continue 头部给服务器,然后等待服务器的确认。不这么做的话,如果客户端发送了很长的请求体,最终发现被服务器拒绝了,那 么这就太浪费了。
接收到Expect: 100-continue 头 部之后,如果服务器愿意(will to )或能够(can )处理请求,那么服务器会返回下面的100-continue 头部,头部后面再跟两对CRLF 。
HTTP/1.1 100 Continue
接着,服务器继续读取输入流。 Tomcat 连接器必须实现org.apache.catalina.Connector 接 口。该接口的众多方法中,最重要的是getContainer 、setContainer 、createRequest 和createResponse 。
setContainer 方法用来将连接器和容器关联起来。getContainer 方法返回关联的容器。createRequest 方 法为进来的HTTP 请求创建一个请求对象,createResponse 方法创建一个响应对象。
org.apache.catalina.connector.http.HttpConnector 是Connector 接口的一个实现类,下一节"The HttpConnector Class "将讨论它。现在,我们看看默认连接其的类图Figure 4.1 。注意,为了简化类图,Request 和Response 接口的实现类被省略了。除了Simplecontainer 类,其他都省略了前缀"org.apache.catalina ",只保留类型名。
因此,连接器...(原文找不到,省略部分文字)
连接器和容器是一对一的关联关系。关联关系的箭头方向表明,连接器知道容器,而容器却不知道连接器。同时需要注意,与第3 章不同,HttpConnector 与HttpProcessor 的关系变成了一对多。 第3 章已经介绍了org.apache.catalina.connector.http.HttpConnector 的 一个简化版,因此你其实已经知道了HttpConnector 的工作原理。HttpConnector 实现了org.apache.catalina.Connector 接口 (为了和Catalina 协调),java.lang.Runnable 接口 (这样它的实例就可以运行在自己的线程中),以及 org.apache.catalina.Lifecycle 接口。Lifecycle 接口用来维护每个实现该接口的Catalina 组件的生命周期。
我们将在第6 章介绍Lifecycle 接口,现在你只需要知道:实 现了Lifecycle 接口,创建HttpConnector 实 例之后,你应该调用它的initialize 和start 方法。这两个方法在组件的生命周期中只能被调用一次。下面,我们看看与第3 章HttpConnector 不 同的地方:如何创建服务器套接字,如何维护HttpProcessor 池,如何处理HTTP 请求。
创建服务器套接字
HttpConnector的initialize方法调用了返回java.net.ServerSocket实例的私有open方法,然后将 ServerSocket实例赋值给serverSocket变量。不过,open方法从一个服务器套接字工厂获取ServerSocket实例,而不是 调用java.net.ServerSocket的构造函数。如果你想知道服务器套接字工厂的细节,那么可以阅读ServerSocketFactory 接口和DefaultServerSocketFactory类,它们在org.apache.catalina.net包中,代码很容易理解。
维护HttpProcessor 实例
在第3 章 中,HttpConnector 实例同时只拥有一个HttpProcessor 实例,因此同时只能处理一个HTTP 请求。在默认连接器中,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的数量。创建初始数量HttpProcessor实例的代码在 HttpConnector的start方法中:
while (curProcessors 0) && (curProcessors >= maxProcessors))
break;
HttpProcessor processor = newProcessor();
recycle(processor);
}
newProcessor方法创建一个HttpProcessor对象,并递增curProcessors。recycle方法将 HttpProcessor压回栈中。每个HttpProcessor实例都负责解析HTTP请求行(request line)和头部,并填充(populate)一个请求对象。因此,每个HttpProcessor实例都关联一个请求对象和一个响应对象。 HttpProcessor的构造函数调用了HttpConnector的createRequest和createResponse方法。
HttpConnector的主要逻辑还是在它的run方法中,就像第3章中那样。run方法包括一个while循环,服务器套接字循环等待HTTP请 求,直到HttpConnector被停止。
while (!stopped) {
Socket socket = null;
try {
socket = serverSocket.accept();
...
对于每个HTTP请求,HttpConnector通过调用私有createProcessor方法获取一个HttpProcessor实例。
HttpProcessor processor = createProcessor();
不过,createProcessor方法大部分时间都没有创建新的HttpProcessor实例,而是从实例池中获取。只要栈中还有一个 HttpProcessor实例,那么createProcessor方法就弹出一个。如果栈空了,而且HttpProcessor实例的数量还没有达到 最大值,createProcessor方法就会创建一个实例。但如果HttpProcessor实例数量达到最大值了,那么 createProcessor方法就返回null。这种情况下,套接字被简单关闭掉,而且HTTP请求不会被处理。
if (processor == null) {
try {
log(sm.getString("httpConnector.noProcessor"));
socket.close();
}
...
continue;
如果createProcessor方法没有返回null,那么客户端套接字就会被传递给HttpProcessor的assign方法:
processor.assign(socket);
现在,轮到HttpProcessor读取套接字的输入流,解析HTTP请求了。需要特别注意的是,assign方法必须直接返回,而不要等待 HttpProcessor完成解析,以便下一个HTTP请求能够被处理。因为每个HttpProcess实例都拥有自己的线程用于解析,所以做到这点是 不难的。你在下一节"HttpProcessor类"中可以看到这是怎么实现的。
默认连接器的HttpProcessor类是第3章中同名类的完整版。你已经知道它是如何工作的,因此本章更感兴趣的地方在 于:HttpProcessor是如何使得assign方法异步执行,从而让HttpConnector可以同时处理多个HTTP请求的。
提 示:HttpProcessor另一个重要方法是私有process方法,它负责解析HTTP请求并调用容器的invoke方法。我们将在后面的"处理请求"小节看到该方法。
在第3章中,HttpConnector运行在自己的线程里。但是,在处理下一个HTTP请求前,它必须等待当前的HTTP请求被处理完毕。下面是第3 章HTTPConnector的run方法:
第3章中,HttpProcessor的process方法是同步的。因此,run方法必须等待process方法结束,才能接收下一个请求。
但是在默认连接器中,HttpProcessor实现了java.lang.Runnable接口,HttpProcessor的每个实例都运行在自己 的线程中,我们将这些线程成为"处理器线程(processor thread)"。对于HttpConnector每个HttpProcessor实例,都会调用它们的start方法,从而启动它们的"处理器线程"。 Listing 4.1列出了默认连接器中HttpProcessor的run方法:
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();
}
}
run方法的while循环一直执行这个序列:获取套接字,处理套接字,调用connector的recycle方法将当前HttpProcessor 实例压回栈中。这儿是HttpConnector类的recycle方法:
void recycle(HttpProcessor processor) {
processors.push(processor);
}
注意run方法的while循环在await方法出会暂停。await方法挂起"处理器线程",直到从HttpConnector获得一个新的套接字。 换句话说,也就是直到HttpConnector调用HttpProcessor实例的assign方法。但是,await方法与assign方法运行在 不同的线程中。assign方法是从HttpConnector的run方法中调用的。我们将HttpConnector的run方法所在的线程称为"连 接器线程(connector thread)"。assign方法如何告诉await方法,自己(assign方法)被调用了呢?通过布尔变量 available,java.lang.Object的wait和notifyAll方法。
提示:wait方法导致当前线程等待,直到另一个线程调用了该对象的notify或notifyAll方法。
这里是HttpProcessor的assign方法和await方法: Table 4.1总结了这两个方法的程序流(program flow)。
Table 4.1: Summary of the await and assign method
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 ...
一开始,当"处理器线程"刚刚启动时,available为false,所以"处理器线程"在while循环中等待(参见Table 4.1的第1列)。它会一直等待直到另一个线程调用notify或notifyAll方法为止。这就是说,调用await方法导致"处理器线程"暂停,直到"连接器线程"调用HttpProcessor实例的notifyAll方法。
现在,看看第2列。当一个新套接字被分配(assign)时,"连接器线程"调用HttpProcessor的assign方法。available的值 为false,因此while循环被跳过去,套接字被赋值给HttpProcessor实例的socket变量: 接着,"连接器线程"将available设置成true,并调用notifyAll方法。这会唤醒"处理器线程",而且现在available的值为 true,"处理器线程"从而离开while循环:将实例变量socket赋值给本地变量socket,设置available为false,调用 notifyAll方法,返回本地变量socket,最终套接字将被处理。
为什么await方法需要使用本地变量(socket),而不是返回实例变量socket呢?这样做,当前套接字被处理完之前,下一个套接字就可以赋值给 实例变量socket。
为什么await方法需要调用notifyAll呢?就是为了解决这个问题:available值为true时另一个套接字到达。在这种情况下,"连接器 线程"将停在assign方法while循环中,直到"处理器线程"调用notifyAll。 org.apache.catalina.Request接口代表了默认连接器的HTTP请求对象。该接口被HttpRequest的父类 RequestBase直接继承。最终的实现是HttpRequest的子类HttpRequestImpl。就像第3章一样,这里也有几个门面 (facade)类:RequestFacade和HttpRequestFacade。Figure 4.2给出了Request接口及其实现类的UML图。注意该图不包括javax.servlet和javax.servlet.http包中的类型,前 缀org.apache.catalina被省略。
如果你理解第3章中的请求对象,那么你应该能够理解上面这张图。 Figure 4.3 给出了Response接口及其实现类的UML图。
到这里,你已经理解了HttpConnector是如何创建请求对象和响应对象的。现在是整个处理过程的最后一步。本节我们重点关注 HttpProcess的process方法。HttpProcess得到套接字之后,其run方法就会调用process方法。process方法会执 行以下操作: 解释完process方法后,我们会分子章节讨论上述每个操作。 process方法使用布尔变量ok指示处理过程是否发生了错误,使用布尔变量finishResponse指示Response接口的 finishResponse是否应该被调用。
另外,process方法还使用了布尔变量keepalive、stopped和http11。keepalive指示连接是否是持久化的 (persistent),stopped指示HttpProcessor实例是否被连接器停止了(这样process方法也需要停止),http11指 示来自网络客户端的HTTP请求是否支持HTTP 1.1。
就像第3章一样,一个SocketInputStream实例包装了套接字的输入流。注意,来自连接器对象的缓冲区大小(buffer size)被传递给SocketInputStream的构造函数,而不是HttpProcessor的本地变量。这是因为默认连接器的用户访问不到 HttpProcessor。将缓冲区大小放到Connector接口中,可以让任何人使用连接器来设置缓冲区大小。
然后,有一个while循环,不断从输入流中读取数据,直到HttpProcessor停止,或抛出异常,或连接关闭。 在while循环中,process方法首先设置finishResponse为true,然后获取输出流,并初始化请求对象和响应对象。
接着,process方法首先依次调用parseConnection、parseRequest和parseHeaders方法来解析请求,下面每个 子章节会讨论这些方法。
parseConnection方法获取协议(protocol)的值,该值可能是HTTP 0.9、HTTP 1.0或HTTP 1.1。如果协议是HTTP 1.0,keepAlive的值将被设置为false,因为HTTP 1.0不支持持久化连接。如果在HTTP请求中找到Expect: 100-continue头部,parseHeaders方法将会设置sendAck为true。如果协议是HTTP 1.1,且客户端发送了Expect: 100-continue头部,parseHeaders方法就会通过调用ackRequest方法来响应该头部。同时,parseHeaders方法也 会检查连接器是否允许chunking。
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的值,如果为true就发送下面的字符串:
HTTP/1.1 100 Continue\r\n\r\n
在解析HTTP请求的过程中,可以会抛出多种异常。抛出任何异常,process方法都会设置ok或finishResponse为false。解析结束 之后,process方法将请求对象和响应对象传递给容器的invoke方法。
try {
((HttpServletResponse) response).setHeader
("Date", FastHttpDateFormat.getCurrentDate());
if (ok) {
connector.getContainer().invoke(request, response);
}
}
然后,如果finishResponse仍为true,响应对象的finishResponse方法和请求对象的finishRequest方法都会被调 用,且输出被flush。
if (finishResponse) {
...
response.finishResponse();
...
request.finishRequest();
...
output.flush();
while循环的最后一部分检查响应的Connection头部是否被Servlet设置成close,或者协议是否是HTTP 1.0。如果是这样,keepAlive被设置成false。同时,请求对象和响应对象被回收再利用(recycle)。
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,且前面的解析过程和容器的invoke方法都没有发生错误,那么while循环将从头重新开始。否 则,process方法调用shutdownInput方法并关闭套接字。
try {
shutdownInput(input);
socket.close();
}
...
shutdownInput方法检查是否有未读的字节。如果有,就跳过这些字节。
parseConnection方法获得套接字的网络地址(Internet address),并将它赋值给HttpRequestImpl对象。该方法还检查是否使用代理服务器,如果使用就将代理服务器的端口赋值给请求对象。 Listing 4.2给出了parseConnection方法的代码。
Listing 4.2: The parseConnection method
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)
parseRequest方法是第3章中同名方法的完整版。如果你很好地理解了第3章,那么你通过阅读代码就应该能理解该方法的工作原理。
默认连接器的parseHeaders方法使用了org.apache.catalina.connector.http包中HttpHeader和 DefaultHeaders这两个类。HttpHeader类表现了一个HTTP请求头部。HttpHeader使用字符数组替代了第3章中的字符串, 以避免昂贵的字符串操作。DefaultHeaders是一个final类,以字符数组形式提供了标准的HTTP请求头部名称:
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请求,直到没有头部可以读取。while循环首先调用请求对象的 allocateHeader方法来获取一个空的HttpHeader实例。该实例被传递给SocketInputStream的readHeader方 法。
HttpHeader header = request.allocateHeader();
// Read the next header
input.readHeader(header);
//If all headers have been read, the readHeader method will assign no name to the
//HttpHeader instance, and this is time for the parseHeaders method to return.
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
}
else {
throw new ServletException
(sm.getString("httpProcessor.parseHeaders.colon")) ;
}
}
HTTP请求中,头部的名称和值必须成对出现:
String value = new String(header.value, 0, header.valueEnd);
下一步,就像第3章一样,parseHeaders方法拿头部名称和DefaultHeaders类的标准头部名称做比较。注意比较的是字符数组而不是字 符串。
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_CLOS E_VALUE)) {
keepAlive = false;
response.setHeader("Connection", "close");
} }
else if (header.equals(DefaultHeaders.EXPECT_NAME)) {
if (header.valueEquals(DefaultHeaders.EXPECT_100_VALU E))
sendAck = true;
else
本章应用的主要目的是展示如何使用默认连接器。该应用共包括两个类:ex04.pyrmont.core.SimpleContainer和ex04 pyrmont.startup.Bootstrap。SimpleContainer类实现了 org.apache.catalina.container接口,因此它能够和连接器关联。Bootstrap类被用来启动应用,我们已经移除第3章应 用中的连接器模块、ServletProcessor类和StaticResourceProcessor类,因此你不能向本章应用请求静态页面。 Listing 4.3给出了SimpleContainer类的代码。
Listing 4.3: The SimpleContainer class
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 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);
}
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方法的实现,因为默认连接器将会调用该方法。invoke方法创建了一个类加载器,加载 servlet类,调用它(servlet)的service方法。invoke方法和第3章中ServletProcessor类的process方法 非常类似。
Listing 4.4给出了Bootstrap类的代码。
Listing 4.4: The ex04.pyrmont.startup.Bootstrap class
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();
Bootstrap类的main方法构造了一个org.apache.catalina.connector.http.HttpConnector实例 connector和一个SimpleContainer实例container。然后以container为参数调用connector的 setContainer方法将connector和container相关联。接着,main方法调用connector的initialize和 start方法。这会让connector在8080端口上等待处理传入的HTTP请求。
你可以在控制台上敲入任何键来停止该应用。
在Linux 上,需要使用冒号来分隔两个库。
java -classpath ./lib/servlet.jar:./ ex04.pyrmont.startup.Bootstrap
你可以调用按照第3章的方法调用PrimitiveServlet和ModernServlet。注意你不能请求index.html,因为本章应用没有 静态资源处理器(processor)。
本章展示了如何构建一个可以和Catalina协同工作的Tomcat连接器。剖析了Tomcat 4默认连接器的代码,构建了一个使用该连接器的小应用。接下来的章节的应用都会使用该默认连接器。
发表评论
-
linux服务器编程--EPOLL
2012-01-20 00:43 546linux服务器编程--EPOLL 20 ... -
异步机制(Asynchronous) -- (二)异步消息机制兼谈Hadoop RPC
2012-01-20 00:42 427异步机制(Asynchronous) -- ( ... -
网络编程常见问题总结
2012-01-20 00:42 1017网络编程常见问题总结 2010年10月26日 网络编程常 ... -
《ASCE1885的网络编程》---Winsock APIの套接口I/O处理函数
2012-01-20 00:42 628《ASCE1885的网络编程》-- ... -
usb-hdd xp
2012-01-17 01:00 632usb-hdd xp 2011年08月28日 ... -
如何用c语言写windows服务程序
2012-01-17 01:00 789如何用c语言写windows服 ... -
有关系统DLL文件大全
2012-01-17 01:00 665有关系统DLL文件大全 2011年05月17日 有关系统 ... -
vista 系统问题
2012-01-17 00:59 536vista 系统问题 2010年06月04日 Vista ... -
救砖必备技巧 :ADB ,fastboot
2012-01-17 00:59 1151救砖必备技巧 :ADB ,fastbo ... -
诬凹沃官燠喷闳
2012-01-15 19:43 420诬凹沃官燠喷闳 2012年01月06日 U辙趴“ ... -
是傻频日志呵阡啄凛仄
2012-01-15 19:43 528是傻频日志呵阡啄凛仄 2012年01月09日 刘 ... -
???
2012-01-15 19:43 537??? 2012年01月10日 ... -
我的日志
2012-01-15 19:43 561我的日志 2012年01月14日 护卫队的战士C ... -
我的日志
2012-01-15 19:42 548我的日志 20小时前 ...
相关推荐
How Tomcat Works中文版
How Tomcat Works【英文PDF+中文HTML+源码】 How Tomcat Works 主要是讲解Tomcat如何运行的一些核心资料。
How Tomcat Works 中文版+例程源码; 源码在src目录下
HowTomcatWorks(书和源码)
How Tomcat Works 全书共20章!
How Tomcat works(PDF),不可用于商业用途,如有版权问题,请联系删除!
tomcat的基本思想,学习完可以对理解spring的基本原理有大致了解,很值得学习
how tomcat works中文版 + 英文版,深入解析了tomcat的实现机制
HowTomcatWorks 中文版+源码.rar HowTomcatWorks 中文版+源码.rar
how tomcat works 高清版 学习tomcat必备书籍 how tomcat works
How Tomcat Works》这本书的读书笔记,及主要内容感想。 作为一个世界范围广泛使用的强大框架,Tomcat必然有非常多的设计思想、设计模式,让我们学习。
How Tomcat Works Tomcat原理的书
How Tomcat Works 深入剖析Tomcat (英文版)
tomcat工作原理深入详解——HowTomcatWorks中文版.pdf
how tomcat works( 深入剖析tomcat) 的随书源码 之前找了很久,后来从官网下载下来的
How Tomcat Works,讲述Tomcat工作原理的英文教程。
How Tomcat Works 中文版+例程源码,源码在src包下面, 祝大家学习愉快
Welcome to How Tomcat Works. This book dissects Tomcat 4.1.12 and 5.0.18 and explains the internal workings of its free, open source, and most popular servlet container code-named Catalina. Tomcat is ...