Jetty9原始碼剖析 - Connection元件 - HttpChannel
轉載自ph0ly:http://www.ph0ly.com
一、概念
HttpChannel主要協助HttpConnection觸發Server或Handler的處理操作,它內部會包裝本次請求對應的Request、Response,並控制它們的生命週期。HttpChannelOverHttp是增強的HttpChannel,它對外暴露請求的引數處理介面(包括請求行、請求頭、請求體等)。HttpChannel同時會提供Servlet3的非同步特性,由於文章篇幅有限,這裡會省略非同步的分析,Servlet3非同步特性將會在後續的專題文章中講解
二、繼承體系
繼承體系比較簡潔,HttpChannel實現了HttpOutput.Interceptor,也就是可以攔截HttpOutput的寫入操作,同時自己是一個Runnable,而對於HttpChannelOverHttp,除了繼承自HttpChannel外,還實現了HttpParser.RequestHandler,HttpParser.ComplianceHandler,其實最核心的還是HttpParser.HttpHandler介面,這個介面抽象了當HttpParser解析完每個模組時需要呼叫的方法,這樣在HttpParser解析完每一部分的時候,就能向HttpChannelOverHttp發起呼叫,從而將資料與HttpChannelOverHttp關聯
三、總體架構
HttpChannel主要就是串聯Request、Response,而HttpChannelOverHttp在HttpChannel之上進一步提供增加Request需要的MetaData,HttpParser解析完成請求行會將資料放到MetaData.Request裡面,而請求頭解析完成後,會放到HttpFields,而這個HttpFields還會和MetaData.Request建立關聯,這樣MetaData就包含了一次請求需要的基礎資料,Response也類似,在Response類會維護一個HttpFields物件,表示響應的頭部,而響應行如果沒有指定,預設會建立一個200 OK的行,這樣後續HttpGenerator會拿著這些元資料生成響應報文。另外值得一提的是在Jetty裡面每條連線都是共享一個Request、Response,因為HTTP/1.x是順序執行,因此也就不存在在同一連線併發爭搶這些物件,所以Jetty優化了這兩個物件的生命週期,當Connection建立的時候,會建立HttpChannel,這樣其實Request、Response都建立好了,對於每次請求處理完成後,HttpChannel會清空Request、Response的所有資料,這樣又恢復到建立之初的資料,這個優化在大量請求的情況下,是非常有效的,要知道頻繁的new物件一方面會增加記憶體分配的耗時,同時分配後GC的操作也會非常頻繁,效能也會有一些影響,所以Jetty的這個優化還是不無道理的
四、原始碼剖析
1. 建立
HttpChannelOverHttp看起來還是比較簡單,主要就是調super了
super自然就是HttpChannel,這裡我們可以看到建立了HttpChannelState,這個主要用於Servlet3非同步呼叫處理(這篇文章將會從同步的角度來分析),同時這裡會建立Request、Response,並建立HttpInput、HttpOutput與它們兩者關聯,可以看到一個HttpChannel唯一對映一個Request、Response,而一個HttpChannel唯一對映一個HttpConnection,所以在同一條連線上,Request、Response是複用的(具體原因上面也提到過)
2. HTTP引數準備
前面的文章提到過,HttpParser在解析資料後,會呼叫HttpParser.RequestHandler,而HttpChannelOverHttp實現了這個介面,因此會觸發
2.1 準備請求行
startRequest方法會在HttpParser請求行解析完成後呼叫,將請求行的方法、URI、Http版本號存下來
2.2 新增請求頭
parsedHeader表示已經完成解析的請求頭,HttpField表示一個請求頭,裡面包含請求頭的K/V值
這裡會判斷一些特殊的頭,例如Connection這樣的,可能是要求處理完後Close,也可能是Keep-Alive,Host這種也需要拿到放到MetaData
之後將這個請求頭放到HttpFileds類,儲存下來
2.3 請求頭完成
當HttpParser完成頭部解析後,會觸發HttpChannelOverHttp.headerComplete,這裡會根據不同的協議請求頭,例如對於HTTP/1.0,需要判斷是否是持久連線,而對於HTTP/1.1,預設連線就是持久,那就需要判斷是否帶了Connection: Close,處理完立即關閉
如果不是持久連線,這裡會把HttpGenerator的持久連線設定為false,以便後續Response完成後,關閉連線
然後呼叫onRequest,將MetaData設定到Request裡面,如下圖
這裡會判斷客戶端是否要求伺服器傳送時間,如果是就直接將這個時間加到了Response的HttpFields了
中間我們可以看到Request接收了MetaData.Request,後續應用層就能從Request拿資料了
2.4 請求體處理與完成
當HttpParser對HTTP請求體解析的時候,解析到每一塊請求體緩衝都將放到Request裡面的HttpInput的緩衝佇列(inputQ)裡面去,如下圖
通常情況對於HttpParser來說是不會立即處理請求體的,在應用層程式碼獲取請求體時才會觸發請求體解析,而請求體的解析其實就是一個個緩衝,在需要的時候挨個從EndPoint讀出來,這樣效能也就不存在問題
當整個請求體也處理完成了,這個時候HttpParser會呼叫contentComplete、messageComplete,其中messageComplete會通知HttpInput已經EOF了,這樣就完成了整個請求體資料的處理
3. 呼叫
這裡我們主要研究同步模式下的呼叫,非同步模式涉及了多狀態輪轉,比較複雜,後續的專題文章將會講解
HttpChannel對外提供的一個handle的核心方法,也就是HttpConnection解析完成後呼叫的
上圖就是核心的同步模式下呼叫的邏輯,即action=DISPATCH。在DISPATCH分支裡面會做一些準備,校驗MetaData、設定Request為未處理,重新設定HttpOutput狀態,然後把DispatcherType改為REQUEST,在利用使用者自定義的調整Request裡面的一些配置
核心的就是下面那一行,如果請求未被處理,就會呼叫getServer().handle(this),也就是讓前面我們定義的Server來處理,而Server的handle方法會拿到之前設定的Handler來處理這個請求,這樣就打通了整個呼叫鏈路
4. 完成
當Server觸發Handler呼叫完成整個鏈路(Handler -> Filter -> Servlet),最後在Servlet觸發往HttpOutput寫資料,資料將會被放到緩衝,呼叫結束,結束後進入最下面的_state.unhandle,這時候action切換為COMPLETE,也就是進入上圖的分支
如果這個請求進入COMPLETE還沒完成處理,直接給404,如果處理完成了,關閉Response的HttpOutput(ServletOutputStream),HttpOutput.close會將積攢下來的Buffer全部刷出去,這個時候會呼叫HttpChannel.write方法(之前提到HttpChannel實現了HttpOutput.Interceptor)
HttpChannel.write實現如上圖,其實就是簡單記個數,然後調sendResponse
sendResponse會觸發_transport來發送資料,前面提到過_transport其實就是HttpConnection。可以看到這裡會將MetaData.Response傳給HttpConnection.send,其實也就是將響應狀態行、響應頭傳遞下去了,而請求體作為另一個content引數傳進來,同時附帶一個回撥,完成寫入後呼叫
最後整個資料刷完後,會將HttpChannelState改為COMPLETED,已完成,然後呼叫自身onCompleted
這裡會呼叫HttpConnection.onCompleted,我們一起來回顧下
從上面看這個方法邏輯很長,不過第一個分支在statusCode為101才會觸發,通常我們也是>200,這裡就忽略
可以看到呼叫了_channel.recycle,_generator.reset,_parser.reset,將這些複用元件全部置為初始狀態。我們再來看下HttpChannel.recycle在幹什麼,準確來說HttpChannelOverHttp.recycle
HttpChannelOverHttp.recycle會把metadata回收了,以及HttpFields頭也全部清空,再來看下super的回收
HttpChannel將Request、Response全部回收,而Request回收時會把HttpInput回收,Response回收時會把HttpOutput回收,至此整個HttpChannel請求呼叫完成呼叫
五、總結
HttpChannel從同步邏輯來看,還是比較容易分析的,由於Servlet3支援非同步特性,因此HttpChannel在支援非同步這塊加入了HttpChannelState這樣複雜的狀態機,從而導致HttpChannel的程式碼異常複雜,個人覺得Jetty這塊的抽象不算非常簡潔。感興趣的讀者可以下來分析下原始碼,相信也能收穫不少知識。接下來的文章會繼續分析HttpParser、HttpInput、HttpOutput等元件,歡迎大家持續關注~