[How Tomcat Works]第3章 连接器(1)
[How Tomcat Works]第3章 连接器(一)译者 jarfield ? 博客 http://jarfield.iteye.com private static Has
[How Tomcat Works]第3章 连接器(一)
译者 jarfield ? 博客 http://jarfield.iteye.com
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;} ?
??? 提示:在附带的zip 文件中,可以找到一篇题为“The Singleton Pattern ”、关于单例模式的文章。
??? 举个例子,为了使用ex03.pyrmont.connector.http 包中的StringManager 类,传递包名给StringManager 的getManager 方法:
StringManager sm = StringManager.getManager("ex03.pyrmont.connector.http");???
??? 在 ex03.pyrmont.connector.http 包中,你可以找到三个属性文件:LocalStrings.properties 、LocalStrings_es.properties 和LocalStrings_ja.properties 。StringManager 实例根据应用程序运行时所在机器的区域(local )来决定使用哪个文件。如果你打开LocalStrings.properties ,非注释的第一行应该是这样的:
httpConnector.alreadyInitialized=HTTP connector has already been initialized
??? 要得到一条错误消息,你需要以错误码(error code )为参数调用StringManager 类的getString 方法。下面是该方法的多个重载之一:
public String getString(String key) ?
?? 以“httpConnector.alreadyInitialized ” 为参数调用getString 方法,就会返回“HTTP connector has already been initialized ”。
应用程序??? 从本章开始,每章附带的应用程序被化分成模块。本章的应用包括三个模块:connector 、startup 和core 。
??? startup 模块只包括一个类:Bootstrap ,其作用是启动整个应用。connector 模块的类可以分成5个类别:
connector 和它的支持(supporting )类(HttpConnector 和HttpProcessor )代表HTTP 请求的类(HttpRequest )及其支持类代表HTTP 响应的类(HttpResponse )及其支持类门面(Facade )类(HttpRequestFacade 和HttpResponseFacade )Constant 类?
??? core 模块包括两个类:ServletProcessor 和StaticResourceProcessor 。
??? Figure 3.1 是本应用的类图。为了让类图更具可读性,HttpRequest 和HttpResponse 相关的类都被省略了。我们后面讨论Request 和Response 对象时,会给出更加详细的类图。
?
??? 我们把Figure 3.1 和Figure 2.1 做个比较。第2 章的HttpServer 类被拆分成两个类:HttpConnector 和HttpProcessor ,Request 类被HttpRequest 类替换,Response 类被HttpResponse 类替换。而且,本章的应用使用了更多其他的类。
??? 第2章中的HttpServer 类负责等待HTTP 请求,创建请求对象和响应对象。本章应用中,等待HTTP 请求的任务交给了HttpConnector 实例,创建请求对象和响应对象的任务分配给了HttpProcessor 实例。
??? 本章中,HTTP 请求对象由实现了javax.servlet.http.HttpServletRequest 接口的HttpRequest 类来代表。HttpRequest 对象被转型为HttpServletRequest 实例,并传递给servlet 的service 方法。因此,每个HttpRequest 实例必须拥有适当的域,以便servlet 使用它们。需要赋给HttpRequest 对象的值包括URI 、query string 、参数、cookies 和其他headers 等等。因为连接器 不知道servlet 需要哪些值,所以它 必须解析所有能够从HTTP 请求获得的值。但是,解析HTTP 请求会带来昂贵(开销巨大)的字符串操作和其他操作。如果只解析servlet 需要的值,那么就可能节省大量的CPU 周期。例如,如果servlet 不需要任何请求(也就是,不调用javax.servlet.http.HttpServletRequest 的getParameter 、getParameterMap 、 getParameterNames或getParameterValues 方法),连接器 就不需要从query string 或HTTP request body 中解析出请求参数。Tomcat 的默认连接器 (包括本章应用中的连接器 )尝试通过“直到真正需要时才解析请求参数”的方式来提高效率。Tomcat 的默认连接器 和我们的连接器 使用SocketInputStream 类从Socket 的InputStream 中读取字节流。SocketInputStream 实例包装了Socket 的getInputStream 返回的java.io.InputStream 实例。SocketInputStream 类提供了两个重要方法:readRequestLine 和readHeader 。readRequestLine 返回HTTP 请求的第一行,即包括URI 、HTTP 方法(method )和HTTP 版本的那一行。处理套接字输入流中的字节流就意味着,从第一个字节读取到最后一个字节(从不回退),因此readRequestLine 必须只能被调用一次,而且必须在readHeader 方法之前调用。每调用一次readHeader 就可以读取一个header 名 /值对,而且应该重复调用直到所有的headers 都被读取。readRequestLine 的返回值是一个HttpRequestLine 实例,readHeader 的返回值是一个HttpHeader 对象。我们将在下面讨论HttpRequestLine 和HttpHeader 。
??? HttpProcessor 对象负责创建HttpRequest 实例,因此必须填充HttpRequest 实例的每个成员变量。HttpProcess 类使用它的parse 方法来解析HTTP 请求的request line 和headers 。parse 方法的返回被赋值给HttpProcessor 对象的成员变量。但是,parse 方法并不解析query string 和request body 中的请求参数。这个任务留给了HttpRequest 对象自己(译者注:这就是延迟解析)。只有servlet 需要一个参数时,query stirng 或request body 才会被解析。
??? 在前一章基础上的另一个改进,就是引入了启动类ex03.pyrmont.startup.Bootstrap 来启动整个应用。
??? 我们将在下面这些小节中,详细解释本章的应用:
启动应用- 连接器 创建HttpRequest 对象创建HttpResponse 对象静态资源处理器和serlvet 处理器运行应用
启动应用 ?? 我们从ex03.pyrmont.startup.Bootstrap 类启动整个应用。Listing 3.1 列出了该类的代码。
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 类的main 方法创建了一个HttpConnector 实例,并调用了它的start 方法。Listing 3.2 列出了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 (); }}?连接器 ??? ex03.pyrmont.connector.http.HttpConnector 类代表了连接器 ,其职责是创建等待HTTP 请求的服务器套接字。Listing 3.2 给出了该类的代码。
??? HttpConnector 类实现了java.lang.Runnable 接口,因此它可以被自己的线程使用。当启动应用时,HttpConnector 的一个实例被创建,并执行其run 方法。
??? 提示:你可以阅读文章“ Working with Threads ”来回忆如何创建 Java 线程。
??? run 方法包括了一个while 循环,用来做下面的事情:
等待HTTP 请求为每个请求创建HttpProcessor 实例调用HttpProcessor 的process 方法?
??? 提示: run 方法和第 2 章中 HttpServer1 类的 await 方法是相同的。
??? 马上你就能看到,HttpConnector 类和ex02.pyrmont.HttpServer1 类非常相似。从java.net.ServerSocket 类的accept 方法获得一个socket 之后发生了变化,HttpConnector 类创建了一个HttpProcessor 实例,并以socket 为参数调用其process 方法。
??? 提示: HttpConnector 类拥有另一个名为 getSchema 的方法,该方法返回网络请求的 schema ( HTTP )。
??? HttpProcessor 类的process 方法接受HTTP 请求的socket 为参数。对于每个HTTP 请求,process 方法会做如下处理:
创建一个HttpRequest 对象
创建一个HttpResponse 对象解析HTTP 请求的第一行和headers ,并填充HttpRequest 对象传递HttpRequest 对象和HttpResponse 对象给ServletProcessor 或StaticResourceProcessor
? ? 就像第2 章里那样,ServletProcessor 调用了被请求的servlet 的service 方法,StaticResourceProcessor 发送静态资源的内容(给客户端)。
??? Listing 3.3 列出了process 方法的代码。
Listing 3.3: The HttpProcessor class's process method.? ?
?
? ??? ???? process 方法首先从获取socket 的输入流和输出流。注意,该方法使用的SocketInputStream 继承自java.io.InputStream 。
SocketInputStream input = null; OutputStream output = null; try { input = new SocketInputStream(socket.getInputStream(), 2048); output = socket.getOutputStream(); // Then, it creates an HttpRequest instance and an HttpResponse instance and assigns // the HttpRequest to the HttpResponse. // create HttpRequest object and parse request = new HttpRequest(input); // create HttpResponse object response = new HttpResponse(output); response.setRequest(request);
??? 本章应用的HttpResponse 类比第2 章的Response 类要复杂很多。举例来说,你可以通过调用HttpResponse 类的setHeader 方法向客户端发送headers 。
response.setHeader("Server", "Pyrmont Servlet Container"); ? ??? ??? 接下来,process 方法调用HttpProcessor 类的两个私有方法来解析请求。
parseRequest(input, output); parseHeaders (input);?
??? 然后,process方法根据请求URI的模式(pattern),将HttpRequest对象和HttpResponse对像甩给(hand off ... to)一个 ServletProcessor对象和一个 StaticResourceProcessor对象。
if (request.getRequestURI().startsWith("/servlet/")) { ServletProcessor processor = new ServletProcessor(); processor.process(request, response); } else { StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); } ?
??? 最后,process 方法关闭socket 。
socket.close(); ? ??? ??? 同样注意,HttpProcessor 类使用org.apache.catalina.util.StringManager 类来发送错误消息:
protected StringManager sm = StringManager.getManager("ex03.pyrmont.connector.http");?
??? HttpProcessor 类的私有方法——parseRequest 、parseHeaders 和normalize —— 被调用来帮助填充HttpRequest 对象。在下一节“创建HttpRequest 对象”,我们将讨论这些方法。
创建HttpRequest 对象??? HttpRequest 类实现了javax.servlet.http.HttpServletRequest 接口。附带还有一个叫做HttpRequestFacade 的门面类。Figure 3.2 展现了HttpRequest 和相关类的类图。
??? HttpRequest 类的许多方法都是留空的(等待第4章才会全部实现),但是servlet 程序员已经可以从HTTP 请求中获得headers 、cookies 和请求参数。这三种值被存储在下面的引用变量中:
protected HashMap headers = new HashMap(); protected ArrayList cookies = new ArrayList(); protected ParameterMap parameters = null; ?
??? 提示:我们会在“获取参数”小节解释 ParameterMap 类。
??? 因此,servlet 程序员可以从javax.servlet.http.HttpServletRequest 下面这些方法中获取正确的值:getCookies 、getDateHeader 、getHeader 、getHeaderNames 、getHeaders 、getParameter 、getPrameterMap 、getParameterNames 和getParameterValues 。正如你在HttpRequest类中看到的,一旦获得headers 、cookies 和请求参数,相关方法的实现就很简单了。
??? 不用说,这里主要的挑战就是解析HTTP请求和填充HttpRequest 对象。对于headers 和cookies ,HttpRequest 类提供了addHeader 和addCookie 方法,