Tomcat 架構探索

AskHarries發表於2018-04-02

花了一個禮拜的時間閱讀了 how tomcat works ,本文基於此書,整理了一下 Tomcat 5 的基本架構,其實也沒什麼多複雜的東西,無非是解析 Http 請求,然後呼叫相應的 Servlet 。另推薦看 CSAPP 的網路程式設計那一章

本文不以別人能看懂為目的,僅作自己備忘筆記只用。

基本架構

Tomcat 由兩個模組協同合作

  • connector
  • container

connector 負責解析處理 HTTP 請求,比如說 請求頭 , 查詢字串 , 請求引數 之類的。生成 HttpRequestHttpResponse
之後交給 container ,由它負責呼叫相應的 Servlet

Connector

Tomcat 預設的 ConnectorHttpConnector 。作為 Connector 必須要實現 Connector 這個介面。

Tomcat 啟動以後會開啟一個執行緒,做一個死迴圈,通過 ServerSocket 來等待請求。一旦得到請求,生成 Socket ,注意這裡 HttpConnector 並不會自己處理 Socket ,而是把它交給 HttpProcessor 。詳細看下面程式碼,這裡我只保留了關鍵程式碼。

public void run() {
        // Loop until we receive a shutdown command
        while (!stopped) {
            Socket socket = null;
            try {
                socket = serverSocket.accept(); //等待連結
            } catch (AccessControlException ace) {
                log("socket accept security exception", ace);
                continue;
            }
            // Hand this socket off to an appropriate processor
            HttpProcessor processor = createProcessor();
            processor.assign(socket);  //這裡是立刻返回的
            // The processor will recycle itself when it finishes
        }
    }複製程式碼

注意一點,上面的 processor.assign(socket); 是立刻返回的,並不會阻塞在那裡等待。因為Tomcat不可能一次只能處理一個請求,所以是非同步的,每個 processor 處理都是一個單獨的執行緒。

HttpProcessor

上面的程式碼並沒有顯示呼叫 HttpProcessorprocess 方法,那這個方法是怎麼呼叫的呢?我們來看一下 HttpProcessorrun 方法。

public void run() {
        // Process requests until we receive a shutdown signal
        while (!stopped) {
            // Wait for the next socket to be assigned
            Socket socket = await(); 
            if (socket == null)
                continue;
            // Process the request from this socket
            try {
                process(socket);
            } catch (Throwable t) {
                log("process.invoke", t);
            }
            // Finish up this request
            connector.recycle(this);
        }
    }複製程式碼

我們發現他是呼叫 await 方法來阻塞等待獲得 socket 方法。而之前 Connector 是呼叫 assign 分配的,這是什麼原因?

下面仔細看 awaitassign 方法。這兩個方法協同合作,當 assign 獲取 socket 時會通知 await 然後返回 socket

synchronized void assign(Socket socket) {
    // Wait for the Processor to get the previous Socket
    while (available) {
        try {
            wait();
        } catch (InterruptedException e) {
        }
    }
    // Store the newly available Socket and notify our thread
    this.socket = socket;
    available = true;
    notifyAll();
}
private synchronized Socket await() {
    // Wait for the Connector to provide a new Socket
    while (!available) {
        try {
            wait();
        } catch (InterruptedException e) {
        }
    }
    // Notify the Connector that we have received this Socket
    Socket socket = this.socket;
    available = false;
    notifyAll();
    return (socket);
}複製程式碼

預設 availablefalse

接下來就是剩下的事情就是解析請求,填充 HttpRequestHttpResponse 物件,然後交給 container 負責。

這裡我不過多贅述如何解析

private void process(Socket socket) {
    //parse
    ....
    connector.getContainer().invoke(request, response);
    ....
}複製程式碼

Container

A Container is an object that can execute requests received from a client, and return responses based on those requests

Container 是一個介面,實現了這個介面的類的例項,可以處理接收的請求,呼叫對應的 Servlet

總共有四類 Container ,這四個 Container 之間並不是平行關係,而是父子關係

  • Engine – 最頂層的容器,可以包含多個 Host
  • Host – 代表一個虛擬主機,可以包含多個 Context
  • Context – 代表一個 web應用 ,也就是 ServletContext ,可以包含多個 Wrappers
  • Wrapper – 代表一個 Servlet ,不能包含別的容器了,這是最底層

Container的呼叫

容器好比是一個加工廠,加工接受的 request ,加工方式和流水線也很像,但又有點區別。這裡會用到一個叫做 Pipeline的 東西,中文翻譯管道request 就放在管道里順序加工,進行加工的工具叫做 Valve ,好比手術刀, Pipeline 可新增多個 Valve ,最後加工的工具稱為 BaseValve

上面可能講的比較抽象,接下來我們來看程式碼。 Engine 是頂層容器,所以上面 invoke ,執行的就是 Engine 的方法。StandardEngineEngine 的預設實現,注意它也同時實現了 Pipeline 介面,且包含了 Pipeline

它的構造方法同時指定了 baseValve ,也就是管道最後一個呼叫的 Valve

public StandardEngine() {
    super();
    pipeline.setBasic(new StandardEngineValve());
}複製程式碼

好,接著我們看 invoke ,這個方法是繼承自 ContainerBase 。只有一行,之間交給 pipeline ,進行加工。

public void invoke(Request request, Response response)
        throws IOException, ServletException {
        pipeline.invoke(request, response);
}複製程式碼

下面是 StandardPipelineinvoke 實現,也就是預設的 pipeline 實現。

public void invoke(Request request, Response response)
        throws IOException, ServletException {
        // Invoke the first Valve in this pipeline for this request
        (new StandardPipelineValveContext()).invokeNext(request, response);
}複製程式碼

也只有一行!呼叫 StandardPipelineValveContextinvokeNext 方法,這是一個 pipeline 的內部類。讓我們來看

具體程式碼

public void invokeNext(Request request, Response response)
            throws IOException, ServletException {
            int subscript = stage;
            stage = stage + 1;
            // Invoke the requested Valve for the current request thread
            if (subscript < valves.length) {
                valves[subscript].invoke(request, response, this);  //加工
            } else if ((subscript == valves.length) && (basic != null)) {
                basic.invoke(request, response, this);
            } else {
                throw new ServletException
                    (sm.getString("standardPipeline.noValve"));
            }
}複製程式碼

它呼叫了 pipeline 所用的 Valve 來對 request 做加工,當Valve執行完,會呼叫 BaseValve ,也就是上面的StandardEngineValve

我們再來看看它的 invoke 方法

// Select the Host to be used for this Request
StandardEngine engine = (StandardEngine) getContainer();
Host host = (Host) engine.map(request, true);
if (host == null) {
    ((HttpServletResponse) response.getResponse()).sendError
        (HttpServletResponse.SC_BAD_REQUEST,
            sm.getString("standardEngine.noHost",
                        request.getRequest().getServerName()));
    return;
}
// Ask this Host to process this request
host.invoke(request, response);複製程式碼

它通過 (Host) engine.map(request, true); 獲取所對應的 Host ,然後進入到下一層容器中繼續執行。後面的執行順序

Engine 相同,我不過多贅述

執行順序小結

經過一長串的 invoke 終於講完了第一層容器的執行順序。估計你們看的有點暈,我這裡小結一下。

Connector -> HttpProcessor.process() -> StandardEngine.invoke() -> StandardPipeline.invoke() ->

StandardPipelineValveContext.invokeNext() -> valves.invoke() -> StandardEngineValve.invoke() ->

StandardHost.invoke()

到這裡位置 Engine 這一層結束。接下來進行 Host ,步驟完全一致

StandardHost.invoke() -> StandardPipeline.invoke() ->

StandardPipelineValveContext.invokeNext() -> valves.invoke() -> StandardHostValve.invoke() ->

StandardContext.invoke()

然後再進行 Context 這一層的處理,到最後選擇對應的 Wrapping 執行。

Wrapper

Wrapper 相當於一個 Servlet 例項, StandardContext 會更根據的 request 來選擇對應的 Wrapper 呼叫。我們直接來看看

Wrapperbasevalve 是如果呼叫 Servletservice 方法的。下面是 StandardWrapperValveinvoke 方法,我省略了很多,

只看關鍵。

public void invoke(Request request, Response response,
                       ValveContext valveContext)
        throws IOException, ServletException {
        // Allocate a servlet instance to process this request
        if (!unavailable) {
            servlet = wrapper.allocate();
        }
        // Create the filter chain for this request
        ApplicationFilterChain filterChain =
            createFilterChain(request, servlet);
        // Call the filter chain for this request
        // NOTE: This also calls the servlet's service() method
        String jspFile = wrapper.getJspFile();  //是否是jsp
        if (jspFile != null)
            sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
        else
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
        if ((servlet != null) && (filterChain != null)) {
            filterChain.doFilter(sreq, sres);
        }
        sreq.removeAttribute(Globals.JSP_FILE_ATTR);
    }複製程式碼

首先呼叫 wrapper.allocate() ,這個方法很關鍵,它會通過 反射 找到對應 servletclass 檔案,構造出例項返回給我們。然後建立一個 FilterChain ,熟悉 j2ee 的各位應該對這個不陌生把?這就是我們在開發 web app 時使用的 filter 。然後就執行 doFilter 方法了,它又會呼叫 internalDoFilter ,我們來看這個方法

private void internalDoFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
        // Call the next filter if there is one
        if (this.iterator.hasNext()) {
            ApplicationFilterConfig filterConfig =
              (ApplicationFilterConfig) iterator.next();
            Filter filter = null;
            
            filter = filterConfig.getFilter();
            filter.doFilter(request, response, this);
            return;
        }
        // We fell off the end of the chain -- call the servlet instance
        if ((request instanceof HttpServletRequest) &&
            (response instanceof HttpServletResponse)) {
            servlet.service((HttpServletRequest) request,
                            (HttpServletResponse) response);
        } else {
            servlet.service(request, response);
        }
}複製程式碼

終於,在這個方法裡看到了 service 方法,現在你知道在使用 filter 的時候如果不執行 doFilterservice 就不會執行的原因了把。

小結

Tomcat 的重要過程應該都在這裡了,還值得一提的是 LifeCycle 介面,這裡所有類幾乎都實現了 LifeCycleTomcat 通過它來統一管理容器的生命流程,大量運用觀察者模式。有興趣的同學可以自己看書

Referance

How Tomcat works

原文地址:www.liuhaihua.cn/archives/37…


相關文章