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 ( #改為@ )
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

