天天看點

擅長捉弄的記憶體馬同學:Valve記憶體馬

前言

記憶體馬的文章已經很久沒有更新過了, 這篇文章不太适合想直接學習利用Valve記憶體馬的師傅

,因為我這篇文章可能會有大篇筆墨去說Tomcat容器,至于原因就是我想更深入的了解一些Tomcat,而Valve記憶體馬屬于已經被師傅們玩爛了的一種方式,并且Valve記憶體馬的實作如果看過之前我寫的記憶體馬文章這個記憶體馬實作起來并不難。是以與其說這篇文章是講Valve記憶體馬,不如說是我學習Tomcat的個人總結,廢話也不多說,我們直接開始吧。

一、Tomcat基本結構

首先我們在學習記憶體馬之前先來重新學習一下Tomcat的基本架構。我們一層一層來進行分析。

擅長捉弄的記憶體馬同學:Valve記憶體馬

可以看到上面我搬來的這張圖,這個圖就将各個結構畫的很簡單了,當然其中的流程還有一些步驟,這裡我們慢慢說。

首先我們将結構進行單獨梳理:

一個Tomcat容器中隻有一個Server

一個Server可以包含多個Service

一個Service隻有一個Container,但是可以有多個Connectors

一個Connector會綁定一個port和protocol

接下來我們可以将Tomcat分為兩個部分,一部分為Connector,另一部分為Container。分别為圖中的左右兩邊的部分。(這塊的圖檔我就直接貼大哥們的圖了)

Connector

:顧名思義是用于接受請求并封裝Request和Response,然後交給Conntainer進行處理。它使用ProtocolHandler來處理請求。

我們來說明一下它的各個部分處理流程,首先Acceptor來監聽請求(前面提到一個Service隻有一個Container,但是可以有多個Connectors,這是因為請求有不同的協定,但不同的協定的位元組流通過不同Connectors最終都會得到統一的處理),AsyncTimeout用于檢查Request逾時,當接收到請求後,送出給Handler來處理接收到的Socket并将Socket位元組流發送給Processor進行處理(這之中應該有一個線程池用于提高處理速度),Processor在接收到EndPoint發送過來的Socket的後将其封裝成了Tomcat

Request并交給Adapter進行處理,Adapter在接收到Tomcat

Request後将其封裝為ServletRequest并将請求交給Container進行适配并進行具體的請求。

擅長捉弄的記憶體馬同學:Valve記憶體馬

Container :Container是用來處理請求的,它通過使用Pipeline-Valve(管道-

閥門)來進行處理(我們記憶體馬就是在這之中進行添加)

我們依舊來說明一下它的各個部分處理流程,當Container接收到Adapter發來的ServletRequest請求後首先會去調用最頂層的容器(EnginePipline)的Pipline來進行處理。之後在EnglinePipline的管道中依次執行,然後到下一個Pipline,最後到StandardWrapperValve,當執行到StandardWrapperValve後,會在StandardWrapperValve中建立FilterChain并調用doFilter方法來處理請求(這裡其實看過之前三個記憶體馬文章的師傅應該比較清楚這裡)doFilter方法會依次調用所有Filter的doFilter方法和Servlet下的所有service方法,到這裡Tomcat就成功處理了請求。

擅長捉弄的記憶體馬同學:Valve記憶體馬

我們通過上面的學習,大概已經簡單了解到了Tomcat的基本結構,當然這結構之中的細節其實有很多,有興趣的同學可以深入去跟一下各個位置配置讀取、請求處理和處理細節等。

二、Valve基礎知識

在我們剛才的學習中已經了解到了Valve在Tomcat中所處的位置,我們的Valve實際上屬于自定義Valve的位置當中,當然,在我們學習之前,我們先簡單了解一下Valve的基礎知識。

對于Valve的定義,其實我們通過上面的學習應該已經知道了Pipeline-Valve機制就是Container是用來處理請求的方式。

Valve:

譯文為閥門,在Container的四個容器類(StandardEngine、StandardHost、StandardContext、StandardWrapper)中都有一個PipeLine和多個Vavle,和我們之前上面分析Container的圖一樣,而Valve顧名思義就是像閥門一樣來控制我們管道當中的水流,。

在PipeLine生成時,同時會生成一個預設Valve實作,就是我們在調試中經常看到的StandardEngine、ValveStandardHostValve、StandardContextValve、StandardWrapperValve,當各個容器類調用getPipeLine().getFirst().invoke(Request

req, Response resp)時,會首先調用使用者添加的Valve,最後再調用預設的Standard-Valve。

擅長捉弄的記憶體馬同學:Valve記憶體馬

注意,每一個上層的Valve都是在調用下一層的Valve,并等待下層的Valve傳回後才完成的,這樣上層的Valve不僅具有Request對象,同時還能擷取到Response對象。使得各個環節的Valve均具備了處理請求和響應的能力。

三、Valve簡單實作

首先我們去看一下valve的接口

擅長捉弄的記憶體馬同學:Valve記憶體馬

getNext方法是擷取目前這個責任鍊節點的上一個節點。

setNext方法是設定目前這個責任鍊節點的上一個節點

invoke方法是邏輯處理位置

isAsyncSupported方法是表示是否支援異步

接下來我們寫一個類,實作這個接口。

擅長捉弄的記憶體馬同學:Valve記憶體馬

然後我們在server.xml配置檔案中配置一下我們自定義的Valve

擅長捉弄的記憶體馬同學:Valve記憶體馬

然後我們運作tomcat,最簡單實作了這個功能

擅長捉弄的記憶體馬同學:Valve記憶體馬

到這裡就出現了一個問題,我們所有的請求走到我們自定義的valve後就斷了。這是因為valve的動作定義在invoke中,通過調用this.getNext().invoke(req,

resp)将請求傳入下一個valve構成了pipeline管道,如果我們不調用下一個的invoke請求到此中斷,是以我們這麼寫是沒調用到下一個invoke請求的,是以我們一般不采取實作valve接口的方法,而是選擇繼承ValveBase類來簡單實作了valve接口,制作一個簡單的valve基類。我們繼承一個ValveBase來實作valve接口,接下來還是按上面的步驟來一遍。

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

四、Valve加載流程(Tomcat啟動流程)

我們通過簡單實作一個Valve,可以知道我們是通過在server.xml中設定了一個Valve,進而實作了自定義的Valve,但是Tomcat是如何讀取并加載我們自定義的Valve資訊的呢,接下來我們就從Tomcat的啟動流程開始分析我們是如何加載我們自定義的Valve的。

首先我們先要了解一些基礎知識,Tomcat啟動的簡單流程如下圖

擅長捉弄的記憶體馬同學:Valve記憶體馬

是以我們Tomcat是從Bootstrap開始運作的,我們跟到Tomcat的入口點開始分析,也就是Bootstrap的main方法,如下圖所示,首先Bootstrap進行了執行個體化、初始化操作,然後調用daemon的load方法和start方法,我們直接跟進load方法。

(1)bootstrap啟動流程

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

(2)Catalina.load()初始化操作

進入load方法,這一塊我們通過反射調用了Catalina的load方法,我們繼續向下跟進

擅長捉弄的記憶體馬同學:Valve記憶體馬

(3)Digester解析server.xml

裡我就不再多做贅述了,大家可以去自學一下。我們首先執行了this.createStartDigester(),這一步一句話概括就是将server.xml中的pattern、rule資訊放入了cache中,将rule資訊放入了rules中,當傳回給digester.parse進行解析的時候,讀取的節點規則資訊都放到了rules下面。

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

當我們将節點和規則都設定完畢後,開始執行digester.parse對其開始進行解析操作,這一步實際上就是将Tomcat各元件都執行個體化出來(這裡面其實Context比較特殊)

擅長捉弄的記憶體馬同學:Valve記憶體馬

這裡的parse的執行太多了,我就不一個一個進行跟進了,我們直接跟到解析我們自定義Valve的位置上,首先我們看一下之前的定義,可以看到比對到/Host/Valve節點的時候最終是去觸發了addValve方法(這裡的比對Object是standardHost)。

擅長捉弄的記憶體馬同學:Valve記憶體馬

然後我們跳到解析到這個節點的位置上去,進入ContainerBase中,最終執行this.pipeline.addValve(valve),我們進行跟進

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

我們向下最後跟進到了StandardPipline.addValve(),這裡其實就相當于按照容器作用域的配置順序來組織valve,将每個valve都設定了指向下一個valve的next引用。我們繼續向下。

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

我們回到Catalina然後繼續向下檢視,我們走到了Server初始化的位置this.getServer().init()接下來我們就快速過一下容器初始化和啟動的流程然後直接到Valve的位置。

擅長捉弄的記憶體馬同學:Valve記憶體馬

接下來就是進行JMX注冊Mbean,jmx這塊大家玩的應該也比較熟了,然後接下面開始循環調用service.init(),對service進行初始化,我們進去檢視一下

擅長捉弄的記憶體馬同學:Valve記憶體馬

這裡對engine進行了初始化操作接下來初始化Executor線程池、Connector連接配接器,最後結束。

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

(4)Catalina.start()啟動操作

到這裡可能就有一些疑問了,初始化了sever、service、engine正常來說是不是應該繼續初始化Host、Context、Wrapper巴拉巴拉的了,但是到了engine這裡就結束了,這是為什麼啊。這個的原因就是:我也不知道,但是在start方法中進行了Host元件的初始化然後進行的啟動操作,是以我們回到Bootstarp去檢視start方法。

擅長捉弄的記憶體馬同學:Valve記憶體馬

回到Bootstarp,進入start方法

擅長捉弄的記憶體馬同學:Valve記憶體馬

這裡我們可以看到一路向下将我們的初始化的容器都進行了啟動操作,最後我們跟到了this.engine.start()下面

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

我們進入this.engine.start()下面可以看到它,将子元件到children中,接下來托管給線程池處理。我們跟到這個下面。

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

進入sumbit,這裡其實就是用到了FutureTask傳入一個Callable實作,通過線程池去達到異步執行,流程就如下圖所示。可以看到這裡将FutrueTask的state設定為了NEW,這裡的state為以下幾個狀态

NEW:表示一個新的任務,初始狀态

COMPLETING:當任務被設定結果時,處于COMPLETING狀态,這是一個中間狀态。

NORMAL:表示任務正常結束。

EXCEPTIONAL:表示任務因異常而結束

CANCELLED:任務還未執行之前就調用了cancel(true)方法,任務處于CANCELLED

可能的狀态過渡:

1、NEW -> COMPLETING -> NORMAL:正常結束

2、NEW -> COMPLETING -> EXCEPTIONAL:異常結束

3、NEW -> CANCELLED:任務被取消

4、NEW -> INTERRUPTING -> INTERRUPTED:任務出現中斷

接下來外面就正常執行,跳轉到run()下面,我們跟到這個位置

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

跟進run方法,我們狀态為NEW,随後觸發call方法,我們跟進call方法

擅長捉弄的記憶體馬同學:Valve記憶體馬

進入call方法,我們來到了ContainerBase下面可以看到,我們執行了this.child.start(),到這裡我們就執行了start()方法,我們繼續一路向下

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

到這裡我們總算是回到了生命周期的位置,而在這裡我們開始了執行了init()和start(),這裡其實就是就是上面我們所說的差別,我們的Server、Service、Engine都是先在init()中進行初始化,然後在start()中進行啟動的,但是Host直接先初始化後運作了。這裡的初始化我們簡單跟進去看一下吧,實際上也沒初始化啥玩意,就是在jmx裡注冊一下子。我們回到生命周期去執行startInternal()

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

跟進startInternal(),這裡我們看到了,到這裡其實就是在添加一些Valve,然後就去執行父類ContainerBase的startInternal方法了

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

五、Tomcat責任鍊處理流程

關于Tomcat的啟動流程到Host這裡我們就不在繼續跟進去了,繼續向下說的話就要說到我們之前的記憶體馬文章讀取configcontext那塊了((*_)),跟到了這塊的我們應該已經能清楚地了解到Tomcat是如何從自定義的server.xml中讀取加載并最終将Valve存儲到Standard中的了,接下來我們就從責任鍊這塊都執行了什麼。

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

我們直接來到夢開始的位置-從線程一路向下

擅長捉弄的記憶體馬同學:Valve記憶體馬

我們來到了Adapter的文章,從這裡開始我們将從connect過來的請求交給最頂層的pipline來處理也就是EngineValve沒有我們自定義的Valve直接到了StandardEngineValve。

擅長捉弄的記憶體馬同學:Valve記憶體馬

到了這裡繼續交給StandardHost的pipline,通過之前我們跟進的啟動流程,我們可以清楚的知道first為我們自定義的Valve,直接跳到我們的Valve

擅長捉弄的記憶體馬同學:Valve記憶體馬

是以我們就直接到達了我們的Valve下面喽,結果就是成功證明了potatosafe is trash

擅長捉弄的記憶體馬同學:Valve記憶體馬

六、Valve記憶體馬思路

其實跟到了這裡,Valve記憶體馬其實對我們來說就屬于清晰易懂的東西,如果單純去實作記憶體馬的話,對于看到這個的師傅們應該就很簡單了,這裡我們就梳理一下思路。

我們回憶一下,我們在分析Tomcat啟動流程的時候在兩個位置添加過valve(我們分析的是Host),一次是在解析server.xml的時候添加了我們注冊的Valve,另一次是在Host啟動過程中添加了一些Valve,如下圖所示

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

他們都通過this.getPipeline().addValve(valve)進行添加操作,是以我們也按照這個方式進行添加就OK的了,換句話說我們隻要能過擷取四個容器的其中一個standard,就可以通過目前容器的pipeline直接進行添加操作,添加流程如下:

1.構造一個惡意的Valve

2.通過線程擷取standrad

3.通過Standard擷取目前容器的Pipeline

4.添加Valve記憶體馬

這裡注意添加一下this.getNext().invoke(request, response);防止影響業務,接下來我們就實作一下.

七、Valve記憶體馬實作

首先我們構造一個測試的demo

擅長捉弄的記憶體馬同學:Valve記憶體馬

接下來就是正常操作了,大家這裡建議自己去實作一下,和前面的差不太多。這裡我搭建一個shiro的測試環境(這裡我把頭長度限制去掉了)

擅長捉弄的記憶體馬同學:Valve記憶體馬

然後我們搓一個shiro反序列化打進去

擅長捉弄的記憶體馬同學:Valve記憶體馬

然後重新通路頁面,成功觸發

擅長捉弄的記憶體馬同學:Valve記憶體馬

接下來我們和之前的文章一樣打一個冰鞋進去

擅長捉弄的記憶體馬同學:Valve記憶體馬

成功連接配接冰鞋馬,也沒有影響業務請求,成功實作。

最後

對于從來沒有接觸過網絡安全的同學,我們幫你準備了詳細的學習成長路線圖。可以說是最科學最系統的學習路線,大家跟着這個大的方向學習準沒問題。

擅長捉弄的記憶體馬同學:Valve記憶體馬

同時每個成長路線對應的闆塊都有配套的視訊提供:

擅長捉弄的記憶體馬同學:Valve記憶體馬
擅長捉弄的記憶體馬同學:Valve記憶體馬

當然除了有配套的視訊,同時也為大家整理了各種文檔和書籍資料&工具,并且已經幫大家分好類了。

擅長捉弄的記憶體馬同學:Valve記憶體馬

因篇幅有限,僅展示部分資料,有需要的小夥伴,可以【掃下方二維碼】免費領取:

擅長捉弄的記憶體馬同學:Valve記憶體馬