连接器概述
3.1 概述
tomcat由两大模块组成:连接器和容器。一个支持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的实例对象。
了解连接器之前,需要了解catalina下面的工具类StringManager类.
3.2 StringManager
对于tomcat的大型应用程序必须小心的处理错误信息。对于servlet程序员来说,在抛出每一个javax.servlet,servletException异常中,tomcat都会发送一条特殊的错误信息。
tomcat将错误信息写在一个properties文件中,这样便于读取和编辑。但若是将所有类的错误信息都写在一个properties文件,优惠导致文件太大,不便于读写。为避免这种情况,tomcat将properties文件按照不同的包进行划分,每个包下都有自己的properties文件。例如,org.apache.catalina.connector包下的properties文件包含了该包下所有的类中可能抛出的错误信息。每个properties文件都由一个org.apache.catalina.util.StringManager实例来处理。在tomcat运行时,会建立很多StringManager类的实例,每个实例对应一个properties文件。
StringManager被设计成单例对象
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;
}
StringManager sm = StringManager.getManager("ex03.pyrmont.connector.http");
3.2 Application
当前的应用程序可分为3个模块:connector、startup、core。
startup模块仅包括一个StartUp类,负责启动应用程序。
connector模块的类可分为以下5个部分:
连接器及其支持类(HttpConnector和HttpProcessor);
表示http请求的类(HttpRequest)及其支持类;
表示http响应的类(HttpResponse)及其支持类;
外观装饰类(HttpRequestFacade和HttpResponseFacade);
常量类。
core模块包括ServletProcessor类和StaticResourceProcessor类。
程序的大体图如下
相比前面的学习,HttpServer被分成了HttpConnector和HttpProcessor两个类,Request和Response被HttpRequest和HttpResponse代替。
在上一章,HttpServer负责等待http请求,并创建Request和Response两个对象。在本程序中,等待请求的任务交给了HttpConnector完成,创建request和response交给HttpProcessor去完成。http请求用HttpRequest对象表示,该类实现了javax.servlet.http.HttpServletRequest接口。一个HttpRequest对象在传给servlet的service方法前,会被转型为HttpServletRequest对象。
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方法中是一个循环体,执行以下三件事:
等待http请求;
为每个请求创建一个HttpPorcessor对象;调用HttpProcessor对象的process方法。
HttpProcessor类的process方法从http请求中获取socket。对每个http请求,它要做一下三件事:
创建一个HttpRequest对象和一个HttpResponse对象;
处理请求行(request line)和请求头(request headers),填充HttpRequest对象;
将HttpRequest对象和HttpResponse对象传给ServletProcessor或StaticResourceProcessor的process方法。
3.3.2 创建HttpRequest对象
HttpRequest类实现了javax.servlet.http.HttpServletRequest接口。其伴随的外观类是HttpRequestFacade。日uml图如下所示:
其中HttpRequest很多方法都是空方法,但已经可以从http请求中获取headers,cookies和参数等信息,三种信息的存储方式分别为HashMap、ArrayList和ParameterMap
3.3.3.1 SocketInputStream 类
该类提供了请求行(request line) 和请求头(request header) 的方法
请求行:
HttpProcessor的process调用其私有方法parseRequest来解析请求行(requst 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.2 解析请求头
请求头(request header)由HttpHeader对象表示。可以通过HttpHeader无参构造建立对象,并将其作为参数传给SocketInputStream的readHeader方法,该方法会自动填充HttpHeader对象。parseHeader方法有一个循环体,不断的从SocketInputStream 中获取header信息,直到读取完。
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
获取到header的name和value后,要将其填充到HttpRequest的header属性(hashMap类型)中:
request.addHeader(name, value);
其中某些header要设置到request对象的属性中,如contentLength等。
3.3.3.3 解析cookie
cookie 是由浏览器作为请求头 一部分发送的,请求头是一个cookie,是一个键值对
如下: Cookie:userName=userName;password=pwd
通过一下代码解析
public static Cookie[] parseCookieHeader(String header) {
if ((header == null) || (header.length 0 < 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 ()]));
}
3.3.3.4 获取参数
在调用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.5 创建HttpResponse