译者 jarfield
博客 http://jarfield.iteye.com
概 述
就像《简介》中介绍的,Catalina 中有两个主要模 块:Connector (连接器)和Container (容器)。本章,你将编写一个连接 器 来增强第2章的应用,该连接器 能够创建更好的Request 和Response 对 象。符合Servlet 2.3 和2.4 规范的连接器 必须创建javax.servlet.http.HttpServletRequest 实例和javax.servlet.http.HttpServletResponse 实例,并将它们作 为参数传递给servlet 的service 方法。第2章的Servlet 容器只 能运行实现了javax.servlet.Servlet 接口的servlet ,并传递javax.servlet.ServletRequest 实 例和javax.servlet.ServletResponse 实例给servlet 的service 方 法。连接器 并不知道servlet 的类型(例如,是否实现了javax.servlet.Servlet 接 口, 继承了javax.servlet.GenericServlet ,或继承了javax.servlet.http.HttpServlet ), 因此它必须始终提供HttpServletRequest 实例和HttpServletResponse 实 例。
本章的应用程序中,连接器 解析HTTP 请求的headers , 使得servlet 可以获得headers 、cookies 、参数名/值, 等等。我们也会完善第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.properties 和LocalStrings_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 类,传递包名给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 方 法
马上你就能看到, 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.
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 ();
}
}
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");
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();
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 和 相关类的类图。
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 方 法, HttpProcessor 类的 prseHeaders 就调用了这两个方法。 请求参数是在需要时才被 HttpRequest 类 的 parseParameters 方法解析的。本节所有的方法都会被讨论到。
由于解析 HTTP 请求是一个非常复杂的任务,因此本节被分成下面几个小节:
- 读 取套接字 的输入流
- 解析请求行(request line )
- 解析headers
- 解析cookies
- 获 取请求参数
读取套接字的输入流
在第1 、2 章中,ex01.pyrmont.HttpRequest 类和ex02.pyrmont.HttpRequest 类已经做了一部分解析HTTP 请求的工作。通过调用java.io.InputStream 类 的read 方法,我们可以从请求行获得HTTP 方法、URI 和HTTP 版本:
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 类的原因是为了使用它的两 个重要方法:readRequestLine 和readHeader 。继续往下读。
解析请求行
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/tarzan 和password/pwd 。在Servlet/JSP 编 程中,jsessionid 参数用来携带会话标识(session identity)。会话标识通常嵌入在cookies 中,但是程序员可以选 择将会话标识嵌入在query string 中,例如在浏览器禁止cookie 的情况下。
当parseRequest 方法被HttpProcessor 类 的process 方法调用时,变量request 已 经指向了一个HttpRequest 实例。parseRequest 方法解析了请求行,获得了几个值,并将它们赋给HttpRequest 对 象。现在,我们来看看Listing 3.4 中parseRequest 方法的代码。
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 中。 因此,传递true 给request 对 象的setRequestSessionURL 方法。否则,传递false 给setRequestSessionURL 方 法,传递null 给setRequestSessionId 方 法。
这时,uri 的值已经不包含jsessionid 。接着,parseRequest 方 法传递uri 给normalize 方 法,以纠正“异常(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 方法返回null ,parseRequest 方法就抛出一个异常:
if (normalizedUri == null) {
throw new ServletException("Invalid URI: " + uri + "'");
}
解 析Headers
HttpHeader 类描述了HTTP 头部。第4 章 将详细解释该类,现在我们只要知道下面几点就足够了:
- 通过该类的无参构造函数创建HttpHeader 实例。
- 一旦有了HttpHeader 实 例,你可以把它传递给SocketInputStream 的readHeader 方法。如果有header 可 以读,那么readHeader 方法会相应地填充HttpHeader 对象。如果没有header 可 以读,HttpHeader 的nameEnd 和valueEnd 域都被设置0 。
- 要 获得header 的名称和值,可以使用下面的代码:
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
HttpHeader header = new HttpHeader();
// Read the next header
input.readHeader(header);
然后,通过测试 HttpHeader 实例的nameEnd 和valueEnd 域来判断 输入流中有没有更多的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 方法,将它们添加到用于存储 header 的 HashMap 中:
request.addHeader(name, value);
有的 header 还需要设置一些属性。例如,当 servlet 调 用 javax.servlet.ServletRequest 的 getContentLength 方法时,就返回 content-length 的值。包含 cookie 的 cookie 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 的 名/值对。这里有个例子,包含 username 和 password 两个 cookie 的 header :Cookie: userName=budi; password=pwd;
对 Cookie 的解析,是通过 org.apache.catalina.util.RequestUtil 类 的 parseCookieHeader 方法完成的。该方法接受 cookie header ,返回一个 javax.servlet.http.Cookie 数 组。该数组元素的个数就是 cookie header 中 cookie 名/值对的数量。 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 ()]));
}
这里是 HttpProcessor 类 parseHeader 方法中,负责处理 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]);
}
}