lyd2004888 发表于 2017-2-7 06:16:15

[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]
查看完整版本: [How Tomcat Works]第3章 连接器(二)