How Tomcat Works(四)
Servlet容器有两个主要的模块,即连接器(connector)与容器(container),本文接下来创建一个连接器来增强前面文章中的应用程序的功能,以一种更优雅的方式来创建request对象和response对象;为了兼容Servlet 2.3和2.4,连接器这里创建的是javax.servlet.http.HttpServletRequest对象和javax.servlet.http.HttpServletResponse对象(servlet对象类型可以是实现javax.servlet.Servlet接口或继承自javax.servlet.GenericServlet类或javax.servlet.http.HttpServlet类)。
在本文的应用程序中,连接器会解析http请求头、cookies和请求参数等;同时修改了Response类的getWriter()方法,使其工作得更好。
本文首先要介绍一下在servlet容器中是怎么实现错误消息国际化的,这里主要是StringManager类实现的功能
Tomcat是将错误消息存储在properties文件中,便于读取和编辑;可是由于 tomcat的类特别多,将所有类使用的错误消息都存储在同一个properties文件中将会造成维护的困难;所以tomcat的处理方式是将properties划分在不同的包中,每个properties文件都是用StringManager类的一个实例来处理,这样在tomcat运行时,会产生StringManager类的多个实例;同一个包里面的类共享一个StringManager类的实例(这里采用单例模式);这些不同包用到的的StringManager类的实例存储在一个hashtable容器中,以包名作为key存储StringManager类的实例
public class StringManager { /**
* The ResourceBundle for this StringManager.
*/ private ResourceBundle bundle; /**
* Creates a new StringManager for a given package. This is a
* private method and all access to it is arbitrated by the
* static getManager method call so that only one StringManager
* per package will be created.
*
* @param packageName Name of package to create StringManager for.
*/ private StringManager(String packageName) {
String bundleName = packageName + ".LocalStrings";
bundle = ResourceBundle.getBundle(bundleName);
} /**
* Get a string from the underlying resource bundle.
*
* @param key
*/ public String getString(String key) {
if (key == null) {
String msg = "key is null"; throw new NullPointerException(msg);
} String str = null; try {
str = bundle.getString(key);
} catch (MissingResourceException mre) {
str = "Cannot find message associated with key '" + key + "'";
} return str;
}
// --------------------------------------------------------------
// STATIC SUPPORT METHODS
// -------------------------------------------------------------- private static Hashtable managers = new Hashtable(); /**
* Get the StringManager for a particular package. If a manager for
* a package already exists, it will be reused, else a new
* StringManager will be created and returned.
*
* @param packageName
*/ 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;
}
}
下面我们来分析连接器是怎样实现的:
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();
}
}
我们从HttpConnector连接器的实现可以看到,该连接器负责监听客户端请求,当监听到客户端请求时,将获取的socket对象交给HttpProcessor对象的process()方法处理
我们接下来分析HttpProcessor类的实现:
/* this class used to be called HttpServer */
public class HttpProcessor { public HttpProcessor(HttpConnector connector) {
this.connector = connector;
}
/**
* The HttpConnector with which this processor is associated.
*/
private HttpConnector connector = null;
private HttpRequest request;
private HttpResponse response;
public void process(Socket socket) {
SocketInputStream input = null;
OutputStream output = null;
try {
//包装为SocketInputStream对象
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();
}
}
上面的方法中获取socket对象的输入流与输出流分别创建Request对象与Response对象,然后解析http请求行与请求头(并填充到Request对象的相应属性),最后分发给处理器处理
接下来的Request对象实现了javax.servlet.http.HttpServletRequest接口,主要是提供一些设置相关请求参数的方法和获取相关请求参数的方法
http请求头、cookies和请求参数等信息分别存储在如下成员变量中
protected ArrayList cookies = new ArrayList();
protected HashMap headers = new HashMap();
protected ParameterMap parameters = null;
需要注意的是protected void parseParameters()方法只需执行一次,该方法是用来初始化ParameterMap parameters成员变量,方法如下
/**
* Parse the parameters of this request, if it has not already occurred.
* If parameters are present in both the query string and the request
* content, they are merged.
*/
protected void parseParameters() {
if (parsed)
return;
ParameterMap results = parameters;
if (results == null)
results = new ParameterMap();
results.setLocked(false);
String encoding = getCharacterEncoding();
if (encoding == null)
encoding = "ISO-8859-1"; // Parse any parameters specified in the query string
String queryString = getQueryString();
try {
RequestUtil.parseParameters(results, queryString, encoding);
}
catch (UnsupportedEncodingException e) {
;
} // 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");
}
} // Store the final results
results.setLocked(true);
parsed = true;
parameters = results;
}
我就不明白,该方法为什么不采取同步锁关键字?难道是对初始化ParameterMap parameters成员变量次数没有硬性要求?
上面方法中的ParameterMap对象,继承自HashMap,采用boolean locked = false成员变量设置写入的锁(这玩意也有问题)
public final class ParameterMap extends HashMap { // ----------------------------------------------------------- Constructors /**
* Construct a new, empty map with the default initial capacity and
* load factor.
*/
public ParameterMap() { super(); } /**
* Construct a new, empty map with the specified initial capacity and
* default load factor.
*
* @param initialCapacity The initial capacity of this map
*/
public ParameterMap(int initialCapacity) { super(initialCapacity); } /**
* Construct a new, empty map with the specified initial capacity and
* load factor.
*
* @param initialCapacity The initial capacity of this map
* @param loadFactor The load factor of this map
*/
public ParameterMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); } /**
* Construct a new map with the same mappings as the given map.
*
* @param map Map whose contents are dupliated in the new map
*/
public ParameterMap(Map map) { super(map); } // ------------------------------------------------------------- Properties /**
* The current lock state of this parameter map.
*/
private boolean locked = false; /**
* Return the locked state of this parameter map.
*/
public boolean isLocked() { return (this.locked); } /**
* Set the locked state of this parameter map.
*
* @param locked The new locked state
*/
public void setLocked(boolean locked) { this.locked = locked; } /**
* The string manager for this package.
*/
private static final StringManager sm =
StringManager.getManager("org.apache.catalina.util"); // --------------------------------------------------------- Public Methods /**
* Remove all mappings from this map.
*
* @exception IllegalStateException if this map is currently locked
*/
public void clear() { if (locked)
throw new IllegalStateException
(sm.getString("parameterMap.locked"));
super.clear(); } /**
* Associate the specified value with the specified key in this map. If
* the map previously contained a mapping for this key, the old value is
* replaced.
*
* @param key Key with which the specified value is to be associated
* @param value Value to be associated with the specified key
*
* @return The previous value associated with the specified key, or
* <code>null</code> if there was no mapping for key
*
* @exception IllegalStateException if this map is currently locked
*/
public Object put(Object key, Object value) { if (locked)
throw new IllegalStateException
(sm.getString("parameterMap.locked"));
return (super.put(key, value)); } /**
* Copy all of the mappings from the specified map to this one. These
* mappings replace any mappings that this map had for any of the keys
* currently in the specified Map.
*
* @param map Mappings to be stored into this map
*
* @exception IllegalStateException if this map is currently locked
*/
public void putAll(Map map) { if (locked)
throw new IllegalStateException
(sm.getString("parameterMap.locked"));
super.putAll(map); } /**
* Remove the mapping for this key from the map if present.
*
* @param key Key whose mapping is to be removed from the map
*
* @return The previous value associated with the specified key, or
* <code>null</code> if there was no mapping for that key
*
* @exception IllegalStateException if this map is currently locked
*/
public Object remove(Object key) { if (locked)
throw new IllegalStateException
(sm.getString("parameterMap.locked"));
return (super.remove(key)); } }
同样Response对象实现了javax.servlet.http.HttpServletResponse接口
这里改写了前面文章中的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;
}
---------------------------------------------------------------------------
本系列How Tomcat Works系本人原创
转载请注明出处 博客园 刺猬的温驯
本人邮箱: chenying998179#163.com (#改为@)
本文链接 http://www.cnblogs.com/chenying99/p/3231639.html
How Tomcat Works(四)的更多相关文章
- How Tomcat works — 四、tomcat启动(3)
上一节说到StandardService负责启动其子组件:container和connector,不过注意,是有先后顺序的,先启动container,再启动connector,这一节先来看看conta ...
- How Tomcat Works(十四)补充
在How Tomcat Works(十四)中,本人并没有对javax.servlet.Filter及javax.servlet.FilterChain做详细的描述,本文在这里做一下补充 FilterC ...
- How Tomcat Works(十四)
我们已经知道,在tomcat中有四种类型的servlet容器,分别为Engine.Host.Context 和Wrapper,本文接下来对tomcat中Wrapper接口的标准实现进行说明. 对于每个 ...
- how tomcat works 读书笔记四 tomcat的默认连接器
事实上在第三章,就已经有了连接器的样子了,只是那仅仅是一个学习工具,在这一章我们会開始分析tomcat4里面的默认连接器. 连接器 Tomcat连接器必须满足下面几个要求 1 实现org.apache ...
- How Tomcat Works(十八)
在前面的文章中,如果我们要启动tomcat容器,我们需要使用Bootstrap类来实例化连接器.servlet容器.Wrapper实例和其他组件,然后调用各个对象的set方法将它们关联起来:这种配置应 ...
- How Tomcat Works(十七)
在前面的文章中,已经学会了如何通过实例化一个连接器和容器来获得一个servlet容器,并将连接器和容器相关联:但在前面的文章中只有一个连接器可用,该连接器服务8080端口上的HTTP请求,无法添加另一 ...
- How Tomcat Works(七)
本文接下来介绍并分析servlet容器,servlet容器是用来处理请求servlet资源,并为web客户端填充response对象的模块. servlet容器是org.apache.catalina ...
- how tomcat works 总结
希望各位网友在看完<<how tomcat works>>一书或者鄙人的tomcat专栏文章后再看这篇博客 这里主要是梳理各个章节的核心概念 第一章 一个简单的Web服务器 第 ...
- How Tomcat Works(十九)
本文重点关注启动tomcat时会用到的两个类,分别为Catalina类和Bootstrap类,它们都位于org.apachae.catalina.startup包下:Catalina类用于启动或关闭S ...
随机推荐
- LeetCode Linked List Cycle II 单链表环2 (找循环起点)
题意:给一个单链表,若其有环,返回环的开始处指针,若无环返回NULL. 思路: (1)依然用两个指针的追赶来判断是否有环.在确定有环了之后,指针1跑的路程是指针2的一半,而且他们曾经跑过一段重叠的路( ...
- 物联网操作系统HelloX已成功移植到MinnowBoard MAX开发板上
在HelloX开发团队的努力下,以及Winzent Tech公司(总部在瑞典斯德哥尔摩)的支持下,HelloX最新版本V1.78已成功移植到MinnowBoard MAX开发板上.相关源代码已经发布到 ...
- (转)Mac OS X写了个rm时将文件放入回收站的小工具
上次由于公司里机器要面临重装,开始仓皇地将Mac本里的文件scp到我的台式机上.忙乱之中本来要删除一个无用的文件夹的,结果用rm -rf的时候tab了一下,补全出来的文件都没看清就按下了回车,毫无疑问 ...
- jQuery事件绑定方式(转)
bind() 简要描述 bind()向匹配元素添加一个或多个事件处理器. 使用方式 $(selector).bind(event,data,function) event:必需项:添加到元素的一个或多 ...
- 一种Javascript解释ajax返回的json的好方法
通常ajax请求返回的格式为json或者xml,如果返回的是json,则可以通过转换成javascript对象进行操作,如下: 1.ajax请求的controller实现 @RequestMappin ...
- uglifyjs使用
1.安装Nodejs:去这里下载并安装. 2.打开cmd,运行npm,node应该会有输出东西 3.如果在window上运行Node.js的npm报Error: ENOENT, stat 'C:\Us ...
- NoSQL--非关系型的数据库是什么?
NoSQL,指的是非关系型的数据库.NoSQL,意即反SQL运动,是一项全新的数据库革命性运动,早期就有人提出,发展至2009年趋势越发高涨.NoSQL的拥护者们提倡运用非关系型的数据存储,相对于目前 ...
- XSS 前端防火墙(1):内联事件拦截
关于 XSS 怎样形成.如何注入.能做什么.如何防范,前人已有无数的探讨,这里就不再累述了.本文介绍的则是另一种预防思路. 几乎每篇谈论 XSS 的文章,结尾多少都会提到如何防止,然而大多万变不离其宗 ...
- location.hash来保持页面状态
/*本例是为了在客户端页面返回时保存状态,采用hash值记录的模式,为了使用方便所写的存取hash值的库,时间仓促,望指出错误.*/var pageStateHash = { hashArray: [ ...
- linux实现自动远程备份(scp+ssh)
刚上线的服务器需要备份日志,要备份到另一台服务器上去,为了减少工作量,采用linux的定时任务去自动执行.因服务器都是linux的,因此采用linux的远程复制scp命令.但这里涉及到一个问题,就是s ...