coder 爱翻译 How Tomcat Works 第三章 第一部分第三章: Connector在正式开始这个应用之前,我们先以org.ap
coder 爱翻译 How Tomcat Works 第三章 第一部分
第三章: Connector
在正式开始这个应用之前,我们先以org.apache.catalina.util包下的StringManager作为开始。这个类用来处理在应用程序中和Cacalina本身错误信息的国际化。
The StringManager Class
一个像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。
The Application
开始这一部分,我们把应用分成模块。连接器模块所包含的类可以分组成为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 运行这个应用程序
Starting the Application
从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 ();} }