`
yangqianyu222
  • 浏览: 19571 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

译How Tomcat Works(第四章)

阅读更多

第四章: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。

 

 

分享到:
评论
2 楼 yangqianyu222 2010-07-13  
gosin 写道
这么厉害啊。
你把代码跑起来没有?


哈哈,没时间跑代码,只能有空就翻译一点,很多词不会翻译。
1 楼 gosin 2010-06-24  
这么厉害啊。
你把代码跑起来没有?

相关推荐

    WEB服务器工作机制由浅至深(2):【How Tomcat Works】1~4章翻译分析

    NULL 博文链接:https://sulin.iteye.com/blog/1014952

    How Tomcat Works: A Guide to Developing Your Own Java Servlet Container

    第4章 tomcat的默认连接器 18 4.1 简介 18 4.2 HTTP1.1的新特性 18 4.2.1 持久化连接 18 4.2.2 编码 18 4.2.3 状态码100的使用 19 4.3 Connector接口 19 4.4 HttpConnector类 20 4.4.1 创建ServerSocket 20 4.4.2 ...

    WEB服务器工作机制由浅至深(4):【How Tomcat Works】第10章翻译分析及Tomcat安全性探讨

    NULL 博文链接:https://sulin.iteye.com/blog/1028877

    how-tomcat-works

    第4章 tomcat的默认连接器 18 4.1 简介 18 4.2 HTTP1.1的新特性 18 4.2.1 持久化连接 18 4.2.2 编码 18 4.2.3 状态码100的使用 19 4.3 Connector接口 19 4.4 HttpConnector类 20 4.4.1 创建ServerSocket 20 4.4.2 ...

    how-tomcat-works中文版

    讲述Tomcat的工作原理 ...第4章 tomcat的默认连接器 第5章 container 第6章 生命周期(Lifecycle) 第7章 Logger 第8章 Loader 第9章 session管理 第10章 安全性 第11章 StandardWrapper 第12章 StandardContext类 ...

    HowTomcatWorks:《深度剖析Tomcat》原始码及笔记

    第四章Tomcat的默认连接器 第五章servlet容器 第六章生命周期 第七章日志记录器 第八章加载器 第九章会议管理 第十章应用程序 第十一章StandardWrapper 第十二章StandardContext 第十三章主机和引擎 第十四章服务器...

    servlet和jsp学习指南

    第3章和第4章系统讲解JSP的语法以及JSP中的重要特性之一:Expression Language;第5~7章分别阐述JSTL中最重要的类库、标签的具体编写方法和标签文件;第8~10章讨论Servlet中的事件驱动编程、过滤器,以及Model 2架构...

    Servle和JSP学习指南

    第3章和第4章系统讲解JSP的语法以及JSP中的重要特性之一:Expression Language;第5~7章分别阐述JSTL中最重要的类库、标签的具体编写方法和标签文件;第8~10章讨论Servlet中的事件驱动编程、过滤器,以及Model 2架构...

    Servlet和jsp学习指南

    第3章和第4章系统讲解JSP的语法以及JSP中的重要特性之一:Expression Language;第5~7章分别阐述JSTL中最重要的类库、标签的具体编写方法和标签文件;第8~10章讨论Servlet中的事件驱动编程、过滤器,以及Model 2架构...

    Servle和JSP学习指南,完整扫描版

    第3章和第4章系统讲解JSP的语法以及JSP中的重要特性之一:Expression Language;第5~7章分别阐述JSTL中最重要的类库、标签的具体编写方法和标签文件;第8~10章讨论Servlet中的事件驱动编程、过滤器,以及Model 2架构...

    Servlet和Jsp学习指南(带详细书签)

    第3章和第4章系统讲解jsp的语法以及jsp中的重要特性之一:expression language;第5~7章分别阐述jstl中最重要的类库、标签的具体编写方法和标签文件;第8~10章讨论servlet中的事件驱动编程、过滤器,以及model 2架构...

    servlet和jsp学习指南_pdf.rar

    第3章和第4章系统讲解jsp的语法以及jsp中的重要特性之一:expression language;第5~7章分别阐述jstl中最重要的类库、标签的具体编写方法和标签文件;第8~10章讨论servlet中的事件驱动编程、过滤器,以及model 2架构...

    深入浅出Struts 2 .pdf(原书扫描版) part 1

    第4章 OGNL 52 4.1 Value Stack栈 52 4.2 读取Object Stack里的对象的属性 53 4.3 读取Context Map里的对象的属性 54 4.4 如何调用字段和方法 55 4.5 如何访问数组类型的属性 56 4.6 如何访问List类型的属性 56 4.7 ...

Global site tag (gtag.js) - Google Analytics