我們到底能走多遠系列(11)
扯淡:
最近行情不好嗎?跳槽的比較少嘛,哈哈。有些人一直抗拒跳槽,覺得弊端很多:什麼業務積累,職務,別人覺得你不可靠啊等等。我就想:這一輩子時間有限,何必為了一顆可以乘涼的樹,放棄穿過森林的機會呢?祝在跳槽路上的朋友 順利!(ps:個人喜歡面試 那種刺激感)
最爽不顧躺著,最美不過夕陽。秋天的夕陽是一年中最華麗的,各位不要錯過哦。
主題:
在tomcat中,一個http請求,會被送到Http11Processor類,執行這個類的process(Socket theSocket) 處理的傳入的Socket,Socket裡面裝的就是http訊息。
tomcat是如何調用到Http11Processor的process方法的,可以參照:http://blog.csdn.net/woorh/article/details/8017323
Http11Processor在org.apache.coyote.http11包下。
Http11Processor的rocess方法中,用inputBuffer.parseRequestLine();調用瞭解析http訊息的請求行。這裡的inputBuffer是tomcat自訂的InternalInputBuffer。
需要瞭解的是:
1,org.apache.coyote.Request 是tomcat內部使用用於存放關於request訊息的資料結構
2,org.apache.tomcat.util.buf.MessageBytes 用於存放訊息,在org.apache.coyote.Request中大量用於存放解析後的byte字元
3,org.apache.tomcat.util.buf.ByteChunk 真正用於存放資料的資料結構,存放的是byte[],org.apache.tomcat.util.buf.MessageBytes使用它。
大流程:
http訊息通過inputBuffer解析後放到Request中,Request把它放到相應的MessageBytes,最後MessageBytes把它存到ByteChunk裡。
以上都可以通過方法調用來完成流程。
主要關注的是解析的原始碼,在查看原始碼前需要瞭解http請求行的結構:可以參照:http://www.cnblogs.com/killbug/archive/2012/10/10/2719142.html
閱讀前準備:
1,方法中全部的異常時,會調用getString方法,其實就是StringManager的寫日誌方法,這是tomcat中統一的管理日誌的方法。詳細的解釋在前一篇中已經幾時過了:Tomcat StringManager閱讀學習
2,
逸出字元 |
意義 |
ASCII碼值(十進位) |
\a |
響鈴(BEL) |
007 |
\b |
退格(BS) ,將當前位置移到前一列 |
008 |
\f |
換頁(FF),將當前位置移到下頁開頭 |
012 |
\n |
換行(LF) ,將當前位置移到下一行開頭 |
010 |
\r |
斷行符號(CR) ,將當前位置移到本行開頭 |
013 |
\t |
水平製表(HT) (跳到下一個TAB位置) |
009 |
\v |
垂直製表(VT) |
011 |
\\ |
代表一個反斜線字元''\' |
092 |
源碼:
public void parseRequestLine() throws IOException { int start = 0; // // Skipping blank lines // 忽略空行 // byte chr = 0; do { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } chr = buf[pos++]; } while ((chr == Constants.CR) || (chr == Constants.LF)); pos--; // Mark the current buffer position start = pos; // // Reading the method name // Method name is always US-ASCII // // space類似於開關一樣,當為false時,查內容,為true時,去除空行時間 boolean space = false; while (!space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } // Spec says no CR or LF in method name if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) { throw new IllegalArgumentException( sm.getString("iib.invalidmethod")); } // Spec says single SP but it also says be tolerant of HT // 查出第一個空格,tab居然也是允許的 if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true;//跳出迴圈 // 把下標記錄下來,這裡的method()得到一個Requast的MessageBytes:methodMB request.method().setBytes(buf, start, pos - start); } pos++; } // Spec says single SP but also says be tolerant of multiple and/or HT // 忽略空格後面的空格或者tab,因為是忽略的內容所以不需要什麼start while (space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { pos++;// 忽略的方式就是繼續移動下標 } else { space = false; } } // Mark the current buffer position start = pos; // 出現start了,後面肯定是需要記錄下標 int end = 0; int questionPos = -1; // // Reading the URI // 上面是源碼的注釋,URI是什嗎?你懂的 // boolean eol = false; while (!space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } // Spec says single SP but it also says be tolerant of HT // 尋找第二個空格,第一個空格和第二個空格之間就是傳說中的URI if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; end = pos; } else if ((buf[pos] == Constants.CR) || (buf[pos] == Constants.LF)) { // HTTP/0.9 style request // 為了相容HTTP/0.9格式 eol = true; space = true; end = pos; } else if ((buf[pos] == Constants.QUESTION) // 遇到‘?’了 && (questionPos == -1)) { // 把問號的位置先記錄下來 questionPos = pos; } pos++; } // 把可能包含問號的URI的起始位和結束位記錄下來 request.unparsedURI().setBytes(buf, start, end - start); if (questionPos >= 0) {// 有問號的情況 // 問號位置記錄 request.queryString().setBytes(buf, questionPos + 1, end - questionPos - 1); // 把URI記錄下來 request.requestURI().setBytes(buf, start, questionPos - start); } else { request.requestURI().setBytes(buf, start, end - start); } // Spec says single SP but also says be tolerant of multiple and/or HT // 這段算是重複代碼吧,就是忽略空格用和tab用的 while (space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { pos++; } else { space = false; } } // Mark the current buffer position start = pos; end = 0; // // Reading the protocol // Protocol is always US-ASCII // // eol標誌位是為了標記是否是HTTP/0.9 style request 前面代碼已經提到 // 最後一樣:protocol(HTTP/ 1.1或1.0) while (!eol) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } // 查出 /r/n(CRLF) if (buf[pos] == Constants.CR) { end = pos; } else if (buf[pos] == Constants.LF) { if (end == 0) end = pos; eol = true; } pos++; } // 至此把head分成三部分,放到Request定義好的MessageBytes中去了 if ((end - start) > 0) { request.protocol().setBytes(buf, start, end - start); } else { request.protocol().setString(""); } }
總結習點:
1,利用表示位來控制解析式每個while的功能,上面代碼用的是space
2,不用截斷的方式儲存需要的內容,而是記錄開始和結束的下標。
讓我們繼續前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不會成功。
共勉