coder 爱翻译 How Tomcat Works 第三章 第一部分

本文深入探讨了Tomcat中Connector模块的设计原理与实现细节,重点介绍了HTTP请求与响应处理流程、错误信息国际化处理机制及如何提高处理效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[size=x-large]第三章: Connector[/size]

在正式开始这个应用之前,我们先以org.apache.catalina.util包下的StringManager作为开始。这个类用来处理在应用程序中和Cacalina本身错误信息的国际化。

[size=medium]The StringManager Class[/size]

一个像Tomcat这样大的应用需要很小心地处理错误信息。Tomcat的错误信息对系统管理员和servlet程序员都是非常有用的。例如:Tomcat的logs错误信息可以让系统管理员很精确地定位异常的发生。对servlet程序员,Tomcat发送javax.servlet.ServletException抛出的特定的错误信息,让程序员知道他的servlet出了什么问题。

Tomcat的解决办法是在properties文件中存储错误信息,所以编辑它们就很容易。但是在Tomcat中有几百个类,存储所有的被使用类的错误信息在一个大的properties文件中很容易为维护带来麻烦(a maintenance nightmare)。为了避免这种情况,Tomcat为每一个包分配一个properties文件。例如:在org.apache.catalina.connector包下的properties文件包含所有在这包下类可能抛出的的错误信息。每个properties文件由org.apache.catalina.util.StringManager类的实例处理。当Tomcat一运行,就会有许多的StringMnager类的实例生成。每一个包对应一个StringManager实例来读取一个properties文件具体内容。由于Tomcat的广泛应用,提供多种语言的错误信息就显得很有必要了。现在支持有三种语言。英语版本的错误信息是叫做LocalStrings.properties的文件。另外两种是西班牙语和日语,分别为:LocalString_es和LocalString_js.properties文件。

当一个包中的类需要在该包的properties文件中查询一个错误信息时,它首先要获得一个StringManager实例。但是,同一个包下面的许多类可能需要一个StringManager。浪费资源来为每一个需要的错误信息的对象来创建一个StringManager实例。
如果你熟悉设计模式,你可以很正确地想到StringManager是一个单例类。唯一的构造函数是private修饰。所以你不能使用new关键字来类的外面实例化它。你可以通过传递一个包名,调用public static getManager方法来获得一个实例。每一个实例以它的包名为key存储在Hashtable中。

private static Hashtable managers = new Hashtable();
public synchronized static StringManager getManager(String packageName) {
StringManager mgr = (StringManager)managers.get(packageName);
if (mgr == null) {
mgr = new StringManager(packageName);
managers.put(packageName, mgr);
}
return mgr;
}


例如:使用ex03.pyrmont.connector.http包下的StringManager:

StringManager sm = StringManager.getManager("ex03.pyrmont.connector.http");

在ex03.pyrmont.connector.http包下,你可以找到3个properties文件:
LocalStrings.properties, LocalStrings_es.properties 和LocalStrings_ja.properties。这些文件会被StringManager实例使用。使用哪一个文件取决于服务器运行的应用的区域设置。如果你打开LocalStrings.properties文件,第一行没有注释的代码:

httpConnector.alreadyInitialized=HTTP connector has already been initialized

为了获取错误信息,可以使用StringManager类的getString方法。这个方法需要传递一个错误代号码(error code)。

public String getString(String key)

把httpConnector.alreadyInitialized作为参数传递给getString方法返回:
HTTP connector has already been initialized。

[size=medium]The Application[/size]

开始这一部分,我们把应用分成模块。连接器模块所包含的类可以分组成为5个类别:

 The connector and its supporting class (HttpConnector and HttpProcessor). 连接器和它支持的类
 The class representing HTTP requests (HttpRequest) and its supporting classes.代表HTTP请求和其支持的类.
 The class representing HTTP responses (HttpResponse) and its supporting classes.代表HTTP响应及其支持的类
 Facade classes (HttpRequestFacade and HttpResponseFacade). Facade类(门面类)
 The Constant class.

核心模块包含2个类:ServletProcessor和StaticResourceProcessor

第二章的HttpServer类是负责等待HTTP请求和生成request和response对象。在这章,等待HTTP请求的任务给了HttpConnector实例,而创建request和response对象的任务分配给了HttpProcessor实例。

在这章,HTTP请求对象就是实现了javax.servlet.http.HttpServletRequest接口的HttpRequest类。一个HttpRequest对象会被转型为一个HttpServletRequest实例,然后传递给servlet的service方法调用。此外,每一个HttpRequest实例必须填充它自己的字段(fields)以便servlet可以使用它们。HttpRequest对象中的包括URI,查询字符串,参数,cookies和其他头部信息等需要被赋值。因为连接器不知道哪个值需要被servlet调用,所以连接器必须解析所有HTTP请求获取的值。但是,解析一个HTTP请求包括字符串和其他操作是有很大开销的,连接器可以保存许多CPU周期以便只解析那些servlet真正只需要的值。例如:如果servlet不需要任何请求参数(不调用getParameter等javax.servlet.http.HttpServletRequest方法),这时连接器就不需要解析这些从查询字符串或者HTTP请求体的参数。Tomcat的默认连接器更加高效:在servlet到了真的需要的时候才去执行参数解析。

Tomcat默认的连接器和我们这个连接器使用SocketInputStream类来读取从socket的InputStream字节流。SocketInputStream的实例是包装了socket的getInputStream方法返回的java.io.InputStream实例。SocketInputStream类提供2个重要的方法:readRequestLine和readHeader。ReadRequestLine返回一个HTTP请求的第一行内容,例如:URI,方法(POST等),HTTP版本。因为处理来自socket的输入流的字节流意味着读取第一个字节到最后一个字节。(但是不会向回读取 backwards),所以readRequestLine必须只被调用一次且必须在调用readHeader方法之后。每次调用readHeader方法是用来获取一个头部信息的name/value对。它会被重复调用,直到读完所有的头信息。readRequestLine的返回值是一个HttpRequestLine实例,readHeader的返回值是一个HttpHeader对象。下面我们讨论HttpRequestLine和HttpHeader类。

HttpProcessor对象创建HttpRequest实例,然后必须填充他们的字段。HttpProcessor类,使用parse方法,解析一个HTTP请求的请求行和头部信息。这些被解析的结果会填充在HttpProcessor对象的字段中。但是,这parse方法不会解析在请求体或查询字符串的参数。这个任务交给了HttpRequest对象自己解决。只有servlet需要一个参数的时候,这个参数它才会在查询字符串或请求体中解析。

我们一下面几个小部分开始详细介绍这个应用:

 Starting the Application 开始这个应用程序
 The Connector 连接器
 Creating an HttpRequest Object 创建一个HttpRequest对象
 Creating an HttpResponse Object 创建一个HttpResponse对象
 Static resource processor and servlet processor 静态资源处理器和servlet处理器
 Running the Application 运行这个应用程序

[size=medium]Starting the Application[/size]

从Bootstrap类开始: bootstrap(自举或引导程序)

Listing 3.1: The Bootstrap class

package ex03.pyrmont.startup;
import ex03.pyrmont.connector.http.HttpConnector;
public final class Bootstrap {
public static void main(String[] args) {
HttpConnector connector = new HttpConnector(); connector.start();
}
}

Bootstrap类的主函数实例化了HttpConnector类和调用它的start方法。下面是HttpConnector:

Listing 3.2: The HttpConnector class's start method

package ex03.pyrmont.connector.http;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class HttpConnector implements Runnable {
boolean stopped;
private String scheme = "http";
public String getScheme() {
return scheme;
}
public void run() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!stopped) {
// Accept the next incoming connection from the server socket
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 start() {
Thread thread = new Thread(this);
thread.start ();
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值