Jetty9原始碼剖析 - Connection元件 - HttpGenerator
轉載自ph0ly:http://www.ph0ly.com
一、概念
HttpGenerator顧名思義就是HTTP協議報文生成器,用於HTTP響應報文的生成,和HttpParser相對應。相比於HttpParser複雜的狀態機,HttpGenerator狀態機設計更精簡一點
二、流程圖
Servlet(或Filter)通過向輸出流中寫資料(當然也可以不寫),完成業務處理後,通過HttpOutput.close操作調回HttpChannel,再調到HttpConnection,HttpConnection利用HttpGenerator利用三步操作:generateResponseLine、generateHeaders、content或prepareChunk來生成響應報文,而其中content是不需要處理的,chunk分塊傳輸是需要每次計算塊的大小,並按照塊格式傳輸,最後HttpConnection的SendCallback會呼叫EndPoint.write,往SocketChannel寫入資料
三、狀態機
狀態機相比於HttpParser還算是比較簡單,就5種狀態,而且輪轉也比較直接
相比於HttpParser,HttpGenerator並不會將每個請求行、請求頭、請求體每個位置都標上一個狀態,而是簡化成資料提交狀態,這樣狀態機就會簡單很多,之所以這裡可以簡化,是因為HttpGenerator並不會像HttpParser一樣將解析後的資料放到其他元件裡面,HttpGenerator只是生成HTTP報文
- START -> COMMITTED -> COMPLETING -> END,這種狀態遷移是當資料一次性寫不完,從START切位COMMITTED,之後可能多次停留在COMMITTED狀態,表示要不斷生成新的資料,當資料生成完了,就會進入COMPLETING狀態,這個狀態會處理一些善後操作,之後切為END狀態,表示本次生成完全結束
- START -> COMPLETING -> END,這種狀態遷移一般是一次性就能將資料完,也就不需要COMMITTED這個中間狀態
- START -> COMPLETING_1XX,這種狀態遷移是出現了1XX的狀態碼,這個狀態其實也是一種終結態,不會響應body體
四、原始碼剖析
1. 建立
建構函式比較簡單,沒啥說的,就是把Jetty伺服器目前的版本號記下來,後面再response報文裡面帶上
2. 生成響應
2.1 START狀態
START相對還是稍微冗長一點,這裡會根據HTTP協議版本來判斷是否是持久連線,如果沒有Buffer就會要求一塊新的(通過Action來讓外層的SendCallback處理),值得注意的是這裡申請的頭部Buffer是HeapByteBuffer,也就是堆內緩衝區,區別於響應體的DirectByteBuffer,使用了堆外緩衝區(頭部通常都是小資料量),也是不難理解的。申請完Buffer就可以切換到讀模式,生成響應行(狀態行),呼叫的是generateResponseLine,之後呼叫generateHeaders生成響應頭,再通過應用層傳入的content(響應體)是否有資料,如果有資料,就需要判斷頭部是否設定了要按照分塊來傳輸,如果分塊傳輸就需要準備分塊傳輸的元資料,最後根據當前是否是最後一次生成(請求體的資料可能會很大,分多次),如果是就會直接將狀態切成COMPLETING,否則進入COMMITTED狀態
2.2.1 狀態行(響應行)生成
可以看到狀態行(響應行)實現還是比較簡單,唯一需要注意的是這裡用了status狀態碼來從__preprepared裡面去索引響應行資料,這個算是一個比較有特點的優化,如果狀態碼在這裡查不到,就按照標準的協議格式一個個位元組放進去
2.2.2 響應頭生成
由於篇幅有限,這裡擷取比較核心的程式碼
生成響應頭的時候會判斷一些需要特殊處理的頭,例如CONTENT_LENGTH,因為生成響應頭就需要知道當前響應體大小,或者對於TRANSFER_ENCODING就需要標識當前是分塊傳輸,後面在處理響應體時就能判斷,如果是普通的不需要處理的響應頭,直接調putTo,這個方法會將頭按照標準格式放到緩衝中
2.2.3 分塊傳輸生成
分塊傳輸其實並不複雜,可以看到,直接往這個chunk緩衝按照格式放資料,先把塊大小放進去,然後放換行
2.2 COMMITTED狀態
前面其實說到COMMITTED就是內容較大的情況使用的狀態,可能是定長響應體或分塊傳輸響應體
這裡會看是否上層給的content仍然有,如果有就要看是不是分塊傳輸,如果分塊一樣的準備元資料,如果是最後一塊資料,直接切成COMPLETING狀態,表示可以結束了,否則繼續刷資料,而且狀態仍然停留在COMMITTED,也就是下次SendCallback會繼續掉HttpGenerator生成時,仍然繼續這個分支,繼續傳資料
2.3 COMPLETING狀態
資料傳輸完成後,就需要進行收尾工作,也就是COMPLETING狀態要做的,如下圖
這個狀態會判斷是否是分塊傳輸,如果是分塊傳輸,就需要補一個結束的分塊標記,即0\r\n(不清楚的讀者可以去看下HttpParser章節,有專門將分塊傳輸的格式),最後將狀態切為END,至此HttpGenerator狀態輪轉就完成了
2.4 END狀態
END是HttpGenerator的終結狀態,這裡會返回上層一個完成的標記,表示不需要呼叫HttpGenerator生成資料了
五、總結
HttpGenerator作為響應報文生成的工具類,設計上不算很複雜,其核心就是按照HTTP規範生成這些字串,保證正確性的同時,HttpGenerator也做了很多效能優化,例如將狀態行按照狀態碼存放一個512大小的陣列,這樣就能通過陣列的索引查詢到這個狀態碼的響應行,用空間換時間的思路在Jetty的優化中還是比較常見的。這篇文章就寫到這裡,相信讀者對Jetty的HTTP響應解析有了一些認識。後續的文章我會做一些專題,例如Jetty中的效能優化,以及HTTP/2等專題文章,歡迎大家持續關注~