Tomcat容器結構及Pipeline機制 -我們到底能走多遠系列(13)


 我們到底能走多遠系列(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.*網段訪問:

<Valve
className="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配置中的比部分,具有使用價值。

 

讓我們繼續前行

----------------------------------------------------------------------

 

努力不一定成功,但不努力肯定不會成功。
共勉


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM