[How Tomcat Works]第3章 连接器(二)
译者 jarfield博客 http://jarfield.iteye.com
[*]
概
述
[*]
StringManager
类
[*]
应用程序
[*]
启
动应用
[*]
连
接器
[*]
创
建HttpRequest对象
[*]
读
取套接字的输入流
[*]
解
析请求行
[*]
解
析Headers
[*]
解
析Cookies
[*]
获
取参数
[*]
创
建HttpResponse对象
[*]
静
态资源处理器和Servlet处理器
[*]
运
行应用程序
[*]
总
结
前面的部分见:第3章 连接器(一)
获取参数
除非servlet
调用javax.servlet.http.HttpServletRequest
的getParameter
、getParameterMap
、getParameterNames
和getParameterValues
方
法来读取请求参数,否则,我们都不需要解析query string
或HTTP
请求体(request
body
)。 因此,HttpRequest
类实现的这四个方法,都
是先调用parseParameter
方法。
请求参数只需要被解析一次,而且也许只能被解析一次,因为如果请求参数是在请求体中,参数解析会导致SocketInputStream
到
达字节流的末尾。HttpRequest
类使用boolean
成员parsed
来
表示请求参数是否已经被解析。
如果是GET
请求,那
么所有的请求参数都在query sting
中。如果是POST
请求,在请求体中也可以找到一些参数。所有参数以名/值对的形式存储在一个HashMap
中。Servlet
程
序员能够以两种形式获取参数:Map
(调用HttpServletRequest
的getParameterMap
方
法)和名/值对。但是,这里有个“圈套”。Servlet
程序员不允许修改参
数的值。因此,使用了一个特殊的HashMap
:org.apache.catalina.util.ParameterMap
。
ParameterMap
类继承了java.util.HashMap
,并使用了一个boolean
成
员locked
。只有locked
的
值为false
时,名/值对才能被添加、修改和删除。否则,将会抛出一个IllegalStateException
异常。ParameterMap
的代码见Listing 3.6
。
它重载了添加、修改和删除元素的方法。这些方法只能在locked
值为false
才能被调用。
Listing
3.6: The org.apache.Catalina.util.ParameterMap class.
package org.apache.catalina.util;
import java.util.HashMap;
import java.util.Map;
public final class ParameterMap extends HashMap {
public ParameterMap() {
super ();
}
public ParameterMap(int initialCapacity) {
super(initialCapacity);
}
public ParameterMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
public ParameterMap(Map map) {
super(map);
}
private boolean locked = false;
public boolean isLocked() {
return (this.locked);
}
public void setLocked(boolean locked) {
this.locked = locked;
}
private static final StringManager sm =
StringManager.getManager("org.apache.catalina.util");
public void clear() {
if (locked)
throw new IllegalStateException(sm.getString("parameterMap.locked"));
super.clear();
}
public Object put(Object key, Object value) {
if (locked)
throw new IllegalStateException
(sm.getString("parameterMap.locked"));
return (super.put(key, value));
}
public void putAll(Map map) {
if (locked)
throw new IllegalStateException
(sm.getString("parameterMap.locked"));
super.putAll(map);
}
public Object remove(Object key) {
if (locked)
throw new IllegalStateException
(sm.getString("parameterMap.locked"));
return (super.remove(key));
}
}
现在,让我们看看parseParameters
方法是如何工作的。
由于请求参数既可以存在于query string
中,也可以存在于
HTTP请求体中,parseParameters
方法必须都检查query string
和请求体。一旦解析完成,就可以在parameters
成员变量中找到这些参数。因此该方法首先检查成员变量parsed
的值,如果之前已经解析过,parsed
的
值就是true
。
if (parsed)
return;
然后,parseParameters
方法声明创建一个名为results
的ParameterMap
变量,将它指向parameters
。如果parameters
为null
,就创建一个新的ParameterMap
对
象。
ParameterMap results = parameters;
if (results == null)
results = new ParameterMap();
接着,parseParameters
方法打开ParameterMap
的
锁,以便修改它。
results.setLocked(false);
下一步,parseParameters
方法检查编码,如果编码为空则赋予默认编码。
String encoding = getCharacterEncoding();
if (encoding == null)
encoding = "ISO-8859-1";
接着,parseParameters
方
法尝试解析query string
。参数解析是由org.apache.Catalina.util.RequestUtil
类的parseParameter
方法完成的。
// Parse any parameters specified in the query string
String queryString = getQueryString();
try {
RequestUtil.parseParameters(results, queryString, encoding);
}
catch (UnsupportedEncodingException e) {
;
}
下一步,parseParameters
方法尝试看看HTTP
请求体中是否包含参数。如果用户使用POST
方法发送请求,content length
大
于0
,并且content
type
是application/x-www-form-urlencoded
,
那么请求体中就会包含参数。解析请求体的代码如下。
// Parse any parameters specified in the input stream
String contentType = getContentType();
if (contentType == null)
contentType = "";
int semicolon = contentType.indexOf(';');
if (semicolon >= 0) {
contentType = contentType.substring (0, semicolon).trim();
}
else {
contentType = contentType.trim();
}
if ("POST".equals(getMethod()) && (getContentLength() > 0)
&& "application/x-www-form-urlencoded".equals(contentType)) {
try {
int max = getContentLength();
int len = 0;
byte buf[] = new byte;
ServletInputStream is = getInputStream();
while (len < max) {
int next = is.read(buf, len, max - len);
if (next < 0 ) {
break;
}
len += next;
}
is.close();
if (len < max) {
throw new RuntimeException("Content length mismatch");
}
RequestUtil.parseParameters(results, buf, encoding);
}
catch (UnsupportedEncodingException ue) {
;
}
catch (IOException e) {
throw new RuntimeException("Content read fail");
}
}
最后,parseParameters
方
法锁住ParameterMap
,设置parsed
成员的值为true
,并将results
赋值给parameters
。
// Store the final results
results.setLocked(true);
parsed = true;
parameters = results;
创建HttpResponse
对
象
HttpResponse
类实现了javax.servlet.http.HttpServletResponse
接口,跟随该类的
还有一个门面类HttpResponseFacade
。Figure 3.3
展现了HttpResponse
和
相关类的类图。
在第2
章,我们使用的HttpResponse
类
仅包括部分功能。例如,它的getWriter
方法返回了一个java.io.PrintWriter
对象,该对象的print
方法并不会自动flush
。本章的应用将修复这个
问题。为了理解如何修复的,你需要知道Writer
是什么。
在servlet
内部,你使用PrintWriter
来
写入字符。你可以使用任何你希望的编码,但是字符总是作为字节流发送给浏览器。因此,第2
章
中ex02.pyrmont.HttpResponse
类getWriter
方法的实现也就不奇怪了:
public PrintWriter getWriter() {
// if autoflush is true, println() will flush,
// but print() will not.
// the output argument is an OutputStream
writer = new PrintWriter(output, true);
return writer;
}
看,我们是如何创建PrintWriter
对象的?传递一个java.io.OutputStream
实
例作为参数。任何内容传递给PrintWriter
的print
或println
方
法,都会被转成字节流,通过底层OutputStream
对象发送出去。
本章我们使用ex03.pyrmont.connector.ResponseStream
类
的实例作为PrintWriter
的OutputStream
。注意,ResponseStream
类
直接继承了java.io.OutputStream
类。
本章我们还使用继承了PrintWriter
的ex03.pyrmont.connector.ResponseWriter
类。ResponseWriter
类重写了所有的print
和println
方法,对这些方法的调用都会自动将输出flush
到底层的OutputStream
。因此,我们使
用以ResponseStream
对象作为底层的ResponseWriter
实例。
我们本可以将一个ResponseStream
对象作为参数,实例化ResponseWriter
类。然而,我们使用java.io.OutputStreamWriter
对
象桥接了ResponseWriter
对象和ResponseStream
对象。
有了OutputStreamWriter
,
被写入的字符自动按照指定的字符集被编码成字节。这里使用的字符集,或者通过名称来指定,或者显式指定Charset
对
象,或者使用平台默认的字符集。对write
方法的每次调用,都会对给定的字符进行编
码转换。在写入底层输入流之前,转换后的字节先被累积在缓冲区中。缓冲区的大小可以指定,但对于大多数场景来说,默认大小已经足够了。
因此,getWriter
方法就按下面的代码来实现:
public PrintWriter getWriter() throws IOException {
ResponseStream newStream = new ResponseStream(this);
newStream.setCommit(false);
OutputStreamWriter osr =
new OutputStreamWriter(newStream, getCharacterEncoding());
writer = new ResponseWriter(osr);
return writer;
}
静态资源处理器和Servlet
处
理器
ServletProcessor
类和第2
章的ex02.pyrmont.ServletProcessor
类
是类似的。它们都只有一个方法:process
。然而,ex03.pyrmont.connector.ServletProcessor
的process
方法接受一个HttpRequest
对
象和一个HttpResponse
对象,而不是Request
对象和Response
对
象。下面是本章应用的process
方法原型:
public void process(HttpRequest request, HttpResponse response) {
另外,process
方法使用HttpRequestFacade
和HttpResponseFacade
,作为请求和响应的门面类。而且在调用了servlet
的service
方
法后,还调用了HttpResponse
类的finishResponse
方法。
servlet = (Servlet) myClass.newInstance();
HttpRequestFacade requestPacade = new HttpRequestFacade(request);
HttpResponseFacade responseFacade = new
HttpResponseFacade(response);
servlet.service(requestFacade, responseFacade);
((HttpResponse) response).finishResponse();
StaticResourceProcessor
类和ex02.pyrmont.StaticResourceProcessor
基本相同。
运行应用程序
要在Windows
运行该应用,在工作目录运行以下命令:
java -classpath ./lib/servlet.jar;./ ex03.pyrmont.startup.Bootstrap
在Linux
上,需要使用冒号来分
隔两个库。
java -classpath ./lib/servlet.jar:./ ex03.pyrmont.startup.Bootstrap
要显示index.html
,使用下面的URL
:
http://localhost:808O/index.html
要调用PrimitiveServlet,直接在浏览器中访问下面的URL:
http://localhost:8080/servlet/PrimitiveServlet
你会在浏览器中看到下面的内容:
Hello. Roses are red.
Violets are blue.
提示:第2章中运行
PrimitiveServlet
时,并没有输出第二行。
你也可以调用第2
章中没有的ModernServlet
。URL
是:
http://localhost:8080/servlet/ModernServlet
提
示:
ModernServlet
的代码在工作目录的
webroot
目录下。
你可以在URL后面拼上一个query stirng
,来测试servlet
。Figure 3.4
显示了,
以下面URL
运行ModernServlet
的
输出结果:
http://localhost:8080/servlet/ModernServlet?userName=tarzan&password=pwd
Figure 3.4
: 运行ModernServlet
总结
本章你已经学习到了连接器
是如何工作的。本章构建的连接器
,是Tomcat 4
默
认连接器
的一个简化版本。正如你知道的,这个默认连接器
因为低效而被不推荐使用(deprecated
)。
例如,所有的HTTP
请求headers都被解析,即使servlet
从来没有使用它们。结果,默认连接器
运行速度缓慢,并被更快的Coyote
代替。Coyote
的代码可以从Apache基金会网站下载到。无论如何,默认连接器都是一个很好的学
习工具,我们将在第4
章详细讨论它。
页:
[1]