设为首页 收藏本站
查看: 310|回复: 0

[经验分享] [How Tomcat Works]第3章 连接器(二)

[复制链接]

尚未签到

发表于 2017-2-7 06:16:15 | 显示全部楼层 |阅读模式
译者 jarfield
 



博客 http://jarfield.iteye.com







  • StringManager


  • 应用程序



    • 动应用


    • 接器


    • 建HttpRequest对象



      • 取套接字的输入流


      • 析请求行


      • 析Headers


      • 析Cookies


      • 取参数




    • 建HttpResponse对象


    • 态资源处理器和Servlet处理器


    • 行应用程序






前面的部分见:[How Tomcat Works]第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[getContentLength()];
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

相关类的类图。

DSC0000.jpg
  

    在第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   
DSC0001.jpg
Figure 3.4
: 运行ModernServlet   



总结

  
本章你已经学习到了连接器
是如何工作的。本章构建的连接器
,是Tomcat 4

连接器
的一个简化版本。正如你知道的,这个默认连接器
因为低效而被不推荐使用(deprecated
)。
例如,所有的HTTP
请求headers都被解析,即使servlet
从来没有使用它们。结果,默认连接器
运行速度缓慢,并被更快的Coyote
代替。Coyote
的代码可以从Apache基金会网站下载到。无论如何,默认连接器都是一个很好的学
习工具,我们将在第4
章详细讨论它。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-338450-1-1.html 上篇帖子: Tomcat请求处理(五) -- 请求在容器间的流动 下篇帖子: How tomcat works 第四章学习笔记(1)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表