前幾天面試阿里轮锥,面試官問我如何解析HTTP協(xié)議,我大概說了一下的思路毡琉,他最后得出的結(jié)論是我對HTTP協(xié)議不了解铁瞒,讓我很受打擊∥ψ蹋回來看《深入剖析Tomcat》慧耍,研究一下Tomcat是如何解析HTTP協(xié)議的
1. 環(huán)境說明
- 《深入剖析Tomcat》是基于
tomcat-4.1.12
進行分析,這個版本在2002年發(fā)布,可以說是老古董了丐谋。不過就學習而言還是很好的工具. - Http協(xié)議的解析在連接器(connector) 中進行芍碧,連接器是一個獨立的模塊,可以被插入到容器中号俐,
tomcat-4.1.12
里提供了默認連接器泌豆,但已被標注為過時。
2. 源碼分析
2.1 連接器
默認連接器在org.apache.catalina.connector.http
包下,它實現(xiàn)了Connector
接口和Runnable
接口吏饿。分析的入口在run
方法中
2.1.1 run()
public void run() {
while (!stopped) {
//從ServerSocket中接受下一個進入的連接
Socket socket = null;
try {
serverSocket.accept()");
socket = serverSocket.accept();
if (connectionTimeout > 0)
socket.setSoTimeout(connectionTimeout);
socket.setTcpNoDelay(tcpNoDelay);//這個有點意思踪危,關(guān)閉TCP延遲確認
} catch (AccessControlException ace) {
log("socket accept security exception", ace);
continue;
} catch (IOException e) {
try {
// 如果重新打開失敗蔬浙,退出
synchronized (threadSync) {
if (started && !stopped)
log("accept error: ", e);
if (!stopped) {
serverSocket.close();
serverSocket = open();
}
}
} catch (IOException ioe) {
log("socket reopen, io problem: ", ioe);
break;
} catch (KeyStoreException kse) {
log("socket reopen, keystore problem: ", kse);
break;
} catch (NoSuchAlgorithmException nsae) {
log("socket reopen, keystore algorithm problem: ", nsae);
break;
} catch (CertificateException ce) {
log("socket reopen, certificate problem: ", ce);
break;
} catch (UnrecoverableKeyException uke) {
log("socket reopen, unrecoverable key: ", uke);
break;
} catch (KeyManagementException kme) {
log("socket reopen, key management problem: ", kme);
break;
}
continue;
}
// 把socket給適當?shù)奶幚砥? HttpProcessor processor = createProcessor();//2.1.2
if (processor == null) {
try {
log(sm.getString("httpConnector.noProcessor"));
socket.close();
} catch (IOException e) {
;
}
continue;
}
processor.assign(socket);//2.2.3
// The processor will recycle itself when it finishes
}
synchronized (threadSync) {
threadSync.notifyAll();
}
}
2.1.2 createProcessor()
具體的處理將在HttpProcessor
中進行,一個連接器會創(chuàng)建多個處理器,連接器的數(shù)量通過maxProcessors
和minProcessors
進行控制贞远。今天的重點在http協(xié)議的解析畴博,創(chuàng)建HttpProcessor
的一些細節(jié)就不說了
private HttpProcessor createProcessor() {
synchronized (processors) {
if (processors.size() > 0) {
return ((HttpProcessor) processors.pop());
}
if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
return (newProcessor());
} else {
if (maxProcessors < 0) {
return (newProcessor());
} else {
return (null);
}
}
}
}
2.2 處理器
HttpProcessor
在獨立的線程中對請求進行處理,連接器將請求分配給處理器(調(diào)用處理器的assign()
方法)蓝仲,處理器處理完成后將進行回收重復利用
2.2.1 run()
HttpProcessor
同樣實現(xiàn)了Runnable
接口绎晃,在后臺一直運行(被設(shè)置為守護線程),等待處理請求
public void run() {
while (!stopped) {
// 等待下一個socket
Socket socket = await();//2.2.2
if (socket == null)
continue;
// 處理請求
try {
process(socket);//2.2.4
} catch (Throwable t) {
log("process.invoke", t);
}
// 完成此次請求
connector.recycle(this);
}
synchronized (threadSync) {
threadSync.notifyAll();
}
}
2.2.2 await()
await()
監(jiān)視available
變量杂曲,如果沒有新的請求,就進入阻塞狀態(tài)袁余,同時run()
方法也會被阻塞
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
Socket socket = this.socket;
available = false;
notifyAll();
if ((debug >= 1) && (socket != null))
log(" The incoming request has been awaited");
return (socket);
}
2.2.3 assign(Socket socket)
連接器調(diào)用assign
方法分配請求擎勘,它會喚醒阻塞的線程.這實際上是一個生產(chǎn)者-,消費者模型颖榜,通過available
變量棚饵,將請求從連接器傳遞到處理器。但這個實現(xiàn)并不優(yōu)雅掩完,并且效率也不高
synchronized void assign(Socket socket) {
// Wait for the Processor to get the previous Socket
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
notifyAll();
if ((debug >= 1) && (socket != null))
log(" An incoming request is being assigned");
}
2.2.4 process(Socket socket)
process(Socket socket)
方法對請求進行處理,此處省略了很多
private void process(Socket socket) {
boolean ok = true;
boolean finishResponse = true;
SocketInputStream input = null;
OutputStream output = null;
// 構(gòu)造和初始化需要的對象
try {
input = new SocketInputStream(socket.getInputStream(),
connector.getBufferSize());
} catch (Exception e) {
log("process.create", e);
ok = false;
}
keepAlive = true;
while (!stopped && ok && keepAlive) {
finishResponse = true;
try {
//此處的request,response是循環(huán)利用的
request.setStream(input);
request.setResponse(response);
output = socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse) response.getResponse()).setHeader
("Server", SERVER_INFO);
} catch (Exception e) {
//...
}
// 解析請求
try {
if (ok) {
parseConnection(socket);//2.2.5
parseRequest(input, output);//2.2.6
if (!request.getRequest().getProtocol().startsWith("HTTP/0"))
parseHeaders(input);//2.2.8
if (http11) {
// 若在請求頭中發(fā)現(xiàn)"EXpect:100-continue",則設(shè)置sendAck為true
//ackRequest方法檢查sendAck的值和是否允許分塊噪漾,如果為true向客戶端發(fā)送HTTP/1.1 100 Continue\r\n\r\n
ackRequest(output);
// If the protocol is HTTP/1.1, chunking is allowed.
if (connector.isChunkingAllowed())
response.setAllowChunking(true);
}
}
} catch (EOFException e) {
//很可能client或server中的一方斷開連接
ok = false;
finishResponse = false;
} catch (ServletException e) {
//...
} catch (InterruptedIOException e) {
//...
} catch (Exception e) {
//...
}
try {
((HttpServletResponse) response).setHeader("Date", FastHttpDateFormat.getCurrentDate());
if (ok) {
connector.getContainer().invoke(request, response);//如果處理正常調(diào)用容器的invoke方法
}
} catch (ServletException e) {
//...
} catch (InterruptedIOException e) {
//...
} catch (Throwable e) {
//...
}
// 完成處理請求
if (finishResponse) {
//省略...
//主要是調(diào)用response.finishResponse();
}
//必須檢查Connection是否被設(shè)置為close或者在HTTP/1.0下
if ( "close".equals(response.getHeader("Connection")) ) {
keepAlive = false;
}
// 如果keepAlive為true并且解析沒有發(fā)生錯誤,則繼續(xù)while循環(huán)
status = Constants.PROCESSOR_IDLE;
// 回收request和response對象
request.recycle();
response.recycle();
}
try {
shutdownInput(input);
socket.close();
} catch (IOException e) {
//...
} catch (Throwable e) {
//...
}
socket = null;
}
2.2.5 parseConnection(Socket socket)
解析連接信息且蓬,獲取Internet地址欣硼,檢查是否使用代理
private void parseConnection(Socket socket)
throws IOException, ServletException {
if (debug >= 2)
log(" parseConnection: address=" + socket.getInetAddress() +
", port=" + connector.getPort());
((HttpRequestImpl) request).setInet(socket.getInetAddress());
if (proxyPort != 0)
request.setServerPort(proxyPort);
else
request.setServerPort(serverPort);
request.setSocket(socket);
}
2.2.6 parseRequest(SocketInputStream input, OutputStream output)
requestLine
是一個HttpRequest
實例,其中包含3個char[]
,分別對應method,uri,protocol.調(diào)用SocketInputStream
的readRequestLine()
方法填充請求行恶阴,再獲得對應的請求方法诈胜,URI,協(xié)議版本,(查詢參數(shù),session ID)
private void parseRequest(SocketInputStream input, OutputStream output)
throws IOException, ServletException {
// 解析請求行
input.readRequestLine(requestLine);//2.2.7
status = Constants.PROCESSOR_ACTIVE;
String method = new String(requestLine.method, 0, requestLine.methodEnd);//獲得請求方法
String uri = null;
String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);//獲得協(xié)議版本信息
if (protocol.length() == 0)
protocol = "HTTP/0.9";
// 如果是HTTP/1.1需要在解析請求后保持連接
if ( protocol.equals("HTTP/1.1") ) {
http11 = true;
sendAck = false;
} else {
http11 = false;
sendAck = false;
// 對于HTTP/1.0, 默認不保持連接冯事,除非指定Connection:Keep-Alive
keepAlive = false;
}
// 驗證請求行
if (method.length() < 1) {
throw new ServletException(sm.getString("httpProcessor.parseRequest.method"));
} else if (requestLine.uriEnd < 1) {
throw new ServletException(sm.getString("httpProcessor.parseRequest.uri"));
}
// 解析URI上的查詢參數(shù)
int question = requestLine.indexOf("?");
if (question >= 0) {
request.setQueryString(new String(requestLine.uri, question + 1,requestLine.uriEnd - question - 1));//設(shè)置查詢參數(shù)
if (debug >= 1)
log(" Query string is " +
((HttpServletRequest) request.getRequest())
.getQueryString());
uri = new String(requestLine.uri, 0, question);//獲得URI
} else {
request.setQueryString(null);
uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}
// Checking for an absolute URI (with the HTTP protocol)
//檢驗絕對URI路徑和HTTP協(xié)議
if (!uri.startsWith("/")) {
int pos = uri.indexOf("://");
// 解析協(xié)議和主機名
if (pos != -1) {
pos = uri.indexOf('/', pos + 3);
if (pos == -1) {
uri = "";
} else {
uri = uri.substring(pos);
}
}
}
// 從請求URI解析session ID
int semicolon = uri.indexOf(match);//match=";jsessionid="0
if (semicolon >= 0) {
String rest = uri.substring(semicolon + match.length());
int semicolon2 = rest.indexOf(';');
if (semicolon2 >= 0) {
request.setRequestedSessionId(rest.substring(0, semicolon2));//設(shè)置session ID
rest = rest.substring(semicolon2);
} else {
request.setRequestedSessionId(rest);
rest = "";
}
request.setRequestedSessionURL(true);
uri = uri.substring(0, semicolon) + rest;
if (debug >= 1)
log(" Requested URL session id is " +
((HttpServletRequest) request.getRequest())
.getRequestedSessionId());
} else {
request.setRequestedSessionId(null);
request.setRequestedSessionURL(false);
}
//修正RUI(使用字符串操作)
String normalizedUri = normalize(uri);
if (debug >= 1) log("Normalized: '" + uri + "' to '" + normalizedUri + "'");
// 設(shè)置請求屬性
((HttpRequest) request).setMethod(method);//設(shè)置請求方法
request.setProtocol(protocol);//設(shè)置協(xié)議版本
if (normalizedUri != null) {
((HttpRequest) request).setRequestURI(normalizedUri);
} else {
((HttpRequest) request).setRequestURI(uri);
}
request.setSecure(connector.getSecure());
request.setScheme(connector.getScheme());
if (normalizedUri == null) {
log(" Invalid request URI: '" + uri + "'");
throw new ServletException("Invalid URI: " + uri + "'");
}
if (debug >= 1)
log(" Request is '" + method + "' for '" + uri +
"' with protocol '" + protocol + "'");
}
2.2.7 readRequestLine(HttpRequestLine requestLine)
readRequestLine
方法會分別填充請求行,URI,協(xié)議版本
public void readRequestLine(HttpRequestLine requestLine)
throws IOException {
// 檢查是否已回收
if (requestLine.methodEnd != 0)
requestLine.recycle();
// 檢查空白行
int chr = 0;
do { // 跳過 CR(\r) 或 LF(\n)
try {
chr = read();
} catch (IOException e) {
chr = -1;
}
} while ((chr == CR) || (chr == LF));
if (chr == -1)
throw new EOFException (sm.getString("requestStream.readline.error"));
pos--;
// 讀取方法名
int maxRead = requestLine.method.length;//這里的char[]數(shù)組的長度為8
int readStart = pos;
int readCount = 0;
boolean space = false;
//讀取到空格說明方法名已解析完成
while (!space) {
// 如果char[]已滿焦匈,將容量翻倍
if (readCount >= maxRead) {
if ((2 * maxRead) <= HttpRequestLine.MAX_METHOD_SIZE) {
char[] newBuffer = new char[2 * maxRead];
System.arraycopy(requestLine.method, 0, newBuffer, 0,
maxRead);
requestLine.method = newBuffer;
maxRead = requestLine.method.length;
} else {
throw new IOException
(sm.getString("requestStream.readline.toolong"));
}
}
// 檢查是否讀取到末尾
if (pos >= count) {
int val = read();
if (val == -1) {
throw new IOException
(sm.getString("requestStream.readline.error"));
}
pos = 0;
readStart = 0;
}
// 檢查是否讀取到空格
if (buf[pos] == SP) {
space = true;
}
//填充char[] method
requestLine.method[readCount] = (char) buf[pos];
readCount++;
pos++;
}
requestLine.methodEnd = readCount - 1;//設(shè)置請求方法結(jié)束位置
// 解析URI
maxRead = requestLine.uri.length;
readStart = pos;
readCount = 0;
space = false;
boolean eol = false;
while (!space) {
if (readCount >= maxRead) {
if ((2 * maxRead) <= HttpRequestLine.MAX_URI_SIZE) {
char[] newBuffer = new char[2 * maxRead];
System.arraycopy(requestLine.uri, 0, newBuffer, 0,
maxRead);
requestLine.uri = newBuffer;
maxRead = requestLine.uri.length;
} else {
throw new IOException(sm.getString("requestStream.readline.toolong"));
}
}
// 檢查是否讀取到末尾
if (pos >= count) {
int val = read();
if (val == -1)
throw new IOException(sm.getString("requestStream.readline.error"));
pos = 0;
readStart = 0;
}
// 檢查是否讀取到空格
if (buf[pos] == SP) {
space = true;
} else if ((buf[pos] == CR) || (buf[pos] == LF)) {
// HTTP/0.9 風格的請求
eol = true;
space = true;
}
//填充 char[] uri
requestLine.uri[readCount] = (char) buf[pos];
readCount++;
pos++;
}
requestLine.uriEnd = readCount - 1;//設(shè)置uri結(jié)束位置
// 解析協(xié)議
maxRead = requestLine.protocol.length;
readStart = pos;
readCount = 0;
//是否結(jié)束
while (!eol) {
if (readCount >= maxRead) {
if ((2 * maxRead) <= HttpRequestLine.MAX_PROTOCOL_SIZE) {
char[] newBuffer = new char[2 * maxRead];
System.arraycopy(requestLine.protocol, 0, newBuffer, 0,
maxRead);
requestLine.protocol = newBuffer;
maxRead = requestLine.protocol.length;
} else {
throw new IOException(sm.getString("requestStream.readline.toolong"));
}
}
// 檢查是否讀取到末尾
if (pos >= count) {
int val = read();
if (val == -1)
throw new IOException(sm.getString("requestStream.readline.error"));
pos = 0;
readStart = 0;
}
//是否結(jié)束
if (buf[pos] == CR) {
// 跳過\r
} else if (buf[pos] == LF) {
eol = true;
} else {
//填充char[] protocol
requestLine.protocol[readCount] = (char) buf[pos];
readCount++;
}
pos++;
}
requestLine.protocolEnd = readCount;//設(shè)置協(xié)議版本結(jié)束位置
}
2.2.8 parseHeaders(SocketInputStream input)
一個HttpHeader
包含一個name數(shù)組和value數(shù)組,通過SocketInputStream
中的readHeader
方法填充HttpHeader
對象,整個過程和readRequestLine
類似.
通過HttpHeader
對象昵仅,設(shè)置request對象對應的屬性
private void parseHeaders(SocketInputStream input)
throws IOException, ServletException {
while (true) {
HttpHeader header = request.allocateHeader();//分配一個HttpHeader對象缓熟,從對象池中
// 解析請求頭
input.readHeader(header);
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
} else {
throw new ServletException
(sm.getString("httpProcessor.parseHeaders.colon"));
}
}
String value = new String(header.value, 0, header.valueEnd);//獲得value值
if (debug >= 1)
log(" Header " + new String(header.name, 0, header.nameEnd)+ " = " + value);
// 設(shè)置對應的請求頭
if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) {//authorization頭
request.setAuthorization(value);
} else if (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {//accept-language頭
parseAcceptLanguage(value);
} else if (header.equals(DefaultHeaders.COOKIE_NAME)) {//cookie頭
Cookie cookies[] = RequestUtil.parseCookieHeader(value);//將value解析成Cookie數(shù)組
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals
(Globals.SESSION_COOKIE_NAME)) {//判斷cookie名是否為JSESSIONID
if (!request.isRequestedSessionIdFromCookie()) {
// 只接受第一個session ID
request.setRequestedSessionId(cookies[i].getValue());//設(shè)置session ID
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
if (debug >= 1)
log(" Requested cookie session id is " +
((HttpServletRequest) request.getRequest())
.getRequestedSessionId());
}
}
if (debug >= 1)
log(" Adding cookie " + cookies[i].getName() + "=" +
cookies[i].getValue());
request.addCookie(cookies[i]);//添加cookie到request對象
}
} else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {//content-length頭
int n = -1;
try {
n = Integer.parseInt(value);
} catch (Exception e) {
throw new ServletException
(sm.getString("httpProcessor.parseHeaders.contentLength"));
}
request.setContentLength(n);
} else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {//content-type頭
request.setContentType(value);
} else if (header.equals(DefaultHeaders.HOST_NAME)) {//host頭
int n = value.indexOf(':');
if (n < 0) {
if (connector.getScheme().equals("http")) {
request.setServerPort(80);//設(shè)置http協(xié)議端口
} else if (connector.getScheme().equals("https")) {
request.setServerPort(443);//設(shè)置https協(xié)議端口
}
if (proxyName != null)
request.setServerName(proxyName);
else
request.setServerName(value);
} else {
if (proxyName != null)
request.setServerName(proxyName);
else
request.setServerName(value.substring(0, n).trim());
if (proxyPort != 0)
request.setServerPort(proxyPort);
else {
int port = 80;
try {
port =Integer.parseInt(value.substring(n+1).trim());
} catch (Exception e) {
throw new ServletException
(sm.getString("httpProcessor.parseHeaders.portNumber"));
}
request.setServerPort(port);
}
}
} else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {//connection頭
if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {//close值
keepAlive = false;
response.setHeader("Connection", "close");
}
} else if (header.equals(DefaultHeaders.EXPECT_NAME)) {//expect頭
if (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE))//100-continue值
sendAck = true;
else
throw new ServletException
(sm.getString("httpProcessor.parseHeaders.unknownExpectation"));
} else if (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {//transfer-encoding頭
//request.setTransferEncoding(header);
}
request.nextHeader();//讀取下一個請求頭
}
}
2.3 Request對象
在調(diào)用getParameter
,getParameterMap
,getParameterNames
,getParameterValues
時會先調(diào)用parseParameters
方法解析請求參數(shù)
2.3.1 parseParameters()
parseParameters
方法將解析結(jié)果放入ParameterMap
對象中;ParameterMap
基礎(chǔ)自HashMap
,添加了鎖定屬性,當被鎖定時不允許修改
protected void parseParameters() {
if (parsed)//如果已解析直接返回
return;
ParameterMap results = parameters;//初始化ParameterMap對象
if (results == null)
results = new ParameterMap();
results.setLocked(false);//解除鎖定
String encoding = getCharacterEncoding();//獲得編碼信息
if (encoding == null) encoding = "ISO-8859-1";//默認編碼
// 解析查詢字符串中的參數(shù)
String queryString = getQueryString();
try {
RequestUtil.parseParameters(results, queryString, encoding);//解析查詢字符串
} catch (UnsupportedEncodingException e) {
;
}
// 從正文中的參數(shù)
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)&& (this.stream == null)
&& "application/x-www-form-urlencoded".equals(contentType)) {
//判斷條件:POST方法,content-length>0,有ServletInputStream,content-type=application/x-www-form-urlencoded
try {
int max = getContentLength();
int len = 0;
byte buf[] = new byte[getContentLength()];
ServletInputStream is = getInputStream();
while (len < max) {//讀取數(shù)據(jù)
int next = is.read(buf, len, max - len);
if (next < 0 ) {
break;
}
len += next;
}
is.close();
if (len < max) {
//FIX ME,當實際接收長度小于content-length聲明的長度時
//上面的代碼中檢查next=-1可以預防出現(xiàn)死循環(huán)
//但是這個bug必須在mod_jk模塊中
//記錄額外的信息用于debug mod_jk
StringBuffer msg = new StringBuffer();
msg.append("HttpRequestBase.parseParameters content length mismatch\n");
msg.append(" URL: ");
msg.append(getRequestURL());
msg.append(" Content Length: ");
msg.append(max);
msg.append(" Read: ");
msg.append(len);
msg.append("\n Bytes Read: ");
if ( len > 0 ) {
msg.append(new String(buf,0,len));
}
log(msg.toString());
throw new RuntimeException
(sm.getString("httpRequestBase.contentLengthMismatch"));
}
RequestUtil.parseParameters(results, buf, encoding);//解析參數(shù)
} catch (UnsupportedEncodingException ue) {
;
} catch (IOException e) {
throw new RuntimeException
(sm.getString("httpRequestBase.contentReadFail") +
e.getMessage());
}
}
results.setLocked(true);
parsed = true;
parameters = results;
}
至此整個HTTP協(xié)議的解析流程就完成了
3. 總結(jié)
- 連接器負責接收請求,處理器負責解析請求,每個處理器擁有自己的Request和Response對象,這兩個對象可以重復使用
- 處理器處理流程
- 解析連接信息:設(shè)置Internet地址和代理信息
- 解析請求行:請求方法,URI,協(xié)議版本摔笤,查詢參數(shù)(如果有),keep-alive屬性,session ID(如果禁用了cookie),標準化URI地址
- 解析請求頭:將請求頭設(shè)置到對應的屬性中,其中有幾個重要的屬性,cookie,content-length和content-type(在處理正文時會用到),conection(主要檢查是否close值)
- 解析參數(shù):先解析URI中的查詢參數(shù),再解析正文中的參數(shù)
- 調(diào)用容器的invoke方法
PS:
看完Tomcat如何解析才發(fā)現(xiàn)自己對HTTP協(xié)議不了解,只考慮到了HTTP協(xié)議的格式,沒有考慮到不同版本的區(qū)別,特殊請求頭的處理,不同請求方法的處理,cookie的解析,session的處理够滑。
隨著HTTP協(xié)議的發(fā)展,解析的難度越來越大,要求也越來越高(高效+正確);以上的代碼在Tomcat4中已經(jīng)廢棄,換了更高效的連接器.等有時間去看一下Tomcat8的源碼