?
第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
方法的原型如下:
public void invoke( org.apache.catalina.Request request, org.apache.catalina.Response response);
?
??? 在invoke
方法内部,容器加载servlet
类,
调用其service
方法,管理会话,打印错误日志等等。默认连接器还利用了一些第3
章连接器没有的优化措施。首先,默认连接器提供了对象池来避免昂贵的对象创建。第二,
默认连接器在很多地方使用字符数组来代替字符串。
???
本章的应用,是一个与默认连接器关联的、简单的容器。不过,本章的焦点不是这个容器,而是默认连接器。容器将在第5
章被讨论。不管怎么样,我们还是在本章的{zh1}一节“简单的容器程序”讨论该容器,以演示如何使用默认连接器。
???
另一个需要注意的地方是,默认连接器实现了那些在HTTP 1.1
中新加
的、同样可以服务HTTP 0.9
和HTTP 1.0
客户的特性。为了理解HTTP
1.1
的新特性,你首先需要理解这些特性,我们将在本章{dy}节解释它们。在此之后,我们讨论org.apache.catalina.Connector
,如何创建请求对象和响应对象。如果你理解第3
章的连接器是如何工作的,你也不难理解默认连接器。
??? 本章以HTTP 1.1
的3
个
新特性作为开始。理解它们是理解默认连接器内部原理的关键。然后,本章介绍了所有连接器都必须是实现的org.apache.catalina.Connector
接
口。你会发现第3章中已经遇到的一些类,像HttpConnector
、HttpProcessor
等等。不过,现在这些类比第3
章要高级的多。
??? 本节介绍HTTP 1.1 的三个新特性。理解这些特性,对于理解默认连接器如何处理HTTP 请求,是非常关键的。
持久化连接(Persistent Connections )
??? 在HTTP 1.1
之
前,无论浏览器什么时候连接上Web
服务器,服务器在发送完被请求资源之后立
刻关闭连接。但是,一个网页可以包含其他资源,例如图片文件、applets
等。
因此,当一个页面被请求时,浏览器也需要下载该页面引用的资源。如果页面及其引用的所有资源都通过不同的连接下载,那么整个处理过程会很慢。这就是HTTP 1.1
引入持久化连接的原因。对于持久化连接,页面下载完成后,服务器不会直
接关闭连接,而是等待客户端请求该页面引用的所有资源。这样,页面及其引用的所有资源均使用同一个连接下载。考虑到建立和关闭HTTP
连接都是昂贵的操作,这种方式将大大节省了Web
服务器、客户端及网络的负载和时间。
???
??? 持久化连接是HTTP 1.1
的默认连接。浏览器也可以通过发送下面的connection
头部,显式地告诉服务器使用持久化连接:
??? connection: keep-alive
Chunked 编码(Chunked Encoding )
???
建立持久化连接的一个结果就是,服务器可以在同一个连接上发送多个资源的字节流,客户端也可以在同一个连接上发送多个请求。因此,发送者必须发送每个请求
或响应的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
个字节,{dy}个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
的
十六进制形式,指示{dy}个chunk
包括29
个字节。0\r\n
标识该传输单元的结
束。
100 (Continue ) 状态码的使用
??? 如果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 接口 (这样它的实例就可以运行在自己的线程中),以及
??? 我们将在第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 < minProcessors) { if ((maxProcessors > 0) && (curProcessors >= maxProcessors)) break; HttpProcessor processor = newProcessor(); recycle(processor); }?
??? newProcessor方法创建一个HttpProcessor对象,并递增curProcessors。recycle方法将 HttpProcessor压回栈中。每个HttpProcessor实例都负责解析HTTP请求行(request line)和头部,并填充(populate)一个请求对象。因此,每个HttpProcessor实例都关联一个请求对象和一个响应对象。 HttpProcessor的构造函数调用了HttpConnector的createRequest和createResponse方法。
处理HTTP 请 求
??? 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实例的数量还没有达到 {zd0}值,createProcessor方法就会创建一个实例。但如果HttpProcessor实例数量达到{zd0}值了,那么 createProcessor方法就返回null。这种情况下,套接字被简单关闭掉,而且HTTP请求不会被处理。
?
if (processor == null) { try { log(sm.getString("httpConnector.noProcessor")); socket.close(); } ... continue;
processor.assign(socket);?
??? 现在,轮到HttpProcessor读取套接字的输入流,解析HTTP请求了。需要特别注意的是,assign方法必须直接返回,而不要等待 HttpProcessor完成解析,以便下一个HTTP请求能够被处理。因为每个HttpProcess实例都拥有自己的线程用于解析,所以做到这点是 不难的。你在下一节“HttpProcessor类”中可以看到这是怎么实现的。
???
??? 提 示:HttpProcessor另一个重要方法是私有process方法,它负责解析HTTP请求并调用容器的invoke方法。我们将在后面的“处理请 求”小节看到该方法。
?? ?在第3章中,HttpConnector运行在自己的线程里。但是,在处理下一个HTTP请求前,它必须等待当前的HTTP请求被处理完毕。下面是第3 章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); } }
?
?
?
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(); } }
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方法:
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); }
?
???
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变量:
this.socket = 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是如何创建请求对象和响应对象的。现在是整个处理过程的{zh1}一步。本节我们重点xx HttpProcess的process方法。HttpProcess得到套接字之后,其run方法就会调用process方法。process方法会执 行以下操作:
- 解析连接
- 解析请求
- 解析头部
??
?解释完process方法后,我们会分子章节讨论上述每个操作。
boolean ok = true; boolean finishResponse = true; ?
?
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停止,或抛出异常,或连接关闭。
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); //第7章将讨论日志 ok = false; }?
try { if (ok) { parseConnection(socket); parseRequest(input, output); if (!request.getRequest().getProtocol() .startsWith("HTTP/0")) parseHeaders(input);
?
?
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循环的{zh1}一部分检查响应的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) request.setServerPort(proxyPort); else request.setServerPort(serverPort); request.setSocket(socket); }
解析请求?
??? 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_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();??? 本章应用的主要目的是展示如何使用默认连接器。该应用共包括两个类: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 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方法的实现,因为默认连接器将会调用该方法。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(); // make the application wait until we press any key. System in.read(); } catch (Exception e) { e.printStackTrace(); } } }
??? 你可以在控制台上敲入任何键来停止该应用。
运行应用
java -classpath ./lib/servlet.jar:./ ex04.pyrmont.startup.Bootstrap
??? 你可以调用按照第3章的方法调用PrimitiveServlet和ModernServlet。注意你不能请求index.html,因为本章应用没有 静态资源处理器(processor)。
??? 本章展示了如何构建一个可以和Catalina协同工作的Tomcat连接器。剖析了Tomcat 4默认连接器的代码,构建了一个使用该连接器的小应用。接下来的章节的应用都会使用该默认连接器。
?