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 ( #改为@ )

