《深入剖析Tomcat 》第3章 连接器(Connector)

第3章  连接器(Connector)

3.1  概述

在简介一章里说明了,tomcat由两大模块组成:连接器(connector)和容器(container)。本章将使用连接器来增强application 2的功能。一个支持servlet2.3和2.4规范的连接器必须要负责创建javax.servlet.http.HttpServletRequest和javax.servlet.http.HttpServletResponse实例,并将它们作为参数传递给要调用的某个的servlet的service方法。在第2章中的servlet容器仅仅能运行实现了javax.servlet.Servlet接口,并想service方法中传入了javax.servlet.ServletRequest和javax.servlet.ServletResponse实例的servlet。由于连接器并不知道servlet的具体类型(例如,该servlet是否javax.servlet.Servlet接口,还是继承自javax.servlet.GenericServlet类,或继承自javax.servlet.http.HttpServlet类),因此连接器总是传入HttpServletRequest和HttpServletResponse的实例对象。

         本章中所要建立的connector实际上是tomcat4中的默认连接器(将在第4章讨论)的简化版。本章中,connector和container将分离开。

         在开始说明本章的程序之前,先花点时间介绍下org.apache.catalina.util.StringManager类,它被tomcat用来处理不同模块内错误信息的国际化。在本章中,也是这样用的。

3.2  StringManager类

tomcat将错误信息写在一个properties文件中,这样便于读取和编辑。但若是将所有类的错误信息都写在一个properties文件,优惠导致文件太大,不便于读写。为避免这种情况,tomcat将properties文件按照不同的包进行划分,每个包下都有自己的properties文件。例如,org.apache.catalina.connector包下的properties文件包含了该包下所有的类中可能抛出的错误信息。每个properties文件都由一个org.apache.catalina.util.StringManager实例来处理。在tomcat运行时,会建立很多StringManager类的实例,每个实例对应一个properties文件。

         当包内的某个类要查找错误信息时,会先获取对应的StringManager实例。StringManager被设计为在包内是共享的一个单例,功过hashtable实现。如下面的代码所示:

 

java代码:
  1. private static Hashtable managers = new Hashtable();   
  2. public synchronized static StringManager   
  3.     getManager(String packageName) {   
  4.    StringManager mgr = (StringManager)managers.get(packageName);    if (mgr == null) {   
  5.      mgr = new StringManager(packageName);   
  6.      managers.put(packageName, mgr);   
  7.    }   
  8.    return mgr;   
  9. }  
  10. StringManager sm = StringManager.getManager("ex03.pyrmont.connector.http");  

3.3  Application

从本章开始,每章的应用程序都会按照模块进行划分。本章的应用程序可分为3个模块:connector、startup、core。

         startup模块仅包括一个StartUp类,负责启动应用程序。

         connector模块的类可分为以下5个部分:

l         连接器及其支持类(HttpConnector和HttpProcessor);

l         表示http请求的类(HttpRequest)及其支持类;

l         表示http响应的类(HttpResponse)及其支持类;

l         外观装饰类(HttpRequestFacade和HttpResponseFacade);

l         常量类。

core模块包括ServletProcessor类和StaticResourceProcessor类。

下面是程序的uml图:

图表 4  application的uml图

相比于第2章中的程序,HttpServer在本章中被分成了HttpConnector和HttpProcessor两个类。Request和Response分别被HttpRequest和HttpResponse代替。此外,本章的应用程序中还使用了一些其他的类。

         在第2章中,HttpServer负责等待http请求,并创建request和response对象。本章中,等待http请求的工作由HttpConnector完成,创建request和response对象的工作由HttpProcessor完成。

本章中,http请求用HttpRequest对象表示,该类实现了javax.servlet.http.HttpServletRequest接口。一个HttpRequest对象在传给servlet的service方法前,会被转型为HttpServletRequest对象。因此,需要正确设置每个HttpRequest对象的成员变量,方便servlet使用。需要设置的值包括,uri,请求字符串,参数,cookie和其他一些请求头信息等。由于连接器并不知道servlet中会使用那些变量,素以它会将从http请求中获取的变量都设置到HttpRequest对象中。但是,处理一个http请求会设计到一些比较耗时的操作,如字符串处理等。因此,若是connector仅仅传入servlet需要用到的值就会节省很多时间。tomcat的默认connector对这些值的处理是等到servlet真正用到的时候才处理的。

tomcat的默认connector和本程序的connector通过SocketInputStream类来读取字节流,可通过socket的getInputStream方法来获取该对象。它有两个重要的方法readRequestLine和readHeader。readRequestLine方法返回一个http请求的第一行,包括uri,请求方法和http协议版本。从socket的inputStream中处理字节流意味着要从头读到尾(即不能返回来再读前面的内容),因此,readRequestLine方法一定要在readHeader方法前调用。readRequestLine方法返回的是HttpRequestLine对象,readHeader方法返回的是HttpHeader对象(key-value形式)。获取HttpHeader对象时,应重复调用readHeader方法,直到再也无法获取到。

HttpProcessor对象负责创建HttpRequest对象,并填充它的成员变量。在其parse方法中,将请求行(request line)和请求头(request header)信息填充到HttpRequest对象中,但并不会填充请求体(request body)和查询字符串(query string)。

3.3.1  启动

在Bootstrap类的main方法内实例化一个HttpConnector类的对象,并调用其start方法就可以启动应用程序。

3.3.2  connector

HttpConnector类实现了java.lang.Runnable接口,这样它可以专注于自己的线程。启动应用程序时,会创建一个HttpConnector对象,其run方法会被调用。其run方法中是一个循环体,执行以下三件事:

l         等待http请求;

l         为每个请求创建一个HttpPorcessor对象;

l         调用HttpProcessor对象的process方法。

         HttpProcessor类的process方法从http请求中获取socket。对每个http请求,它要做一下三件事:

l         创建一个HttpRequest对象和一个HttpResponse对象;

l         处理请求行(request line)和请求头(request headers),填充HttpRequest对象;

l         将HttpRequest对象和HttpResponse对象传给ServletProcessor或StaticResourceProcessor的process方法。

 

3.3.3  创建HttpRequest对象

HttpRequest类实现了javax.servlet.http.HttpServletRequest接口。其伴随的外观类是HttpRequestFacade。日uml图如下所示:


图表 5  HttpRequest类的uml图

其中HttpRequest的很多方法都是空方法,但已经可以从hhtp请求中获取headers,cookies和参数信息了。这三种数据分别以HashMap、ArrayList和ParameterMap(后面介绍)存储。

3.3.3.1  SocketInputStream类

本章的应用程序中,使用的SocketInputStream就是org.apache.catalina.connector.http.SocketInputStream。该类提供了获取请求行(request line)和请求头(request header)的方法。通过传入一个InputStream对象和一个代表缓冲区大小的整数值来创建SocketInputStream对象。

3.3.3.2  解析请求行(request line)

HttpProcessor的process调用其私有方法parseRequest来解析请求行(request line,即http请求的第一行)。下面是一个请求行(request line)的例子:

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

         注意:“GET”后面和“HTTP”前面各有一个空格。

         请求行的第2部分是uri加上查询字符串。在上面的例子中,uri是:

/myApp/ModernServlet

问号后面的都是查询字符串,这里是:

userName=tarzan&password=pwd

在servlet/jsp编程中,参数jsessionid通常是嵌入到cookie中的,也可以将其嵌入到查询字符串中。parseRequest方法的具体内容参见代码。

         

3.3.3.3  解析请求头(request header)

请求头(request header)由HttpHeader对象表示。可以通过HttpHeader的无参构造方法建立对象,并将其作为参数传给SocketInputStream的readHeader方法,该方法会自动填充HttpHeader对象。parseHeader方法内有一个循环体,不断的从SocketInputStream中读取header信息,直到读完。获取header的name和value值可使用下米娜的语句:

 

java代码:
  1. String name = new String(header.name, 0, header.nameEnd);   
  2. String value = new String(header.value, 0, header.valueEnd);  
  3. 获取到header的name和value后,要将其填充到HttpRequest的header属性(hashMap类型)中:  
  4. request.addHeader(name, value);  
  5. 其中某些header要设置到request对象的属性中,如contentLength等。  
3.3.3.4  解析cookie

ookie是由浏览器作为请求头的一部分发送的,这样的请求头的名字是cookie,它的值是一个key-value对。举例如下:

 

java代码:
  1. Cookie: userName=budi; password=pwd;  

对cookie的解析是通过org.apache.catalina.util.RequestUtil类的parseCookieHeader方法完成的。该方法接受一个cookie头字符串,返回一个javax.servlet.http.Cookie类型的数组。方法实现如下:

 

java代码:
  1. public static Cookie[] parseCookieHeader(String header) {   
  2. if ((header == null) || (header.length 0 < 1) )   
  3.     return (new Cookie[0]);   
  4.         ArrayList cookies = new ArrayList();   
  5. while (header.length() > 0) {   
  6.             int semicolon = header.indexOf(';');   
  7.             if (semicolon < 0)   
  8.     semicolon = header.length();   
  9.     if (semicolon == 0)   
  10.     break;   
  11.     String token = header.substring(0, semicolon);   
  12.     if (semicolon < header.length())   
  13.        header = header.substring(semicolon + 1);   
  14.     else   
  15.                 header = "";   
  16.     try {   
  17.        int equals = token.indexOf('=');   
  18.                if (equals > 0) {   
  19.        String name = token.substring(0, equals).trim();   
  20.                    String value = token.substring(equals+1).trim();   
  21.        cookies.add(new Cookie(name, value));   
  22.                 }   
  23. catch (Throwable e) { ; }   
  24. }   
  25. return ((Cookie[]) cookies.toArray (new Cookie [cookies.size ()]));   
  26. }  
3.3.3.5  获取参数

在调用javax.servlet.http.HttpServletRequest的getParameter、getParameterMap、getParameterNames或getParameterValues方法之前,都不会涉及到对查询字符串或http请求体的解析。因此,这四个方法的实现都是先调用parseParameter方法。

         参数只会被解析一次,因为,HttpRequest类会设置一个标志位表明是否已经完成参数解析了。参数可以出现在查询字符串或请求体中。若用户使用的GET方法,则所有的参数都会在查询字符串中;若是使用的POST方法,则请求体中也可能会有参数。所有的key-value的参数对都会存储在HashMap中,其中的值是不可修改的。tomcat中使用的是一个特殊的hashmap类,org.apache.catalina.util.ParameterMap。

         ParameterMap类继承自java.util.HashMap,使用一个标志位来表示锁定。如果该标志位为false,则可以对其中的key-value进行添加、修改、删除操作,否则,执行这些操作时,会抛出IllegalStateException异常。代码如下:

3.3.3.6  创建HttpResponse对象

HttpResponse类继承自javax.servlet.http.HttpServletResponse,其相应的外观类是HttpResponseFacade。其uml图如下所示:

图表 6  HttpResponse及其外观类的uml图示

在第2章中,HttpResponse的功能有限,例如,它的getWriter方法返回的java.io.PrintWriter对象执行了print方法时,并不会自动flush。本章的程序将解决此问题。在此之前,先说明一下什么是Writer。

         在servlet中,可以使用PrintWriter对象想输出流中写字符。可以使用任意编码格式,但在发送的时候,实际上都是字节流。

         在本章中,将要使用的是ex03.pyrmont.connector.ResponseStream类作为PrintWriter的输出流。该类直接继承自java.io.OutputStream类。

         类ex03.pyrmont.connector.ResponseWriter继承自PrintWriter,重写了其print和println方法,实现自动flush。因此,本章适用ResponseWriter作为输出对象。

         示例代码如下:

 

java代码:
  1. public PrintWriter getWriter() throws IOException {   
  2.      ResponseStream newStream = new ResponseStream(this);   
  3.      newStream.setCommit(false);   
  4.      OutputStreamWriter osr =   
  5.        new OutputStreamWriter(newStream, getCharacterEncoding());   
  6.      writer = new ResponseWriter(osr);   
  7.      return writer;   
  8.    }  
3.3.3.7  静态资源处理器和servlet处理器

         本章的servlet处理器和第2章的servlet处理器类似,都只有一个process方法。但个,本章中,process方法接收的参数类型为HttpRequest和HttpResponse。方法签名如下:

         public void process(HttpRequest request, HttpResponse response);

         此外,process使用了request和response的外观类,并在调用了servlet的service方法后,再调用HttpResponse的finishResponse方法。示例代码如下:

 

java代码:
  1. servlet = (Servlet) myClass.newInstance();   
  2.       HttpRequestFacade requestPacade = new HttpRequestFacade(request);   
  3.       HttpResponseFacade responseFacade = new   
  4.         HttpResponseFacade(response);   
  5.       servlet.service(requestFacade, responseFacade);   
  6.       ((HttpResponse) response).finishResponse();  

 

转载请注明出处【http://sishuok.com/forum/blogPost/list/0/4076.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值