论坛首页 Java企业应用论坛

[How Tomcat Works]第3章 连接器(一)

浏览 6176 次
精华帖 (3) :: 良好帖 (14) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-04-10   最后修改:2010-04-10

译者 jarfield  

博客 http://jarfield.iteye.com

概 述

    就像《简介》中介绍的,Catalina 中有两个主要模 块:Connector (连接器)和Container (容器)。本章,你将编写一个连接 器 来增强第2章的应用,该连接器 能够创建更好的RequestResponse 对 象。符合Servlet 2.32.4 规范的连接器 必须创建javax.servlet.http.HttpServletRequest 实例和javax.servlet.http.HttpServletResponse 实例,并将它们作 为参数传递给servletservice 方法。第2章的Servlet 容器只 能运行实现了javax.servlet.Servlet 接口的servlet ,并传递javax.servlet.ServletRequest 实 例和javax.servlet.ServletResponse 实例给servletservice 方 法。连接器 并不知道servlet 的类型(例如,是否实现了javax.servlet.Servlet 接 口, 继承了javax.servlet.GenericServlet ,或继承了javax.servlet.http.HttpServlet ), 因此它必须始终提供HttpServletRequest 实例和HttpServletResponse 实 例。

    本章的应用程序中,连接器 解析HTTP 请求的headers , 使得servlet 可以获得headerscookies 、参数名/值, 等等。我们也会完善第2章中Response 类的getWriter 方法,修正它的行为(译者注:第2 章中的实现是有问题的)。由于这些改进,我们可以从PrimitiveServlet 得 到完整的响应,同时也能够运行更加复杂的ModernServlet 。本章构建的连接器Tomcat 4 默认连接器 的一个简化版本,我们会在第4 章详细讨论Tomcat 4 的默认Connecotr 。虽然Tomcat 默 认连接器Tomcat 4 中已经不推荐使用了,但是它仍是一个很好的学习工具。在本章接下来的讨论中,凡是提到的连接器 ,都是指本章构建的连接器 ,而不是Tomcat 的默认连接器

    提示:与上一章的 应用不同,本章应用中连接器 和容器是分离的。
 
    本章应用的代码在ex03.pyrmont 包及其子包中。构成连接器 的类是ex03.pyrmont.connector 包 和ex03.pyrmont.connector.http 包的一部分。从本章开始, 每个应用都有一个bootstrap 类,用于启动整个应用。不过,目前还没有 停止应用的机制。应用一旦运行起来,你必须通过关闭控制台(Windows 平 台)或杀死进程(UNIX/Linux 平台)的粗鲁的方式来停止应用。

    在解释应用之前,请允许我先介绍org.apache.catalina.util 包 中的StringManager 类。该类负责处理本应用及Catalina 自身各模块的错误消息的国际化。然后,我们会讨论整个应用。

StringManager

    像Tomcat 这样的大型程序,都需要仔细地处理错误消息。在Tomcat 中, 错误消息对系统管理员和Servlet 程序员都很重要。例如,通过Tomcat 的错误日志,系统管理员可以轻松定位任何异常。Tomcat 为内部抛出的每个javax.servlet.ServletException 打 印出一条特定错误日志,这样Servlet 程序员就可以知道自己写的servlet 哪里出了问题。

    Tomcat 采用的方法是将错误消息存储在一个属性(properties )文件中,这样就可以方便地编辑错误消息。但是,Tomcat 有数百个类,如果将所有类的错误消息都存储在一个巨大的属性文件中,那么维护这些错误消息就是一个恶 梦。为了避免这个问题,Tomcat 为每个包定义了一个属性文件。例如,org.apache.catalina.connector 包中的属性文件包括了该包所有类抛 出的错误消息。每个属性文件都会被一个特定的org.apache.catalina.util.StringManager 实 例处理。Tomcat 运行的时侯,会有很多StringManager 实例,每个实例都会读取对应包中的属性文件。而且,由于Tomcat 十分流行,提供多语言版本的错误消息是很有意义的。目前,Tomcat 共支持三种语言。英语的属性文件名都是LocalStrings.properties 。其他两种语言是西班牙语和日语,其属性文件分别 为LocalStrings_es.propertiesLocalStrings_ja.properties

    当类需要在属性文件中查找错误消息时,它首先获取一个StringManager 实 例。但是,同一个包中很多类都可能需要一个StringManager 实例,如果为每 个需要错误消息的类对象创建一个StringManager 实例,则是对资源的浪费。 因此StringManager 类被设计成,同一个包中所有类对象可以共享一个StringManager 实例。如果熟悉设计模式,你可能会猜到StringManager 是一个单例类(singleton class )。StringManager 类 唯一的构造函数是私有的(private ),因此你不能使用new 关键字在类外部创建该类的实例。以包名为参数,调用StringManager 类的公开静态方法getManager , 就可以获得一个StringManager 实例。每个实例被存储在一个Hashtable 中,key 就是包的名称。

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 类,传递包名给StringManagergetManager 方法:

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

   

    ex03.pyrmont.connector.http 包 中,你可以找到三个属性文件:LocalStrings.propertiesLocalStrings_es.propertiesLocalStrings_ja.propertiesStringManager 实例根据应用程序运行时所在机器的区域(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 ”。

应用程序

    从本章开始,每章附带的应用程序被化分成模块。本章的应用包括三个模块:connectorstartupcore

    startup 模块只包括一个类:Bootstrap , 其作用是启动整个应用。connector 模块的类可以分成5个类别:

  • connector 和它的支持(supporting )类(HttpConnectorHttpProcessor
  • 代表HTTP 请求的类(HttpRequest )及 其支持类
  • 代表HTTP 响应的类(HttpResponse )及其支持类
  • 门面(Facade )类(HttpRequestFacadeHttpResponseFacade )
  • Constant

 
    core 模块包括两个类:ServletProcessorStaticResourceProcessor


    Figure 3.1 是本应用的类图。为了让类图更具可读性,HttpRequestHttpResponse 相关的类都被省略了。我们后面讨论RequestResponse 对 象时,会给出更加详细的类图。
 
    我们把Figure 3.1Figure 2.1 做个比较。第2 章的HttpServer 类被拆分成两个类:HttpConnectorHttpProcessorRequest 类被HttpRequest 类 替换,Response 类被HttpResponse 类 替换。而且,本章的应用使用了更多其他的类。

    第2章中的HttpServer 类 负责等待HTTP 请求,创建请求对象和响应对象。本章应用中,等待HTTP 请求的任务交给了HttpConnector 实 例,创建请求对象和响应对象的任务分配给了HttpProcessor 实例。

    本章中,HTTP 请求对象由实现了javax.servlet.http.HttpServletRequest 接口的HttpRequest 类来代表。HttpRequest 对 象被转型为HttpServletRequest 实例,并传递给servletservice 方 法。因此,每个HttpRequest 实例必须拥有适当的域,以便servlet 使用它们。需要赋给HttpRequest 对 象的值包括URIquery string 、参数、cookies 和 其他headers 等等。因为连接器 不知道servlet 需要哪些值,所以 必须解析所有能够从HTTP 请 求获得的值。但是,解析HTTP 请求会带来昂贵(开销巨大)的字符串操作和其 他操作。如果只解析servlet 需要的值,那么就可能节省大量的CPU 周期。例如,如果servlet 不 需要任何请求(也就是,不调用javax.servlet.http.HttpServletRequestgetParametergetParameterMap 、 getParameterNames或getParameterValues 方法),连接器 就不需要从query stringHTTP request body 中解析出 请求参数。Tomcat 的默认连接器 (包括本章应用中的连接器 )尝试通过“直 到真正需要时才解析请求参数”的方式来提高效率。Tomcat 的默认连接器 和我们的连接器 使 用SocketInputStream 类从SocketInputStream 中读取字节流。SocketInputStream 实例包装了SocketgetInputStream 返回的java.io.InputStream 实例。SocketInputStream 类提供了两个重要方法:readRequestLinereadHeaderreadRequestLine 返 回HTTP 请求的第一行,即包括URIHTTP 方法(method )和HTTP 版 本的那一行。处理套接字输入流中的字节流就意味着,从第一个字节读取到最后一个字节(从不回退),因此readRequestLine 必 须只能被调用一次,而且必须在readHeader 方法之前调用。每调用一次readHeader 就可以读取一个header 名 /值对,而且应该重复调用直到所有的headers 都被读取。readRequestLine 的返回值是一个HttpRequestLine 实 例,readHeader 的返回值是一个HttpHeader 对象。我们将在下面讨论HttpRequestLineHttpHeader

    HttpProcessor 对象负责创建HttpRequest 实 例,因此必须填充HttpRequest 实例的每个成员变量。HttpProcess 类使用它的parse 方 法来解析HTTP 请求的request lineheadersparse 方法的返回被赋值给HttpProcessor 对 象的成员变量。但是,parse 方法并不解析query stringrequest body 中 的请求参数。这个任务留给了HttpRequest 对象自己(译者注:这就是延迟解 析)。只有servlet 需要一个参数时,query stirngrequest 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 实例
  • 调用HttpProcessorprocess 方 法

 

    提示: 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 方法会做如下处 理:
  1. 创建一个HttpRequest 对象
  2. 创 建一个HttpResponse 对象
  3. 解析HTTP 请求的第一行和headers , 并填充HttpRequest 对象
  4. 传递HttpRequest 对象和HttpResponse 对 象给ServletProcessorStaticResourceProcessor

    就像第2 章里那样,ServletProcessor 调 用了被请求的servletservice 方法,StaticResourceProcessor 发 送静态资源的内容(给客户端)。

    Listing 3.3 列 出了process 方法的代码。

Listing 3.3: The HttpProcessor class's process method.   
 
public void process(Socket socket) {
   SocketInputStream input = null;
   OutputStream output = null;
   try {
     input = new SocketInputStream(socket.getInputStream(), 2048);
     output = socket.getOutputStream();
 
     // create HttpRequest object and parse
     request = new HttpRequest(input);
 
      // create HttpResponse object
     response = new HttpResponse(output);
     response.setRequest(request);
     response.setHeader("Server", "Pyrmont Servlet Container");
 
     parseRequest(input, output);
     parseHeaders(input);
 
     //check if this is a request for a servlet or a static resource
     //a request for a servlet begins with "/servlet/"
     if (request.getRequestURI().startsWith("/servlet/")) {
       ServletProcessor processor = new ServletProcessor();
       processor.process(request, response);
     }
     else {
       StaticResourceProcessor processor = new
         StaticResourceProcessor();
       processor.process(request, response);
     }
 
     // Close the socket
     socket.close();
     // no shutdown for this application
   }
   catch (Exception e) {
     e.printStackTrace ();
   }
} 
     
     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 类的私有方法——parseRequestparseHeadersnormalize —— 被调用来帮助填充HttpRequest 对象。在下一节“创建HttpRequest 对象”,我们将讨论这些方法。

创建HttpRequest 对象

    HttpRequest 类 实现了javax.servlet.http.HttpServletRequest 接 口。附带还有一个叫做HttpRequestFacade 的门面类。Figure 3.2 展现了HttpRequest 和 相关类的类图。
    HttpRequest 类 的许多方法都是留空的(等待第4章才会全部实现),但是servlet 程序员 已经可以从HTTP 请求中获得headerscookies 和请求参数。这 三种值被存储在下面的引用变量中:
   protected HashMap headers = new HashMap();
   protected ArrayList cookies = new ArrayList();
   protected ParameterMap parameters = null; 
 
    提示:我们会在“获取参数”小节解释 ParameterMap 类。

    因此,servlet 程序员可以从javax.servlet.http.HttpServletRequest 下面这些方法中获取正确的值:getCookiesgetDateHeadergetHeadergetHeaderNamesgetHeadersgetParametergetPrameterMapgetParameterNamesgetParameterValues 。正如你在HttpRequest类中看到的,一旦获得headerscookies 和 请求参数,相关方法的实现就很简单了。

    不用说,这里主要的挑战就是解析HTTP请求和填充HttpRequest 对象。对于headerscookiesHttpRequest 类 提供了addHeaderaddCookie 方 法,HttpProcessor 类的prseHeaders 就调用了这两个方法。 请求参数是在需要时才被HttpRequest 类 的parseParameters 方法解析的。本节所有的方法都会被讨论到。

    由于解析HTTP 请求是一个非常复杂的任务,因此本节被分成下面几个小节:
  • 读 取套接字 的输入流
  • 解析请求行(request line
  • 解析headers
  • 解析cookies
  • 获 取请求参数

读取套接字的输入流

    在第12 章中,ex01.pyrmont.HttpRequest 类和ex02.pyrmont.HttpRequest 类已经做了一部分解析HTTP 请求的工作。通过调用java.io.InputStream 类 的read 方法,我们可以从请求行获得HTTP 方法、URIHTTP 版本:

     byte[] buffer = new byte [2048];
     try {
       // input is the InputStream from the socket.
       i = input.read(buffer);
     } 


    第1、2章的应用中,我们没有尝试进一步解析HTTP请求。但是在本章的应用中,我们有了 ex03.pyrmont.connector.http.SocketInputStream
类——org.apache.catalina.connector.http.SocketInputStream 类 的一个拷贝。该类提供了一些方法,这些方法不但可以获得请求行,还可以获得headers

    要构造SocketInputStream 的实例,我们需要传递两个参数:InputStream 对象,指定SocketInputStream 实 例缓冲区大小的整数。在本应用中,我们在ex03.pyrmont.connector.http.HttpProcessor 类 的process 方法中创建了一个SocketInputStream 实 例,代码片段如下所示:

  SocketInputStream input = null;
     OutputStream output = null;
     try {
       input = new SocketInputStream(socket.getInputStream(), 2048);
       ... 

 

    正如前面提到的,使用SocketInputStream 类的原因是为了使用它的两 个重要方法:readRequestLinereadHeader 。继续往下读。

解析请求行

    HttpProcessor 类的process 方 法调用私有方法parseRequest 来解析请求行,即HTTP 请求的第一行。这里给出请求行的一个例子:

GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1 


   请求行 的第二部分是URI 和可选的query string 。在上面的例子中,URI是:

/myApp/ModernServlet 


    然后,问号之后的部分都是query stirng 。因此,query string 就是:

userName=tarzan&password=pwd


    query string 可 以包含0 或多个参数。在上面的例子中,有两个参数名/值对:username/tarzanpassword/pwd 。在Servlet/JSP 编 程中,jsessionid 参数用来携带会话标识(session identity)。会话标识通常嵌入在cookies 中,但是程序员可以选 择将会话标识嵌入在query string 中,例如在浏览器禁止cookie 的情况下。
  
    当parseRequest 方法被HttpProcessor 类 的process 方法调用时,变量request 已 经指向了一个HttpRequest 实例。parseRequest 方法解析了请求行,获得了几个值,并将它们赋给HttpRequest 对 象。现在,我们来看看Listing 3.4parseRequest 方法的代码。

Listing 3.4: The parseRequest method in the HttpProcessor class   

private void parseRequest(SocketInputStream input, OutputStream output)
   throws IOException, ServletException {
 
   // Parse the incoming request line
   input.readRequestLine(requestLine);
   String method =
     new String(requestLine.method, 0, requestLine.methodEnd);
   String uri = null;
   String protocol = new String(requestLine.protocol, 0,
     requestLine.protocolEnd);
    // Validate the incoming request line
   if (method, length () < 1) {
     throw new ServletException("Missing HTTP request method");
   }
   else if (requestLine.uriEnd < 1) {
     throw new ServletException("Missing HTTP request URI");
   }
   // Parse any query parameters out of the request URI
   int question = requestLine.indexOf("?");
   if (question >= 0) {
     request.setQueryString(new String(requestLine.uri, question + 1,
       requestLine.uriEnd - question - 1));
     uri = new String(requestLine.uri, 0, question);
   }
   else {
     request.setQueryString(null);
     uri = new String(requestLine.uri, 0, requestLine.uriEnd);
   }
 
   // Checking for an absolute URI (with the HTTP protocol)
   if (!uri.startsWith("/")) {
     int pos = uri.indexOf("://");
     // Parsing out protocol and host name
     if (pos != -1) {
       pos = uri.indexOf('/', pos + 3);
       if (pos == -1) {
         uri = "";
       }
       else {
         uri = uri.substring(pos);
       }
     }
   }
 
   // Parse any requested session ID out of the request URI
   String match = ";jsessionid=";
   int semicolon = uri.indexOf(match);
   if (semicolon >= 0) {
     String rest = uri.substring(semicolon + match,length());
     int semicolon2 = rest.indexOf(';');
     if (semicolon2 >= 0) {
       request.setRequestedSessionId(rest.substring(0, semicolon2));
       rest = rest.substring(semicolon2);
      }
     else {
       request.setRequestedSessionId(rest);
       rest = "";

     }
       request.setRequestedSessionURL (true);
       uri = uri.substring(0, semicolon) + rest;
     }
     else {
       request.setRequestedSessionId(null);
       request.setRequestedSessionURL(false);
     }

 
    如果找到jessionid ,也意味着会话标识由query string 来承载,而不在cookie 中。 因此,传递truerequest 对 象的setRequestSessionURL 方法。否则,传递falsesetRequestSessionURL 方 法,传递nullsetRequestSessionId 方 法。

    这时,uri 的值已经不包含jsessionid 。接着,parseRequest 方 法传递urinormalize 方 法,以纠正“异常(abnormal )”的URI。例如,任何\将被替换成 /。如果uri 的格式是正确的,或者异常已被纠正,normalize 方法就返回原来的uri , 或者被纠正的URI 。如果uri 不 能被纠正,normalize 方法会认为uri 不合法,并返回null 。在这种情况下(normalize 方法返回null ),parseRequest 方法将抛出一个异常。

    最后,parseRequest 方法设置HttpRequest 对 象的一些属性:

    ((HttpRequest) request).setMethod(method);
     request.setProtocol(protocol);
     if (normalizedUri != null) {
       ((HttpRequest) request).setRequestURI(normalizedUri);
     }
     else {
       ((HttpRequest) request).setRequestURI(uri);
     }


    并且,如果normalize 方法返回nullparseRequest 方法就抛出一个异常:

     if (normalizedUri == null) {
       throw new ServletException("Invalid URI: " + uri + "'");
     } 

解 析Headers

    HttpHeader 类描述了HTTP 头部。第4 章 将详细解释该类,现在我们只要知道下面几点就足够了:

  • 通过该类的无参构造函数创建HttpHeader 实例。
  • 一旦有了HttpHeader 实 例,你可以把它传递给SocketInputStreamreadHeader 方法。如果有header 可 以读,那么readHeader 方法会相应地填充HttpHeader 对象。如果没有header 可 以读,HttpHeadernameEndvalueEnd 域都被设置0
  • 要 获得header 的名称和值,可以使用下面的代码:
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);

 

    parseHeaders 方法包括了一个while 循 环,持续从SocketInputStream 中读取headers ,直到没有header 为 止。该循环首先创建一个HttpHeader 对象,并将它传递给SocketInputStream 类的readHeader 方 法:
      HttpHeader header = new HttpHeader();
      // Read the next header
      input.readHeader(header); 
 
    然后,通过测试 HttpHeader 实例的nameEndvalueEnd 域来判断 输入流中有没有更多的header 可以读取:
if (header.nameEnd == 0) {
        if (header.valueEnd == 0) {
          return;
        }
        else {
          throw new ServletException
            (sm.getString("httpProcessor.parseHeaders.colon"));
        }
      }
 
    如果有下一个header ,就可以获取header 的名称和值:
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd); 
 
    一旦获得了header 的名称和值,就调用HttpRequest 对 象的addHeader 方法,将它们添加到用于存储headerHashMap 中:
request.addHeader(name, value); 
 
    有的header 还需要设置一些属性。例如,当servlet 调 用javax.servlet.ServletRequestgetContentLength 方法时,就返回content-length 的值。包含cookiecookie header 需要被添加到cookie 集合(collection )中。 于是,我们还需要做下面的处理:
if (name.equals("cookie")) {
        ... // process cookies here
      }
      else if (name.equals("content-length")) {
        int n = -1;
        try {
          n = Integer.parseInt (value);
        }
        catch (Exception e) {
          throw new ServletException(sm.getString(
            "httpProcessor.parseHeaders.contentLength"));
        }
        request.setContentLength(n);
      }
      else if (name.equals("content-type")) {
         request.setContentType(value);
      } 
 
    Cookie 的解析在下一节“解析Cookies ”中讨论。

解析Cookies

    Cookies 是作为HTTP 请求header 被 浏览器发送的。这种header 的名称是"cookie ",值是cookie 的 名/值对。这里有个例子,包含usernamepassword 两个cookieheader
Cookie: userName=budi; password=pwd; 

    对Cookie 的解析,是通过org.apache.catalina.util.RequestUtil 类 的parseCookieHeader 方法完成的。该方法接受cookie header ,返回一个javax.servlet.http.Cookie 数 组。该数组元素的个数就是cookie headercookie 名/值对的数量。Listing 3.5 给出了parseCookieHeader 方法的代码。

Listing 3.5: The org.apache.catalina.util.RequestUtil class's parseCookieHeader method 
public static Cookie[] parseCookieHeader(String header) {
   if ((header == null) || (header.length() < 1) )
     return (new Cookie[0]);
 
   ArrayList cookies = new ArrayList();
   while (header.length() > 0) {
     int semicolon = header.indexOf(';');
     if (semicolon < 0)
       semicolon = header.length();
     if (semicolon == 0)
       break;
     String token = header.substring(0, semicolon);
     if (semicolon < header.length())
       header = header.substring(semicolon + 1);
     else
       header = "";
     try {
       int equals = token.indexOf('=');
        if (equals > 0) {
         String name = token.substring(0, equals).trim();
         String value = token.substring(equals+1).trim();
         cookies.add(new Cookie(name, value));
       }
     }
     catch (Throwable e) {       
       ;
     }
   }
   return ((Cookie[]) cookies.toArray (new Cookie [cookies.size ()]));
} 
 
    这里是HttpProcessorparseHeader 方法中,负责处理cookies 的 那部分代码:
else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
        Cookie cookies[] = RequestUtil.ParseCookieHeader (value);
        for (int i = 0; i < cookies.length; i++) {
          if (cookies[i].getName().equals("jsessionid")) {
            // Override anything requested in the URL
            if (!request.isRequestedSessionIdFromCookie()) {
              // Accept only the first session id cookie
              request.setRequestedSessionId(cookies[i].getValue());
              request.setRequestedSessionCookie(true);
              request.setRequestedSessionURL(false);
            }
          }
          request.addCookie(cookies[i]);
        }
      } 
 

    剩余内容见:[How Tomcat Works]第3章 连接器(二)

   发表时间:2010-04-12  
非常好,楼主辛苦了
0 请登录后投票
   发表时间:2010-04-12  
看英文好辛苦啊,期待楼主的续作。。。
0 请登录后投票
   发表时间:2010-04-12  
我好像有个 doc 的
0 请登录后投票
   发表时间:2010-04-13  
lz写的比我详细多了,可以看成是翻译。
0 请登录后投票
   发表时间:2010-04-14  
我一下子没有看懂,这里真是很深呀
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics