Catalina有兩個(gè)主要的模塊 Connector 和 Container,在本章的程序中所要建立的連接器是 Tomcat 4 中的默認(rèn)連接器的簡化版
3.1 StringManage
Tomcat 將存儲(chǔ)錯(cuò)誤消息的 properties 文件劃分到不同的包中,每個(gè) properties 文件都是使用 org.apache.catalina.util.StringManager 類的一個(gè)實(shí)例進(jìn)行處理鼎俘。同時(shí) StringManager 是單例模式的,避免空間的浪費(fèi)怔揩。
3.2 應(yīng)用程序
本章的應(yīng)用程序包含三個(gè)模塊:
- 啟動(dòng)模塊:負(fù)責(zé)啟動(dòng)應(yīng)用程序捉邢。
- 連接器模塊:
- 連接器及其支持類(HttpConnector 和 HttpProcessor);
- HttpRequest 類和 HttpResponse 類及其支持類商膊;
- 外觀類(HttpRequesFaced 類和 HttpResponseFaced)伏伐;
- 常量類。
- 核心模塊:
- ServletProcessor : 處理 servlet 請(qǐng)求(類似 Wrapper)晕拆;
- StaticResourceProcessor : 處理靜態(tài)資源請(qǐng)求藐翎。
3.2.1 啟動(dòng)應(yīng)用程序
public final class Bootstrap {
public static void main(String[] args) {
//創(chuàng)建 HttpConnector 實(shí)例
HttpConnector connector = new HttpConnector();
//啟動(dòng) Connector
connector.start();
}
}
3.2.2 HttpConnector 類
Connector 不知道 servlet 接受 Request 和 Response 的具體類型,所以使用 HttpServletRequest 和 HttpResponse 進(jìn)行傳參。同時(shí)解析 HTTP 請(qǐng)求對(duì)參數(shù)是懶加載的吝镣,在這些參數(shù)被 servlet 實(shí)例真正調(diào)用前是不會(huì)進(jìn)行解析的堤器。
Tomcat 的默認(rèn)連接器和本章程序的 Connector 都使用 SocketInputStream 獲取字節(jié)流。SocketInputStream 是 InputStream 的包裝類末贾,提供了兩個(gè)重要的方法:
- readRequestLine:獲取請(qǐng)求行闸溃,包括 URI、請(qǐng)求方法和 HTTP 版本信息拱撵。
- readHead: 獲取請(qǐng)求頭圈暗,每次調(diào)用 readHead 方法都會(huì)返回一個(gè)鍵值對(duì),可以通過遍歷讀取所有的請(qǐng)求頭信息裕膀。
public class HttpConnector implements Runnable {
boolean stopped;
private String scheme = "http";
public void start() {
//啟動(dòng)一個(gè)新的線程员串,調(diào)用自身的 run 方法
Thread thread = new Thread(this);
thread.start();
}
public void run() {
//為減少文章篇幅省略 try catch 語句塊
//創(chuàng)建 ServerSocket
ServerSocket serverSocket = null;
int port = 8080;
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
while (!stopped) {
// 等待連接請(qǐng)求
Socket socket = null;
socket = serverSocket.accept();
// 為當(dāng)前請(qǐng)求創(chuàng)建一個(gè) HttpProcessor
HttpProcessor processor = new HttpProcessor(this);
//調(diào)用 process 方法處理請(qǐng)求
processor.process(socket);
}
}
HttpProcessor 類
HttpProcessor 的 process 方法對(duì)每個(gè)傳入的 HTTP 請(qǐng)求,要完成4個(gè)操作:
- 創(chuàng)建一個(gè) HttpRequest 對(duì)象昼扛;
- 創(chuàng)建一個(gè) HttpResponse 對(duì)象寸齐;
- 解析 HTTP 請(qǐng)求的第一行內(nèi)容和請(qǐng)求頭信息,填充 HttpRequest 對(duì)象抄谐;
- 將 HttpRequest 和 HttpResponse 傳遞給 ServletProcessor 或 Static-ResourceProcessor 的 process 方法渺鹦。
public void process(Socket socket) {
SocketInputStream input= new SocketInputStream(socket.getInputStream(), 2048);
OutputStream output = socket.getOutputStream();
//創(chuàng)建 HttpRequest 和 HttpResponse 對(duì)象
request = new HttpRequest(input);
response = new HttpResponse(output);
response.setRequest(request);
//向客戶端發(fā)送響應(yīng)頭信息
response.setHeader("Server", "Pyrmont Servlet Container");
//解析請(qǐng)求行
parseRequest(input, output);
//解析請(qǐng)求頭
parseHeaders(input);
//根據(jù)請(qǐng)求的 URI 模式判斷處理請(qǐng)求的類
if (request.getRequestURI().startsWith("/servlet/")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
}
else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
}
parseRequest 方法解析請(qǐng)求行:
處理 URI 字符串 --> 絕對(duì)路徑檢查 --> 會(huì)話標(biāo)識(shí)符檢查 --> URI 修正 --> 設(shè)置 request 屬性
private void parseRequest(SocketInputStream input, OutputStream output) {
// requestLine 是 HttpRequestLine 的實(shí)例
//使用 SocketInputStream 中的信息填充 requestLine,并從 requestLine 中獲取請(qǐng)求行信息
input.readRequestLine(requestLine);
String method =
new String(requestLine.method, 0, requestLine.methodEnd);
String uri = null;
String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
// Validate the incoming request line
//驗(yàn)證輸入的 request line 是否合法蛹含, 略
...
//處理 URI 后的查詢字符串
int question = requestLine.indexOf("?");
if (question >= 0) {
request.setQueryString(new String(requestLine.uri, question + 1,
requestLine.uriEnd - question - 1));
uri = new String(requestLine.uri, 0, question);
}
else {
request.setQueryString(null);
uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}
//檢查是否是絕對(duì)路徑
if (!uri.startsWith("/")) {
int pos = uri.indexOf("://");
// 獲取 protocol 的類型
if (pos != -1) {
pos = uri.indexOf('/', pos + 3);
if (pos == -1) {
uri = "";
}
else {
uri = uri.substring(pos);
}
}
}
// 檢查查詢字符串是否還攜帶會(huì)話標(biāo)識(shí)符
String match = ";jsessionid=";
int semicolon = uri.indexOf(match);
if (semicolon >= 0) {
String rest = uri.substring(semicolon + match.length());
int semicolon2 = rest.indexOf(';');
if (semicolon2 >= 0) {
request.setRequestedSessionId(rest.substring(0, semicolon2));
rest = rest.substring(semicolon2);
}
else {
request.setRequestedSessionId(rest);
rest = "";
}
request.setRequestedSessionURL(true);
uri = uri.substring(0, semicolon) + rest;
}
else {
request.setRequestedSessionId(null);
request.setRequestedSessionURL(false);
}
//對(duì) URI 進(jìn)行修正轉(zhuǎn)化為合法的 URI毅厚,如將 "\" 替換成 "/"
String normalizedUri = normalize(uri);
// 屬性 request 的屬性
((HttpRequest) request).setMethod(method);
request.setProtocol(protocol);
if (normalizedUri != null) {
((HttpRequest) request).setRequestURI(normalizedUri);
}
else {
((HttpRequest) request).setRequestURI(uri);
}
}
parseHeaders 方法解析請(qǐng)求頭:
獲取下一個(gè) header --> 如果 header name 和 value 為空則退出循環(huán) --> 從 header 中獲取 key/value 并放入 request --> 對(duì) cookie 和 content-type 做處理 --> 循環(huán)
private void parseHeaders(SocketInputStream input) {
while (true) {
HttpHeader header = new HttpHeader();
// 獲取下一個(gè) header
input.readHeader(header);
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
}
else {
throw new ServletException ();
}
}
// 從 header 中獲取請(qǐng)求頭的 key/value,并存入 request 中
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
request.addHeader(name, value);
// 對(duì)一些特別的 header 做處理
if (name.equals("cookie")) {
Cookie cookies[] = RequestUtil.parseCookieHeader(value);
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("jsessionid")) {
// Override anything requested in the URL
if (!request.isRequestedSessionIdFromCookie()) {
// Accept only the first session id cookie
request.setRequestedSessionId(cookies[i].getValue());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
}
}
request.addCookie(cookies[i]);
}
}
else if (name.equals("content-length")) {
int n = -1;
n = Integer.parseInt(value);
request.setContentLength(n);
}
else if (name.equals("content-type")) {
request.setContentType(value);
}
}
}
獲取參數(shù):
- 在獲取參數(shù)前會(huì)調(diào)用 HttpRequest 的 parseParameter 方法解析請(qǐng)求參數(shù)浦箱。參數(shù)只需解析一次也只會(huì)解析一次吸耿。
- 參數(shù)存儲(chǔ)在特殊的 hashMap 中: ParameterMap
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;
}
4
4
4
4
4
4
4
4
4
4
4