我們到底能走多遠系列(13)
扯淡:
有機會有時間的話,我是會看那個職來職往的電視節目,個人覺得比其他一些娛樂節目對自己有協助一些,我主要關注的還是現在人們的價值觀,因為只有瞭解別人的價值觀,也就是別人想要的,才可以更好的和他們溝通交流,從而達到目的。想必大家都聽過,勵志牛人說過:想要別人給你想要的,談論他想要的。
有時候,“慎言”是很好的習慣,多聆聽,多觀察,就像《聞香識女人》中的台詞一樣:day we stop looking, is the day we die.
主題:
直接學習tomcat的valve好像有點突兀,所以還是先去瞭解下tomcat的一個核心的組件container
container從上一個組件connector手上接過解析好的內部request,根據request來進行一系列的邏輯操作,直到調用到請求的servlet,然後組裝好response,返回給clint。
整個大流程還是清晰明了的。畢竟我們大概瞭解了tomcat的輸入和輸出也能猜出它裡面的一些必要的操作。
先來看看container的分類吧:
Engine
Host
Context
Wrapper
它們各自的實作類別分別是StandardEngine, StandardHost, StandardContext, and StandardWrapper,他們都在tomcat的org.apache.catalina.core包下。
它們之間的關係,可以查看tomcat的server.xml也能明白(根據節點父子關係),這麼比喻吧:除了Wrapper最小,不能包含其他container外,Context內可以有零或多個Wrapper,Host可以擁有零或多個Host,Engine可以有零到多個Host。
在tomcat6裡這些Standard 的 container都是直接繼承抽象類別:org.apache.catalina.core.ContainerBase:
從ContainerBase這個類中的一些方法名我們可以看到各個container之間包含和被包含的操作是如何?的,這些都是他提供的方法:
public void addChild(Container child)public Container findChild(String name)public Container[] findChildren()public ObjectName[] getChildren()public Container getParent()public ObjectName getParentName()public void removeChild(Container child)
當然一個container的實現是複雜的,先跳過,未來定有機會回來學習的。
Pipeline的機制:
它的結構和實現是非常值得我們學習和借鑒的。
來看看來自網路的流程圖(很不錯的圖):
多好的圖啊,單看圖你就可以瞭解大概的tomcat啟動並執行情況了,哈哈。
首先要瞭解的是每一種container都有一個自己的StandardValve
上面四個container對應的四個是:
StandardEngineValve
StandardContextValve
StandardHostValve
StandardWrapperValve
有人把vavle比作filter,而Pipeline比作filter chain,其實已經很恰當了。這裡就說下我的理解:
開端:
在CoyoteAdapter的service方法裡,由下面這一句就進入Container的。
connector.getContainer().getPipeline().getFirst().invoke(request, response);
是的,這就是進入container迷宮的大門,歡迎來到Container。
Pipeline(注意上面結構圖的pipeline的連線):
Pipeline就像一個工廠中的生產線,負責調配工人(valve)的位置,valve則是生產線上負責不同操作的工人。
一個生產線的完成需要兩步:
1,把原料運到工人邊上
2,工人完成自己負責的部分
而tomcat的Pipeline實現是這樣的:
1,在生產線上的第一個工人拿到生產原料後,二話不說就人給下一個工人,下一個工人模仿第一個工人那樣扔給下一個工人,直到最後一個工人,而最後一個工人被安排為上面提過的StandardValve,他要完成的工作居然是把生產資料運給自己包含的container的Pipeline上去。
2,四個container就相當於有四個生產線(Pipeline),四個Pipeline都這麼幹,直到最後的StandardWrapperValve拿到資源開始調用servlet。完成後返回來,一步一步的valve按照剛才丟生產原料是的順序的倒序一次執行。如此才完成了tomcat的Pipeline的機制。
結束:
所有的vavle執行完畢後,整個響應的也就結束了。
關於Pipeline我們來看下源碼:
一個StandardValve
來自org.apache.catalina.core.StandardEngineValve的invoke方法:
public final void invoke(Request request, Response response) throws IOException, ServletException { // 拿到自己包含的host Host host = request.getHost(); if (host == null) { response.sendError (HttpServletResponse.SC_BAD_REQUEST, sm.getString("standardEngine.noHost", request.getServerName())); return; } // 最為自己pipeline上最後一位工人,負責把原料運給系一個pipeline // 這是把所有pipeline串聯起來的關鍵 host.getPipeline().getFirst().invoke(request, response); }
一個普通的valve
來自org.apache.catalina.valves.ErrorReportValveinvoke方法:
public void invoke(Request request, Response response) throws IOException, ServletException { // 已經來就把原料丟給下一個vavle執行,這是把所有vavle串聯成一個pipeline的關鍵 getNext().invoke(request, response); // 等到上面的valve全部執行好,才開始自己的真正工作: Throwable throwable = (Throwable) request.getAttribute(Globals.EXCEPTION_ATTR); if (response.isCommitted()) { return; } if (throwable != null) { // The response is an error response.setError(); // Reset the response (if possible) try { response.reset(); } catch (IllegalStateException e) { ; } response.sendError (HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } response.setSuspended(false); try { report(request, response, throwable); } catch (Throwable tt) { ; } }
以上就描述了Pipeline機制,不知道你是不是會疑問(反正我是有了):vavle是在servlet前執行還是後執行,看上面源碼的例子好像是在後執行哦? 其實是不一定的,以RemoteAddrValve為例子:
RemoteAddrValve可以根據ip地址限制訪問,這個在文章後面附上了一些來自網路的配置步驟。
RemoteAddrValve源碼:
package org.apache.catalina.valves;import java.io.IOException;import javax.servlet.ServletException;import org.apache.catalina.connector.Request;import org.apache.catalina.connector.Response;public final class RemoteAddrValve extends RequestFilterValve { // ----------------------------------------------------- Instance Variables /** * The descriptive information related to this implementation. */ private static final String info = "org.apache.catalina.valves.RemoteAddrValve/1.0"; // ------------------------------------------------------------- Properties /** * Return descriptive information about this Valve implementation. */ public String getInfo() { return (info); } // --------------------------------------------------------- Public Methods /** * 會調用invoke方法 */ public void invoke(Request request, Response response) throws IOException, ServletException { // 調用了父類的process方法,我們就去看下父類的情況 process(request.getRequest().getRemoteAddr(), request, response); }}
上面父類的process方法:
protected void process(String property, Request request, Response response) throws IOException, ServletException { // 在移入下一個valve是做了判斷 isAllowed(property)這個方法就是用來限制IP地址的 if (isAllowed(property)) { getNext().invoke(request, response); return; } // 限制了訪問,都發Error了 response.sendError(HttpServletResponse.SC_FORBIDDEN); }
只有指定的主機或IP地址才可以訪問部署在Tomcat下的應用。Tomcat提供了兩個參數供你配置:RemoteHostValve 和RemoteAddrValve,前者用於限制主機名稱,後者用於限制IP地址。
通過配置這兩個參數,可以讓你過濾來自請求的主機或IP地址,並允許或拒絕哪些主機 IP
一、全域設定,對Tomcat下所有應用生效
server.xml中添加下面一行,重啟伺服器即可:
<Valve className="org.apache.catalina.valves.RemoteAddrValve"allow="192.168.1.*" deny=""/>
注意:此行放在</Host>之前。
例:
1,只允許192.168.1.10訪問:
<Valve className="org.apache.catalina.valves.RemoteAddrValve"allow="192.168.1.10" deny=""/>
2,只允許192.168.1.*網段訪問:
<ValveclassName="org.apache.catalina.valves.RemoteAddrValve"allow="192.168.1.*" deny=""/>
3,只允許192.168.1.10、192.168.1.30訪問:
<Valve className="org.apache.catalina.valves.RemoteAddrValve"allow="192.168.1.10,192.168.1.30" deny=""/>
4,根據主機名稱進行限制:
<Valve className="org.apache.catalina.valves.RemoteHostValve"allow="abc.com" deny=""/>
二、局部設定,僅對具體的應用生效根據項目配置情況進行設定:
1,使用conf目錄下xml檔案進行配置${tomcat_root}\conf\proj_1.xml
2,直接在server.xml中進行設定${tomcat_root}\conf\server.xml
在上述檔案對應項目的</Context>前增加下面一行:
<Valve className="org.apache.catalina.valves.RemoteAddrValve"allow="192.168.1.*" deny=""/>
總結:
1,Pipeline的機制就像可配置方法的隊列,類似鏈表的實現方式。有用,靠譜。
2,valve配置也是tomcat配置中的比部分,具有使用價值。
讓我們繼續前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不會成功。
共勉