Chapter 4: Tomcat Default Connector 第 4 章:Tomcat 默认连接器

Overview

The connector in Chapter 3 worked fine and could have been perfected to achieve much more. However, it was designed as an educational tool, an introduction to Tomcat 4's default connector. Understanding the connector in Chapter 3 is key to understanding the default connector that comes with Tomcat 4. Chapter 4 will now discuss what it takes to build a real Tomcat connector by dissecting the code of Tomcat 4's default connector.

第三章中的连接器工作正常,可以进一步完善以实现更多功能。

然而,它被设计为一种教育工具,用于介绍Tomcat 4的默认连接器。

理解第三章中的连接器对于理解Tomcat 4附带的默认连接器至关重要。

第四章将通过解剖Tomcat 4的默认连接器的代码来讨论构建真正的Tomcat连接器所需的内容。

Note The "default connector" in this chapter refers to Tomcat 4's default connector. Even though the default connector has now been deprecated, replaced by a faster connector code-named Coyote, it is still a great learning tool.

注意 本章中的“默认连接器”指的是Tomcat 4的默认连接器。尽管默认连接器现在已被废弃,被一个更快的代号为Coyote的连接器取代,但它仍然是一个很好的学习工具。

A Tomcat connector is an independent module that can be plugged into a servlet container. There are already many connectors in existence. Examples include Coyote, mod_jk, mod_jk2, and mod_webapp. A Tomcat connector must meet the following requirements:

Tomcat连接器是一个独立的模块,可以插入到一个Servlet容器中。

已经存在许多连接器的例子,包括Coyotemod_jkmod_jk2mod_webapp。一个Tomcat连接器必须满足以下要求:

  1. It must implement the org.apache.catalina.Connector interface.
  2. It must create request objects whose class implements the org.apache.catalina.Request interface.
  3. It must create response objects whose class implements the org.apache.catalina.Response interface
  4. 它必须实现org.apache.catalina.Connector接口。
  5. 它必须创建实现org.apache.catalina.Request接口的请求对象。
  6. 它必须创建实现org.apache.catalina.Response接口的响应对象。

Tomcat 4's default connector works similarly to the simple connector in Chapter 3. It waits for incoming HTTP requests, creates request and response objects, then passes the request and response objects to the container. A connector passes the request and response objects to the container by calling the org.apache.catalina.Container interface's invoke method, which has the following signature.

Tomcat 4的默认连接器的工作方式与第三章中的简单连接器类似。

它等待传入的HTTP请求,创建请求和响应对象,然后将请求和响应对象传递给容器。

连接器通过调用org.apache.catalina.Container接口的invoke方法将请求和响应对象传递给容器,该方法具有以下签名。

public void invoke(


org.apache.catalina.Request request

org.apache.catalina.Response response);

Inside the invoke method, the container loads the servlet class, call its service method, manage sessions, log error messages, etc.

invoke方法内部,容器加载servlet类,调用其service方法,管理会话,记录错误消息等。

The default connector also employs a few optimizations not used in Chapter 3's connector. The first is to provide a pool of various objects to avoid the expensive object creation. Secondly, in many places it uses char arrays instead of strings.

默认连接器还使用了一些在第三章连接器中未使用的优化。

首先,它提供了一个对象池,以避免代价昂贵的对象创建操作。

其次,在许多地方,它使用字符数组而不是字符串。

The application in this chapter is a simple container that will be associated with the default connector. However, the focus of this chapter is not this simple container but the default connector. Containers will be discussed in Chapter 5. Nevertheless, the simple container will be discussed in the section "The Simple Container Application" towards the end of this chapter, to show how to use the default connector.

本章的应用程序是一个简单的容器,将与默认连接器关联。

然而,本章的重点不是这个简单的容器,而是默认连接器。

容器将在第5章中讨论。

尽管如此,本章末尾的"简单容器应用程序"部分将讨论如何使用默认连接器。

Another point that needs attention is that the default connector implements all features new to HTTP 1.1 as well as able to serve HTTP 0.9 and HTTP 1.0 clients.

还需要注意的一点是,默认连接器实现了HTTP 1.1中的所有新功能,并能为HTTP 0.9和HTTP 1.0的客户端提供服务。

To understand the new features in HTTP 1.1, you first need to understand these features, which we will explain in the first section of this chapter. Thereafter, we discuss the org.apache.catalina.Connector, interface and how to create the request and response objects. If you understand how the connector in Chapter 3 works, you should not find any problem understanding the default connector.

要理解HTTP 1.1中的新功能,首先需要理解这些功能,我们将在本章的第一节中解释。

之后,我们将讨论org.apache.catalina.Connector接口以及如何创建请求和响应对象。

如果你理解了第3章中连接器的工作原理,理解默认连接器应该不会有任何问题。

This chapter starts with three new features in HTTP 1.1. Understanding them is crucial to understanding the internal working of the default connector. Afterwards, it introduces org.apache.catalina.Connector, the interface that all connectors must implement. You then will find classes you have encountered in Chapter 3, such as HttpConnector, HttpProcessor, etc. This time, however, they are more advanced than the similar classes in Chapter 3.

本章从HTTP 1.1中的三个新功能开始。

理解它们对于理解默认连接器的内部工作至关重要。

然后,介绍了所有连接器必须实现的org.apache.catalina.Connector接口。

接下来,你将会遇到在第3章中遇到的类,如HttpConnector、HttpProcessor等。

然而,与第3章中的类相比,它们更加高级。

HTTP 1.1 New Features

This section explains three new features of HTTP 1.1. Understanding them is crucial to understanding how the default connector processes HTTP requests.

本节将解释 HTTP 1.1 的三个新特性。

要了解默认连接器如何处理 HTTP 请求,了解它们至关重要。

Persistent Connections

Prior to HTTP 1.1, whenever a browser connected to a web server, the connection was closed by the server right after the requested resource was sent. However, an Internet page can contain other resources, such as image files, applets, etc. Therefore, when a page is requested, the browser also needs to download the resources referenced by the page. If the page and all resources it references are downloaded using different connections, the process will be very slow. That's why HTTP 1.1 introduced persistent connections. With a persistent connection, when a page is downloaded, the server does not close the connection straight away. Instead, it waits for the web client to request all resources referenced by the page. This way, the page and referenced resources can be downloaded using the same connection. This saves a lot of work and time for the web server, client, and the network, considering that establishing and tearing down HTTP connections are expensive operations.

在HTTP 1.1之前,每当浏览器连接到Web服务器时,服务器在发送请求的资源后立即关闭连接。

然而,一个网页可能包含其他资源,如图像文件、小程序等。因此,当请求一个页面时,浏览器还需要下载页面引用的资源。

如果页面和所有引用的资源使用不同的连接下载,这个过程将会非常慢。

这就是为什么HTTP 1.1引入了持久连接

通过持久连接,当一个页面被下载时,服务器不会立即关闭连接。

相反,它会等待Web客户端请求页面引用的所有资源。

这样,页面和引用的资源可以使用同一个连接下载。

这对于Web服务器、客户端和网络来说节省了大量的工作和时间,考虑到建立和关闭HTTP连接是昂贵的操作。

The persistent connection is the default connection of HTTP 1.1. Also, to make it explicit, a browser can send the request header connection with the value keep-alive:

持久连接是HTTP 1.1的默认连接方式。

此外,为了明确表示,浏览器可以发送带有keep-alive值的请求头连接。

connection: keep-alive

Chunked Encoding

The consequence of establishing a persistent connection is that the server can send byte streams from multiple resources, and the client can send multiple requests using the same connection. As a result, the sender must send the content length header of each request or response so that the recipient would know how to interpret the bytes. However, often the case is that the sender does not know how many bytes it will send. For example, a servlet container can start sending the response when the first few bytes become available and not wait until all of them ready. This means, there must be a way to tell the recipient how to interpret the byte stream in the case that the content-length header cannot be known earlier.

建立持久连接的结果是,服务器可以从多个资源发送字节流,客户端可以使用同一连接发送多个请求。

因此,发送方必须发送每个请求或响应的内容长度头,以便接收方知道如何解释字节。

然而,通常情况下,发送方不知道将发送多少字节。

例如,当一些字节可用时,Servlet容器可以开始发送响应,而不必等待全部字节就绪。

这意味着,在无法提前知道内容长度头的情况下,必须有一种方法告诉接收方如何解释字节流

Even without having to send multiple requests or many responses, a server or a client does not necessarily know how much data it will send. In HTTP 1.0, a server could just leave out the content-length header and keep writing to the connection. When it was finished, it would simply close the connection. In this case, the client would keep reading until it got a -1 as an indication that the end of file had been reached.

即使不需要发送多个请求或许多响应,服务器或客户端也不一定知道将发送多少数据。

在HTTP 1.0中,服务器可以省略内容长度头,并继续写入连接。

当完成时,它只需关闭连接。

在这种情况下,客户端将继续读取,直到收到-1表示已达到文件末尾。

HTTP 1.1 employs a special header called transfer-encoding to indicate that the byte stream will be sent in chunks. For every chunk, the length (in hexadecimal) followed by CR/LF is sent prior to the data. A transaction is marked with a zero length chunk. Suppose you want to send the following 38 bytes in 2 chunks, the first with the length of 29 and the second 9.

HTTP 1.1使用了一个特殊的头部字段称为传输编码(transfer-encoding),用于指示字节流将以块的形式发送。

对于每个块,在数据之前发送长度(十六进制表示)和CR/LF。

一个事务以长度为零的块标记。

假设您想要以两个块发送以下38个字节,第一个块的长度为29,第二个块的长度为9。

I'm as helpless as a kitten up a tree.

You would send the following:

您可以发送以下信息

1D\r\n
I'm as helpless as a kitten u
9\r\n
p a tree.
0\r\n

the hexadecimal of 29, indicates that the first chunk consists of 29 bytes. 0\r\n indicates the end of the transaction.

即 29 的十六进制数,表示第一个数据块由 29 个字节组成。0r\n 表示交易结束。

Use of the 100 (Continue) Status

HTTP 1.1 clients may send the Expect: 100-continue header to the server before sending the request body and wait for acknowledgement from the server. This normally happens if the client is going to send a long request body but is not sure that the server is willing to accept it. It would be a waste if the client sent the long body just to find out the server turned it down.

HTTP 1.1客户端在发送请求体之前可以向服务器发送Expect: 100-continue头部,并等待服务器的确认。

通常情况下,如果客户端要发送一个较长的请求体,但不确定服务器是否愿意接受,就会发生这种情况。

如果客户端发送了较长的请求体,却发现服务器拒绝接受,那将是一种浪费。

Upon receipt of the Expect: 100-continue header, the server responds with the following 100-continue header if it is willing to or can process the request, followed by two pairs of CRLF characters.

在收到Expect: 100-continue头部后,如果服务器愿意或能够处理该请求,服务器将以以下100-continue头部作为响应,并在其后附加两对CRLF字符。

HTTP/1.1 100 Continue

The server should then continue reading the input stream.

然后服务器应继续读取输入流。

The Connector Interface

A Tomcat connector must implement the org.apache.catalina.Connector interface. Of many methods in this interface, the most important are getContainer, setContainer, createRequest, and createResponse.

一个Tomcat连接器必须实现org.apache.catalina.Connector接口。

在这个接口中有许多方法,其中最重要的是 getContainersetContainercreateRequestcreateResponse

setContainer is used to associate the connector with a container. getContainer returns the associated container. createRequest constructs a request object for the incoming HTTP request and createResponse creates a response object.

setContainer 用于将连接器与一个容器关联起来。

getContainer 返回关联的容器。createRequest 构建用于传入的HTTP请求的请求对象,而createResponse 则创建响应对象。

The org.apache.catalina.connector.http.HttpConnector class is an implementation of the Connector interface and is discussed in the next section, "The HttpConnector Class". Now, take a close look at Figure 4.1 for the UML class diagram of the default connector. Note that the implementation of the Request and Response interfaces have been omitted to keep the diagram simple. The org.apache.catalina prefix has also been omitted from the type names, except for the SimpleContainer class.

org.apache.catalina.connector.http.HttpConnector类是Connector接口的一个实现,并在下一节“HttpConnector类”中进行了讨论。

现在,请仔细看一下图4.1,这是默认连接器的UML类图。

请注意,为了保持图表简洁,省略了Request和Response接口的实现。

除了SimpleContainer类之外,类型名称中省略了org.apache.catalina前缀。

Figure 4.1: The default connector class diagram

Figure 4.1: The default connector class diagram

图4.1:默认连接器类图

Therefore, Connector should be read org.apache.catalina.Connector,util.StringManager org.apache.catalina.util.StringManager, etc.

因此,Connector 应该读作org.apache.catalina.Connectorutil.StringManager应该读作org.apache.catalina.util.StringManager,等等。

A Connector has one-to-one relationship with a Container. The navigability of the arrow representing the relationship reveals that the Connector knows about the Container but not the other way around. Also note that, unlike in Chapter 3, the relationship between HttpConnector and HttpProcessor is one-to many.

ConnectorContainer之间是一对一的关系。

箭头表示的关系的可导航性表明Connector知道Container,但Container不知道Connector

还要注意,与第3章不同,HttpConnectorHttpProcessor之间的关系是一对多的关系。

The HttpConnector Class(HttpConnector 类)

You already know how tnector.http.HttpConnector was explained in Chapter 3. It implements org.apache.catalina.Connector (to make it eligible to work with Catalina), java.lang.Runnable (so that its instance can work in its own threahis class works because the simplified version of org.apache.catalina.cond), and org.apache.catalina.Lifecycle. The Lifecycle interface is used to maintain the life cycle of every Catalina component that implements it.

你已经知道这个类是如何工作的,因为第 3 章已经解释了 org.apache.catalina.connector.http.HttpConnector 的简化版本。

它实现了 org.apache.catalina.Connector(使其有资格与 Catalina 一起工作)、java.lang.Runnable(使其实例可以在自己的线程中工作)和 org.apache.catalina.Lifecycle 接口。

Lifecycle 接口用于维护实现该接口的每个 Catalina 组件的生命周期。

Lifecycle is explained in Chapter 6 and for now you don't have to worry about it except to know this: by implementing Lifecycle, after you have created an instance of HttpConnector, you should call its initialize and start methods. Both methods must only called once during the life time of the component. We will now look at those aspects that are different from the HttpConnector class in Chapter 3: how HttpConnector creates a server socket, how it maintains a pool of HttpProcessor, and how it serves HTTP requests.

生命周期在第6章中有详细解释,目前你不需要担心它,只需要知道以下内容:在实现生命周期时,在创建 HttpConnector 实例后,应该调用它的 initializestart 方法。

这两个方法在组件的生命周期中只能调用一次。

现在我们来看看与第3章中的HttpConnector类不同的方面:HttpConnector 如何创建服务器套接字、如何维护HttpProcessor池以及如何提供HTTP请求服务。

Creating a Server Socket (创建服务器套接字)

The initialize method of HttpConnector calls the open private method that returns an instance of java.net.ServerSocket and assigns it to serverSocket. However, instead of calling the java.net.ServerSocket constructor, the open method obtains an instance of ServerSocket from a server socket factory. If you want to know the details of this factory, read the ServerSocketFactory interface and the DefaultServerSocketFactory class in the org.apache.catalina.net package. They are easy to understand.

HttpConnectorinitialize 方法调用open私有方法,该方法返回一个 java.net.ServerSocket 实例,并将其赋值给 serverSocket

但是,open 方法不会调用java.net.ServerSocket的构造函数,而是从服务器套接字工厂获取ServerSocket的实例。

如果你想了解这个工厂的详细信息,请阅读org.apache.catalina.net包中的 ServerSocketFactory 接口和 DefaultServerSocketFactory 类。它们很容易理解。

Maintaining HttpProcessor Instances(维护HttpProcessor实例)

In Chapter 3, the HttpConnector instance had only one instance of HttpProcessor at a time, so it can only process one HTTP request at a time. In the default connector, the HttpConnector has a pool of HttpProcessor objects and each instance of HttpProcessor has a thread of its own. Therefore, the HttpConnector can serve multiple HTTP requests simultaneously.

在第3章中,HttpConnector 实例一次只能有一个 HttpProcessor 实例,因此它一次只能处理一个 HTTP 请求。

在默认连接器中,HttpConnector 有一个 HttpProcessor 对象池,每个HttpProcessor实例都有自己的线程。

因此,HttpConnector 可以同时处理多个 HTTP 请求。

The HttpConnector maintains a pool of HttpProcessor instances to avoid creating HttpProcessor objects all the time. The HttpProcessor instances are stored in a java.io.Stack called processors:

HttpConnector 维护 HttpProcessor 实例的对象池,以避免频繁创建 HttpProcessor 对象。

HttpProcessor 实例存储在一个名为 processorsjava.io.Stack 中。

private Stack processors = new Stack();

In HttpConnector, the number of HttpProcessor instances created is determined by two variables: minProcessors and maxProcessors. By default, minProcessors is set to 5 and maxProcessors 20, but you can change their values through the setMinProcessors and setMaxProcessors methods.

HttpConnector 中,创建的 HttpProcessor 实例数量由两个变量决定:minProcessorsmaxProcessors

默认情况下,minProcessors 设置为 5,maxProcessors 设置为 20,但你可以通过 setMinProcessorssetMaxProcessors 方法更改它们的值。

protected int minProcessors = 5;
private int maxProcessors = 20;

Initially, the HttpConnector object creates minProcessors instances of HttpProcessor. If there are more requests than the HttpProcessor instances can serve at a time, the HttpConnector creates more HttpProcessor instances until the number of instances reaches maxProcessors. After this point is reached and there are still not enough HttpProcessor instances, the incoming HTTP requests will be ignored. If you want the HttpConnector to keep creating HttpProcessor instances, set maxProcessors to a negative number. In addition, the curProcessors variable keeps the current number of HttpProcessor instances.

最初,HttpConnector 对象会创建 HttpProcessorminProcessors 实例。

如果请求数量超过了 HttpProcessor 实例一次所能处理的数量,HttpConnector 就会创建更多的 HttpProcessor 实例,直到实例数量达到 maxProcessors

在达到这个数量后,如果仍没有足够的 HttpProcessor 实例,传入的 HTTP 请求将被忽略。

如果希望 HttpConnector 继续创建 HttpProcessor 实例,可将 maxProcessors 设为负数。

此外,curProcessors 变量会保留当前的 HttpProcessor 实例数量。

Here is the code that creates an initial number of HttpProcessor instances in the HttpConnector class's start method:

下面是在 HttpConnector 类的 start 方法中创建 HttpProcessor 实例初始数量的代码:

while (curProcessors < minProcessors) {
     if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
         break;
     HttpProcessor processor = newProcessor();
     recycle(processor);
 }

The newProcessor method constructs a new HttpProcessor object and increments curProcessors. The recycle method pushes the HttpProcessor back to the stack.

newProcessor 方法会构建一个新的 HttpProcessor 对象,并递增 curProcessors

recycle 方法会将 HttpProcessor 推回堆栈。

Each HttpProcessor instance is responsible for parsing the HTTP request line and headers and populates a request object. Therefore, each instance is associated with a request object and a response object. The HttpProcessor class's constructor contains calls to the HttpConnector class's createRequest and createResponse methods.

每个 HttpProcessor 实例都负责解析 HTTP 请求行和标题,并填充一个请求对象。

因此,每个实例都与一个请求对象和一个响应对象相关联。

HttpProcessor 类的构造函数包含对 HttpConnector 类的 createRequestcreateResponse 方法的调用。

Serving HTTP Requests

The HttpConnector class has its main logic in its run method, just like in Chapter 3. The run method contains a while loop where the server socket waits for an HTTP request until the HttpConnector is stopped.

HttpConnector 类的主要逻辑在其 run 方法中,就像在第 3 章中一样。

运行方法包含一个 while 循环,服务器套接字在此循环中等待 HTTP 请求,直到 HttpConnector 停止。

while (!stopped) {
 Socket socket = null;
 try {
 socket = serverSocket.accept();
 ...

For each incoming HTTP request, it obtains an HttpProcessor instance by calling the createProcessor private method.

对于每个传入的 HTTP 请求,它都会通过调用 createProcessor 私有方法获取一个 HttpProcessor 实例。

HttpProcessor processor = createProcessor();

However, most of the time the createProcessor method does not create a new HttpProcessor object. Instead, it gets one from the pool. If there is still an HttpProcessor instance in the stack, createProcessor pops one. If the stack is empty and the maximum number of HttpProcessor instances has not been exceeded, createProcessor creates one. However, if the maximum number has been reached, createProcessor returns null. If this happens, the socket is simply closed and the incoming HTTP request is not processed.

然而,大部分情况下,createProcessor 方法并不会创建一个新的HttpProcessor对象。

相反,它会从池中获取一个对象。如果堆栈中仍然有HttpProcessor实例,createProcessor 会弹出一个。

如果堆栈为空且尚未超过最大的 HttpProcessor 实例数量,createProcesso r会创建一个新的实例。

然而,如果已达到最大数量,createProcessor 会返回null

如果发生这种情况,套接字将被简单地关闭,而传入的HTTP请求将不会被处理。

if (processor == null) {
 try {
 log(sm.getString("httpConnector.noProcessor"));
 socket.close();
 }
 ...
 continue;

If createProcessor does not return null, the client socket is passed to the HttpProcessor class's assign method:

如果 createProcessor 没有返回 null,客户端套接字就会传递给 HttpProcessor 类的 assign 方法:

processor.assign(socket);

It's now the HttpProcessor instance's job to read the socket's input stream and parse the HTTP request. An important note is this. The assign method must return straight away and not wait until the HttpProcessor finishes the parsing, so the next incoming HTTP request can be served. Since each HttpProcessor instance has a thread of its own for the parsing, this is not very hard to achieve. You will see how this is done in the next section, "The HttpProcessor Class".

现在由HttpProcessor实例来负责读取套接字的输入流并解析HTTP请求。

一个重要的注意事项是,assign 方法必须立即返回,而不是等待HttpProcessor完成解析,以便可以处理下一个传入的HTTP请求。

由于每个HttpProcessor实例都有自己的线程来进行解析,所以这并不难实现。

您将在下一节“HttpProcessor类”中看到如何实现这一点。

The HttpProcessor Class

The HttpProcessor class in the default connector is the full version of the similarly named class in Chapter 3. You've learned how it worked and in this chapter we're most interested in knowing how the HttpProcessor class makes its assign method asynchronous so that the HttpConnector instance can serve many HTTP requests at the same time.

默认连接器中的HttpProcessor类是第3章中同名类的完整版本。

您已经了解了它的工作原理,在本章中,我们最感兴趣的是了解HttpProcessor类如何使其assign方法异步,以便HttpConnector实例可以同时处理多个HTTP请求。

Note Another important method of the HttpProcessor class is the private process method which parses the HTTP request and invoke the container's invoke method. We'll have a look at it in the section, "Processing Requests" later in this chapter.

注意,HttpProcessor类的另一个重要方法是私有的process方法,它解析HTTP请求并调用容器的invoke方法。我们将在本章的"处理请求"部分中详细介绍。

In Chapter 3, the HttpConnector runs in its own thread. However, it has to wait for the currently processed HTTP request to finish before it can process the next: request. Here is part of the HttpConnector class's run method in Chapter 3:

在第3章中,HttpConnector 在自己的线程中运行。

然而,在处理完当前的HTTP请求之前,它必须等待才能处理下一个请求。

以下是第3章中 HttpConnectorrun 方法的部分内容:

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);
     }
 }

The process method of the HttpProcessor class in Chapter 3 is synchronous. Therefore, its run method waits until the process method finishes before accepting another request.

第三章的 HttpProcessor 类的处理方法是同步的。

因此,在其运行方法中,会等待处理方法完成后才接受另一个请求。

In the default connector, however, the HttpProcessor class implements java.lang.Runnable and each instance of HttpProcessor runs in its own thread, which we call the "processor thread". For each HttpProcessor instance the HttpConnector creates, its start method is called, effectively starting the "processor thread" of the HttpProcessor instance. Listing 4.1 presents the run method in the HttpProcessor class in the default connector:

然而,在默认连接器中,HttpProcessor 类实现了 java.lang.Runnable 接口,每个 HttpProcessor 实例都在自己的线程中运行,我们称之为“处理器线程”。

对于每个HttpProcessor实例,HttpConnector会调用其start方法,从而有效地启动HttpProcessor实例的“处理器线程”。

清单4.1展示了默认连接器中 HttpProcessor 类的运行方法:

Listing 4.1: The HttpProcessor class's run method.

清单4.1:HttpProcessor 类的运行方法。

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();
     }
}

The while loop in the run method keeps going in this order: gets a socket, process it, calls the connector's recycle method to push the current HttpProcessor instance back to the stack. Here is the HttpConenctor class's recycle method:

运行方法中的 while 循环按以下顺序进行:

  1. 获取套接字
  2. 处理套接字
  3. 调用连接器的回收方法将当前 HttpProcessor 实例推回堆栈

下面是 HttpConenctor 类的回收方法:

void recycle(HttpProcessor processor) {
     processors.push(processor);
}

Notice that the while loop in the run method stops at the await method. The await method holds the control flow of the "processor thread" until it gets a new socket from the HttpConnector. In other words, until the HttpConnector calls the HttpProcessor instance's assign method. However, the await method runs on a different thread than the assign method. The assign method is called from the run method of the HttpConnector. We name the thread that the HttpConnector instance's run method runs on the "connector thread". How does the assign method tell the await method that it has been called? By using a boolean called available, and by using the wait and notifyAll methods of java.lang.Object.

请注意,run 方法中的 while 循环在await方法处停止。

await 方法会阻止“处理器线程”的控制流,直到从HttpConnector获取到一个新的套接字。

换句话说,直到HttpConnector调用HttpProcessor实例的assign方法。

然而,await 方法在与assign方法不同的线程上运行。assign 方法是从 HttpConnectorrun 方法中调用的。

我们将 HttpConnector 实例的 run 方法运行的线程称为“连接器线程”。

assign 方法如何告诉 await 方法它已被调用呢?

通过使用一个名为available的布尔值,并使用java.lang.ObjectwaitnotifyAll 方法。

Note The wait method causes the current thread to wait until another thread invokes the notify or the notifyAll method for this object.

注意wait方法导致当前线程等待,直到另一个线程为该对象调用notify或notifyAll方法。

Here is the HttpProcessor class's assign and await methods:

下面是HttpProcessor类的assignawait方法:

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 program flows of both methods are summarized in Table 4.1.

表 4.1 概述了这两种方法的程序流程。

Table 4.1: Summary of the await and assign method

表 4.1: await 和 assign 方法摘要

The processor thread (the await method) The connector thread (the assign method)

处理器线程(await 方法 连接器线程(assign 方法)

while (!available) { 
wait(); 
} 
Socket socket = this.socket; 
available = false; 
notifyAll(); 
return socket; // to the run 
// method 


The connector thread (the assign method)

连接器线程(分配方法)


while (available) { 
    wait(); 
} 
this.socket = socket; 
available = true; 
notifyAll(); 
... 

Initially, when the "processor thread" has just been started, available is false, so the thread waits inside the while loop (see Column 1 of Table 4.1). It will wait until another thread calls notify or notifyAll. This is to say that calling the wait method causes the "processor thread" to pause until the "connector thread" invokes the notifyAll method for the HttpProcessor instance.

最初,当“处理器线程”刚刚启动时,availablefalse,因此线程会在while循环内等待(见表4.1的第1列)。

它将一直等待,直到另一个线程调用 notifynotifyAll

这意味着调用wait方法会导致“处理器线程”暂停,直到“连接器线程”为 HttpProcessor 实例调用notifyAll方法。

Now, look at Column 2. When a new socket is assigned, the "connector thread" calls the HttpProcessor's assign method. The value of available is false, so the while loop is skipped and the socket is assigned to the HttpProcessor instance's socket variable:

现在,看看第2列。

当分配了新的套接字时,“连接器线程”会调用 HttpProcessorassign 方法。

available 的值为false,因此跳过 while 循环,并将套接字分配给 HttpProcessor 实例的socket变量:

this.socket = socket;

The "connector thread" then sets available to true and calls notifyAll. This wakes up the processor thread and now the value of available is true so the program controls goes out of the while loop: assigning the instance's socket to a local variable, sets available to false, calls notifyAll, and returns the socket, which eventually causes the socket to be processed.

"连接器线程"然后将available设置为true并调用notifyAll

这唤醒了处理器线程,现在available的值为true,所以程序控制流程退出while循环:将实例的socket赋给一个局部变量,将available设置为false,调用notifyAll,并返回socket,最终导致socket被处理。

Why does the await method need to use a local variable (socket) and not return the instance's socket variable? So that the instance's socket variable can be assigned to the next incoming socket before the current socket gets processed completely.

await 方法为什么需要使用一个局部变量(socket),而不是返回实例的socket变量?

这样可以在当前socket完全处理之前将实例的socket变量分配给下一个传入的socket

Request Objects

The HTTP Request object in the default connector is represented by the org.apache.catalina.Request interface. This interface is directly implemented by the RequestBase class, which is the parent of HttpRequest. The ultimate implementation is HttpRequestImpl, which extends HttpRequest. Like in Chapter 3, there are facade classes: RequestFacade and HttpRequestFacade. The UML diagram for the Request interface and its implementation classes is given in Figure 4.2. Note that except for the types belonging to the javax.servlet and javax.servlet.http packages, the prefix org.apache.catalina has been omitted.

默认连接器中的HTTP请求对象由org.apache.catalina.Request接口表示。

该接口直接由RequestBase类实现,该类是HttpRequest的父类。

最终的实现是HttpRequestImpl,它扩展了HttpRequest

就像在第3章中一样,有外观类:RequestFacadeHttpRequestFacade

请求接口及其实现类的UML图如图4.2所示。

请注意,除了属于javax.servletjavax.servlet.http包的类型之外,前缀org.apache.catalina已被省略。

Figure 4.2: The Request interface and related types

Figure 4.2: The Request interface and related types

图 4.2:请求接口和相关类型

If you understand about the request object in Chapter 3, you should not have problems understanding the diagram.

如果你在第 3 章中了解了请求对象,那么理解本图应该不成问题。

Response Objects

Figure 4.3: The Response interface and its implementation classes

Figure 4.3: The Response interface and its implementation classes

图 4.3: 响应接口及其实现类

Processing Requests

At this point, you already understand about the request and response objects and how the HttpConnector object creates them. Now is the last bit of the process. In this section we focus on the process method of the HttpProcessor class, which is called by the HttpProcessor class's run method after a socket is assigned to it. The process method does the following:

此时,您已经了解了关于请求和响应对象以及HttpConnector对象如何创建它们的内容。

现在是整个过程的最后一部分。在本节中,我们重点关注HttpProcessor类的process方法,该方法在套接字分配给它后由HttpProcessor类的run方法调用。

process方法执行以下操作:

  • parse the connection
  • parse the request
  • parse headers
  • 解析连接
  • 解析请求
  • 解析头部

Each operation is discussed in the sub-sections of this section after the process method is explained.

然后是一个 while 循环,不断读取输入流,直到 HttpProcessor 停止、出现异常或连接关闭。

The process method uses the boolean ok to indicate that there is no error during the process and the boolean finishResponse to indicate that the finishResponse method of the Response interface should be called.

process方法使用布尔值ok来指示在过程中没有错误,并使用布尔值finishResponse来指示应调用Response接口的finishResponse方法。

boolean ok = true;
boolean finishResponse = true;

In addition, the process method also uses the instance boolean variables keepAlive, stopped, and http11.keepAlive indicates that the connection is persistent, stopped indicates that the HttpProcessor instance has been stopped by the connector so that the process method should also stop, and http11 indicates that the HTTP request is coming from a web client that supports HTTP 1.1.

此外,该处理方法还使用了实例布尔变量keepAlivestoppedhttp11.keepAlive表示连接是否持久,stopped表示HttpProcessor实例已被连接器停止,因此处理方法也应该停止,http11表示HTTP请求来自支持HTTP 1.1的Web客户端。

Like in Chapter 3, a SocketInputStream instance is used to wrap the socket's input stream. Note that, the constructor of SocketInputStream is also passed the buffer size from the connector, not from a local variable in the HttpProcessor class. This is because HttpProcessor is not accessible by the user of the default connector. By putting the buffer size in the Connector interface, this allows anyone using the connector to set the buffer size.

就像在第3章中一样,SocketInputStream实例用于包装套接字的输入流。

需要注意的是,SocketInputStream 的构造函数也从连接器传递了缓冲区大小,而不是从 HttpProcessor 类的局部变量传递。

这是因为默认连接器的用户无法访问 HttpProcessor

通过将缓冲区大小放在连接器接口中,这使得使用连接器的任何人都可以设置缓冲区大小。

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;
 }

Then, there is a while loop which keeps reading the input stream until the HttpProcessor is stopped, an exception is thrown, or the connection is closed.

然后是一个 while 循环,不断读取输入流,直到 HttpProcessor 停止、出现异常或连接关闭。

keepAlive = true;
while (!stopped && ok && keepAlive) {
...
}

Inside the while loop, the process method starts by setting finishResponse to true and obtaining the output stream and performing some initialization to the request and response objects.

while 循环中,进程方法首先将 finishResponse 设置为 true,然后获取输出流,并对请求和响应对象执行一些初始化操作。

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;
     }

Afterwards, the process method start parsing the incoming HTTP request by calling the parseConnection, parseRequest, and parseHeaders methods, all of which are discussed in the sub-sections in this section.

随后,处理方法通过调用 parseConnectionparseRequestparseHeaders 方法开始解析传入的HTTP请求,所有这些方法都在本节的子部分中讨论。

try {
 if (ok) {
 parseConnection(socket);
 parseRequest(input, output);
 if (!request.getRequest().getProtocol()
 .startsWith("HTTP/0"))
 parseHeaders(input);

The parseConnection method obtains the value of the protocol, which can be HTTP 0.9, HTTP 1.0 or HTTP 1.1. If the protocol is HTTP 1.0, the keepAlive boolean is set to false because HTTP 1.0 does not support persistent connections. The parseHeaders method will set the sendAck boolean to true if an Expect: 100-continue header is found in the HTTP request.

parseConnection 方法获取协议的值,该值可以是HTTP 0.9、HTTP 1.0或HTTP 1.1。

如果协议是HTTP 1.0,则将 keepAlive 布尔值设置为false,因为HTTP 1.0不支持持久连接。

如果在HTTP请求中找到Expect: 100-continue头部,parseHeaders 方法将设置 sendAck 布尔值为 true。

If the protocol is HTTP 1.1, it will respond to the Expect: 100-continue header, if the web client sent this header, by calling the ackRequest method. It will also check if chunking is allowed.

如果协议是HTTP 1.1,并且 web 客户端发送了Expect: 100-continue头部,它将通过调用ackRequest方法来响应。它还将检查是否允许分块传输。

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);
 }

The ackRequest method checks the value of sendAck and sends the following string if sendAck is true:

ackRequest 方法检查 sendAck 的值,如果sendAck为true,则发送以下字符串:

HTTP/1.1 100 Continue\r\n\r\n

During the parsing of the HTTP request, one of the many exceptions might be thrown. Any exception will set ok or finishResponse to false. After the parsing, the process method passes the request and response objects to the container's invoke method.

在解析HTTP请求期间,可能会抛出许多异常之一。

任何异常都会将okfinishResponse设置为false。解析完成后,process 方法将请求和响应对象传递给容器的invoke方法。

try {
     ((HttpServletResponse) response).setHeader
     ("Date", FastHttpDateFormat.getCurrentDate());
     if (ok) {
     connector.getContainer().invoke(request, response);
     }
 }

Afterwards, if finishResponse is still true, the response object's finishResponse method and the request's object finishRequest methods are called, and the output is flushed.

之后,如果finishResponse仍然为true,则调用响应对象的finishResponse方法和请求对象的finishRequest方法,并刷新输出。

if (finishResponse) {
 ...
 response.finishResponse();
 ...
 request.finishRequest();
 ...
 output.flush();

The last part of the while loop checks if the response's Connection header has been set to close from inside the servlet or if the protocol is HTTP 1.0. If this is the case, keepAlive is set to false. Also, the request and response objects are then recycled.

while 循环的最后一部分检查响应的 Connection 头是否已从 servlet 内部设置为关闭,或者协议是否为 HTTP 1.0。

如果是这种情况,keepAlive 就会被设置为 false

此外,请求和响应对象随后被回收利用。

  
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();  
}

At this stage, the while loop will start from the beginning if keepAlive is true, there is no error during the previous parsing and from the container's invoke method, or the HttpProcessor instance has not been stopped. Otherwise, the shutdownInput method is called and the socket is closed.

在这个阶段,如果keepAlivetrue,并且在之前的解析过程中没有错误以及从容器的调用方法中,或者HttpProcessor 实例没有被停止,那么while循环将从头开始。

否则,将调用shutdownInput方法并关闭套接字。

try {
     shutdownInput(input);
     socket.close();
 }
 ..

The shutdownInput method checks if there are any unread bytes. If there are, it skips those bytes.

shutdownInput 方法检查是否有任何未读取的字节。

如果有,它会跳过这些字节。

Parsing the Connection

The parseConnection method obtains the Internet address from the socket and assigns it to the HttpRequestImpl object. It also checks if a proxy is used and assigns the socket to the request object. The parseConnection method is given in Listing 4.2.

parseConnection 方法从套接字中获取互联网地址,并将其分配给 HttpRequestImpl 对象。

它还检查是否使用代理,并将套接字分配给请求对象。parseConnection 方法在清单 4.2 中给出。

Listing 4.2: The parseConnection method

清单 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);  
}

Parsing the Request

The parseRequest method is the full version of the similar method in Chapter 3. If you understand Chapter 3 well, you should be able to understand how this method works by reading the method.

parseRequest 方法是第3章中类似方法的完整版本。

如果你对第3章理解良好,通过阅读该方法,你应该能够理解这个方法的工作原理。

Parsing Headers

The parseHeaders method in the default connector uses the HttpHeader and DefaultHeaders classes in the org.apache.catalina.connector.http package. The HttpHeader class represents an HTTP request header. Instead of working with strings like in Chapter 3, the HttpHeader class uses character arrays to avoid expensive string operations. The DefaultHeaders class is a final class containing the standard HTTP request headers in character arrays:

默认连接器中的 parseHeaders 方法使用org.apache.catalina.connector.http包中的HttpHeaderDefaultHeaders类。

HttpHeader类表示HTTP请求头。

与第3章中使用字符串不同,HttpHeader 类使用字符数组来避免昂贵的字符串操作。

DefaultHeaders 类是一个包含标准HTTP请求头字符数组的最终类:

static final char[] AUTHORIZATION_NAME =
 "authorization".toCharArray();
 static final char[] ACCEPT_LANGUAGE_NAME =
 "accept-language".toCharArray();
 static final char[] COOKIE_NAME = "cookie".toCharArray();
 ...

The parseHeaders method contains a while loop that keeps reading the HTTP request until there is no more header to read. The while loop starts by calling the allocateHeader method of the request object to obtain an instance of empty HttpHeader. The instance is passed to the readHeader method of SocketInputStream.

parseHeaders 方法包含一个while循环,该循环不断读取HTTP请求,直到没有更多的头部可读取为止。

while循环首先调用请求对象的allocateHeader方法来获取一个空的 HttpHeader 实例。

该实例被传递给 SocketInputStreamreadHeader 方法。

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.

如果所有头信息都已读取,readHeader 方法将不为 HttpHeader 实例分配名称,此时就是 parseHeaders 方法返回的时间。

  
if (header.nameEnd == 0) {  
        if (header.valueEnd == 0) {  
        return;  
        }  
        else {  
        throw new ServletException  
        (sm.getString("httpProcessor.parseHeaders.colon"));  
        }  
}

If there is a header name, there must also be a header value:

如果有标头名称,就必须有标头值:

String value = new String(header.value, 0, header.valueEnd);

Next, like in Chapter 3, the parseHeaders method compares the header name with the standard names in DefaultHeaders. Note that comparison is performed between two character arrays, not between two strings.

接下来,与第 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();

The Simple Container Application

The main purpose of the application in this chapter is to show how to use the default connector. It consists of two classes:

本章中应用程序的主要目的是展示如何使用默认连接器。它由两个类组成:

ex04.pyrmont.core.SimpleContainer and ex04 pyrmont.startup.Bootstrap. The SimpleContainer class implements org.apache.catalina.container so that it can be associated with the connector. The Bootstrap class is used to start the application, we have removed the connector module and the ServletProcessor and StaticResourceProcessor classes in the application accompanying Chapter 3, so you cannot request a static page.

ex04.pyrmont.core.SimpleContainerex04.pyrmont.startup.BootstrapSimpleContainer类实现了org.apache.catalina.container,以便可以与连接器关联。

Bootstrap类用于启动应用程序,我们已经在附带第三章应用程序中删除了连接器模块以及ServletProcessorStaticResourceProcessor类,因此无法请求静态页面。

The SimpleContainer class is presented in Listing 4.3.

SimpleContainer 类在代码清单4.3中呈现。

Listing 4.3: The SimpleContainer class

代码清单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) {  
        }  
    }

I only provide the implementation of the invoke method in the SimpleContainer class because the default connector will call this method. The invoke method creates a class loader, loads the servlet class, and calls its service method. This method is very similar to the process method in the ServletProcessor class in Chapter 3.

我只提供SimpleContainer类中invoke方法的实现,因为默认连接器将调用该方法。

invoke方法创建一个类加载器,加载 servlet 类,并调用其service方法。这个方法与第3章中的ServletProcessor类中的process方法非常相似。

The Bootstrap class is given in Listing 4.4.

Bootstrap类见4.4节。

Listing 4.4: The ex04.pyrmont.startup.Bootstrap class

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();  
        }  
    }  
}

The main method of the Bootstrap class constructs an instance of org.apache.catalina.connector.http.HttpConnector and a SimpleContainer instance. It then associates the connector with the container by calling the connector's setContainer method, passing the container. Next, it calls the connector's initialize and start methods. This will make the connector ready for processing any HTTP request on port 8080.

Bootstrap 类的主要方法构造了org.apache.catalina.connector.http.HttpConnector的实例和一个SimpleContainer实例。

然后,通过调用连接器的setContainer方法,将连接器与容器关联起来,并传递容器作为参数。

接下来,调用连接器的initializestart方法。这将使连接器准备好在8080端口上处理任何HTTP请求。

You can terminate the application by pressing a key on the console.

您可以通过在控制台上按下一个键来终止应用程序。

Running the Application

To run the application in Windows, from the working directory, type the following:

要在 Windows 中运行应用程序,请在工作目录中键入以下内容:

java -classpath ./lib/servlet.jar;./ ex04.pyrmont.startup.Bootstrap

In Linux, you use a colon to separate two libraries.

Linux 中,使用冒号分隔两个库。

java -classpath ./lib/servlet.jar:./ ex04.pyrmont.startup.Bootstrap

You can invoke PrimitiveServlet and ModernServlet the way you did in Chapter 3. Note that you cannot request the index.html file because there is no processor for static resources.

你可以像在第3章那样调用PrimitiveServletModernServlet

请注意,你无法请求index.html文件,因为没有处理静态资源的处理器。

Summary

This chapter showed what it takes to build a Tomcat connector that can work with Catalina. It dissected the code of Tomcat 4's default connector and built a small application that used the connector. All applications in the upcoming chapters use the default connector.

本章展示了构建一个可以与Catalina一起工作的Tomcat连接器所需的步骤。

它剖析了Tomcat 4默认连接器的代码,并构建了一个使用该连接器的小应用程序。

在接下来的章节中,所有的应用程序都将使用默认连接器。


Xander
198 声望51 粉丝