1. 程式人生 > >請求在Tomcat中的執行流程

請求在Tomcat中的執行流程

本來想寫完Servlet在將筆記中的Tomcat整理出來,但是發現不說清除Tomcat,Servlet根本沒法說清楚,所以,這一篇主要梳理請求在Tomcat中到底發生了什麼。

就像我們知道的Tomcat被作為WEB容器使用,當tomcat接收到請求後會發生下列事情:
之前有一篇文章說了大概的Tomcat的執行,但是,那篇文章將Tomcat和Servlet作為一個Web容器來講了,並不是很詳細
Tomcat簡述

1.Tomcat流程

下面我試圖將Tomcat更加詳細的說明出來:
這裡寫圖片描述
Tomcat中的六個容器:

容器 作用
Server容器 一個StandardServer類例項就表示一個Server容器,server是tomcat的頂級構成容器
Service容器 一個StandardService類例項就表示一個Service容器,Tomcat的次頂級容器,Service是這樣一個集合:它由一個或者多個Connector組成,以及一個Engine,負責處理所有Connector所獲得的客戶請求。
Engine容器 一個StandardEngine類例項就表示一個Engine容器。Engine下可以配置多個虛擬主機Virtual Host,每個虛擬主機都有一個域名。當Engine獲得一個請求時,它把該請求匹配到某個Host上,然後把該請求交給該Host來處理,Engine有一個預設虛擬主機,當請求無法匹配到任何一個Host上的時候,將交給該預設Host來處理
Host容器 一個StandardHost類例項就表示一個Host容器,代表一個VirtualHost,虛擬主機,每個虛擬主機和某個網路域名Domain Name相匹配。每個虛擬主機下都可以部署(deploy)一個或者多個WebApp,每個Web App對應於一個Context,有一個Context path。當Host獲得一個請求時,將把該請求匹配到某個Context上,然後把該請求交給該Context來處理。匹配的方法是“最長匹配”,所以一個path==”“的Context將成為該Host的預設Context。所有無法和其它Context的路徑名匹配的請求都將最終和該預設Context匹配
Context容器 一個StandardContext類例項就表示一個Context容器。一個Context對應於一個Web Application,一個WebApplication由一個或者多個Servlet組成。Context在建立的時候將根據配置檔案CATALINA_HOME/conf/web.xml和WEBAPP_HOME/WEB-INF/web.xml載入Servlet類。當Context獲得請求時,將在自己的對映表(mappingtable)中尋找相匹配的Servlet類。如果找到,則執行該類,獲得請求的迴應,並返回
Wrapper容器 一個StandardWrapper類例項就表示一個Wrapper容器,Wrapper容器負責管理一個Servlet,包括Servlet的裝載、初始化、資源回收。Wrapper是最底層的容器,其不能在新增子容器了。Wrapper是一個介面,其標準實現類是StandardWrapper

-Connector

 一個Connector將在某個指定埠上偵聽客戶請求,並將獲得的請求交給Engine來處理,從Engine處獲得迴應並返回客戶。
Tomcat有兩個典型的Connector:
 - 一個直接偵聽來自browser的http請求
 - 一個偵聽來自其它WebServer的請求
Coyote Http/1.1 Connector 在埠8080處偵聽來自客戶browser的http請求。
Coyote JK2 Connector 在埠8009處偵聽來自其它WebServer(Apache)的servlet/jsp代理請求。

下面是流程圖:

Created with Raphaël 2.1.2客戶端傳送請求:http://localhost:8080/wsota/wsota_index.jsp1) 請求被髮送到本機埠8080,被在那裡偵聽的Coyote HTTP/1.1 Connector獲得2) Connector把該請求交給它所在的Service的Engine來處理,並等待來自Engine的迴應3) Engine獲得請求localhost/wsota/wsota_index.jsp,匹配它所擁有的所有虛擬主機Host4) Engine匹配到名為localhost的Host(即使匹配不到也把請求交給該Host處理,因為該Host被定義為該Engine的預設主機)5) localhost Host獲得請求/wsota/wsota_index.jsp,匹配它所擁有的所有Context6) Host匹配到路徑為/wsota的Context(如果匹配不到就把該請求交給路徑名為""的Context去處理)7) path="/wsota"的Context獲得請求/wsota_index.jsp,在它的mapping table中尋找對應的servlet8) Context匹配到URL PATTERN為*.jsp的servlet,對應於JspServlet類9) 構造HttpServletRequest物件和HttpServletResponse物件,作為引數呼叫JspServlet的doGet或doPost方法10)Context把執行完了之後的HttpServletResponse物件返回給Host11)Host把HttpServletResponse物件返回給Engine12)Engine把HttpServletResponse物件返回給Connector13)Connector把HttpServletResponse物件返回給客戶browser

這是一個巨集觀的路線

2.Tomcat的啟動與類的具體實現

一個WEB應用對應一個context容器,也就是servlet執行時的servlet容器。

新增一個web應用時將會建立一個StandardContext容器,並且給這個context容器設定必要的引數,url和path分別代表這個應用在tomcat中的訪問路徑和這個應用實際的物理路徑,這兩個引數與tomcat配置中的兩個引數是一致的。
其中一個最重要的一個配置是ContextConfig,這個類會負責整個web應用配置的解析工作。

最後將這個context容器加入到父容器host中。

接下來會呼叫tomcat的start方法啟動tomcat。

Tomcat的啟動邏輯是基於觀察者模式的,所有的容器都會繼承Lifecycle介面,它管理著容器的整個生命週期,所有容器的修改和狀態改變都會由它通知已經註冊的觀察者。

Tomcat啟動的時序如下:
這裡寫圖片描述

仔細看看基本上就清楚了,這裡只記錄一下細節:

ContextConfig的init方法

當context容器初始狀態設定Init時,新增到context容器的listener將會被呼叫。ContextConfig繼承了LifecycleListener介面,它是在呼叫Tomcat.addWebapp時被加入到StandardContext容器中的。ContextConfig類會負責整個WEB應用的配置檔案的解析工作。

  1. ContextConfig的init方法將會主要完成一下工作:
  2. 建立用於解析XML配置檔案的contextDigester物件
  3. 讀取預設的context.xml檔案,如果存在則解析它
  4. 讀取預設的Host配置檔案,如果存在則解析它
  5. 讀取預設的Context自身的配置檔案,如果存在則解析它
  6. 設定Context的DocBase

startInternal方法

ContextConfig的init方法完成後,Context容器會執行startInternal方法,這個方法包括如下幾個部分:

  1. 建立讀取資原始檔的物件
  2. 建立ClassLoader物件
  3. 設定應用的工作目錄
  4. 啟動相關的輔助類,如logger,realm,resources等
  5. 修改啟動狀態,通知感興趣的觀察者
  6. 子容器的初始化
  7. 獲取ServletContext並設定必要的引數
  8. 初始化“load on startuo”的Servlet

Web應用的初始化

web應用的初始化在14步,下面是該初始化的詳細內容

WEB應用的初始化工作是在ContextConfig的configureStart方法中實現的,應用的初始化工作主要是解析web.xml檔案,這個檔案是一個WEB應用的入口。

  1. Tomcat首先會找globalWebXml,這個檔案的搜尋路徑是engine的工作目錄下的org/apache/catalina/startup/NO-DEFAULT_XML或conf/web.xml。

  2. 接著會找hostWebXml,這個檔案可能會在System.getProperty(“catalina.base”)/conf/$ {EngineName}/${HostName}/web.xml.default中。

  3. 接著尋找應用的配置檔案examples/WEB-INF/web.xml,web.xml檔案中的各個配置項將會被解析成相應的屬性儲存在WebXml物件中。

  4. 接下來會講WebXml物件中的屬性設定到context容器中,這裡包括建立servlet物件,filter,listerner等,這些在WebXml的configureContext方法中。

以及下面是解析servlet的程式碼物件:

for (ServletDef servlet : servlets.values()) {  
    Wrapper wrapper = context.createWrapper();  
    String jspFile = servlet.getJspFile();  
    if (jspFile != null) {  
        wrapper.setJspFile(jspFile);  
    }  
    if (servlet.getLoadOnStartup() != null) {  
        wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());  
    }  
    if (servlet.getEnabled() != null) {  
        wrapper.setEnabled(servlet.getEnabled().booleanValue());  
    }  
    wrapper.setName(servlet.getServletName());  
    Map<String,String> params = servlet.getParameterMap();  
    for (Entry<String, String> entry : params.entrySet()) {  
        wrapper.addInitParameter(entry.getKey(), entry.getValue());  
    }  
    wrapper.setRunAs(servlet.getRunAs());  
    Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();  
    for (SecurityRoleRef roleRef : roleRefs) {  
        wrapper.addSecurityReference(roleRef.getName(), roleRef.getLink());  
    }  
    wrapper.setServletClass(servlet.getServletClass());  
    MultipartDef multipartdef = servlet.getMultipartDef();  
    if (multipartdef != null) {  
        if (multipartdef.getMaxFileSize() != null &&  
            multipartdef.getMaxRequestSize()!= null &&  
            multipartdef.getFileSizeThreshold() != null) {  
                wrapper.setMultipartConfigElement(new MultipartConfigElement(  
                                                      multipartdef.getLocation(),  
                                                      Long.parseLong(multipartdef.getMaxFileSize()),  
                                                  Long.parseLong(multipartdef.getMaxRequestSize()),  
                                                  Integer.parseInt(  
                                                  multipartdef.getFileSizeThreshold())));  
        } else {  
        wrapper.setMultipartConfigElement(new MultipartConfigElement(  
                                              multipartdef.getLocation()));  
        }  
    }  
    if (servlet.getAsyncSupported() != null) {  
        wrapper.setAsyncSupported(  
            servlet.getAsyncSupported().booleanValue());  
    }  
    context.addChild(wrapper);  
}

3.建立Servlet例項

前面完成了servlet的解析工作,並且被包裝成了StandardWrapper新增到Context容器中,但是它仍然不能為我們工作,它還沒有被例項化。

3.1.建立

如果Servlet的load-on-startup配置項大於0,那麼在Context容器啟動時就會被例項化。
(這個是在web.xml中進行了配置)
前面提到的在解析配置檔案時會讀取預設的globalWebXml,在conf下的web.xml檔案中定義了一些預設的配置項,其中定義了兩個Servlet,分別是org.apache.catalina.servlets.DefaultServlet和org.apache.jsper.servlet.JspServelt,它們的load-on-startup分別是1和3,也就是當tomcat啟動時這兩個servlet就會被啟動。

建立Servlet例項的方式是從Wrapper.loadServlet開始的,loadServlet方法要完成的就是獲取servletClass,然後把它交給InstanceManager去建立一個基於servletClass.class的物件。如果這個Servlet配置了jsp-file,那麼這個servletClass就是在conf/web.xml中定義的org.apache.jasper.servlet.JspServlet。

3.2.初始化

初始化Servlet在StandardWrapper的initServlet方法中,這個方法很簡單,就是呼叫Servlet的init()方法,同時把包裝了StandardWrapper物件的StandardWrapperFacade作為ServletConfig傳給Servlet。

如果該Servlet關聯的是一個JSP檔案,那麼前面初始化的就是JspServlet,接下來會模擬一次簡單請求,請求呼叫這個JSP檔案,以便編譯這個JSP檔案為類,並初始化這個類。

這樣Servlet物件的初始化就完成了。

3.3.容器預設Servlet

每個servlet容器都有一個預設的servlet,一般都叫做default。

例如:tomcat中的 DefaultServlet 和 JspServlet (上面的部分)

4.建立HttpServlet

這一篇細細說說請求是怎麼交到Servlet手上的。此時,包括Servlet都已經初始化化完畢,一切已經準備就緒。
HttpServletRequest的產生大概在第四步

步驟 過程 詳情
1 AbstractEndpoint類及其子類來處理。 AbstractEndpoint這個抽象類中有一個抽象內部類Acceptor,這個Acceptor的實現類是AbstractEndpoint的三個子類的內部類Acceptor來實現的。
1 我們的請求就是被這Acceptor監聽到並且接收的。這個類其實是一個執行緒類,因為AbstractEndpoint.Acceptor實現了Runnable介面。
1 AprEndpoint接收請求的過程。就是用一個接收器接收請求,過程中會使用套接字。但是好像並不是有的請求都會用這個Acceptor來接收。
1 當接收請求完畢,經過一系列的處理後就會由AprEndpoint的內部類SocketProcessor來將請求傳給ProtocolHandler來處理。這個SocketProcessor也是一個執行緒類。它有一行程式碼將套接字傳給了第二步來處理。
2 在/AbstractConnectionHandler接收到第一步傳來的套接字以後,對套接字進行處理 wapper就是套接字包裝類的物件,這裡還是理解為套接字,套接字在這裡傳給了第③步的Processor介面的例項。state = processor.process(wrapper);
3 第二步完成後,就會交給Processor介面的實現類來處理。 在這裡將會建立請求和響應,但不是我們熟悉的HttpServletRequest或HttpServletResponse型別或其子型別。而是org.apache.coyote.Request 和 org.apache.coyote.Response型別的
3 它將會在Connector中來進行處理,我說的是處理而不是型別轉換是因為org.apache.coyote.Request 和HttpServletRequest並不是父子關係的類,總之,HttpServletRequest的請求是由Connector來建立,在CoyoteAdapter中處理成HttpServletRequest.
3 這個方法就是請求物件、響應物件和套接字進行資訊互動的地方,也就是真真正正將套接字中的資訊轉化為請求資訊,還要把響應資訊寫到套接字中。
4 第三步完成之後交給CoyoteAdapter來處理 CoyotoAdapter是將請求傳入Server容器的切入點。Adapter. This represents the entry point in a coyote-based servlet container.
4 (具體怎麼處理成HttpServletRequest看程式碼) CoyoteAdapter中有一個service()方法。這個方法持有一個Connector的引用。這個Connector又持有一個Service容器的引用,而Service容器有持有一個Container(Container的實現類有StandardEngine、StandardHost等等)的引用。所以CoyoteAdapter就可以根據這些引用將請求傳遞到Server容器中了。
5 如果上面的請求傳遞到的Container是StandaradEngine,那麼就會Engine就會呼叫它持有的StandardPipeline物件來處理請求。 StandardPipeline就相當於一條管道,這條管道中的有許多閥門,這些閥門會對請求進行處理,並且控制它下一步往哪裡傳遞。StandardEngine的管道使用的閥門是StandardEngineValve。
6 容器繼續逐層傳遞 和StandardEngine一樣,StandardHost、StandardContext、StandardWrapper這幾個容器都擁有自己的一條管道StandardPipeline來處理的請求。但是需要注意的是他們使用的閥門是不一樣的。StandardHost則會使用StandardHostValve,其他的同理。
7 當最後一個StandardWrapperVale處理完請求後,此時請求已經到達了最底層的容器了。 StandardWrapper就是最底層的容器,它不允許再有子容器。其實每一個StandardWrapper代表一個Servlet,因為每一個StandardWrapper都會持有一個Servlet例項的引用。
//CoyoteAdapter的service(...)方法
/**
     * Service method.
     */
    @Override
    public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res)
        throws Exception {
     //下面這兩行程式碼就是將請求處理後轉化為HttpServletRequest的
        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);
      //如果請求為null就讓Connector來建立。
        if (request == null) {

            // Create objects
            request = connector.createRequest();
            request.setCoyoteRequest(req);
            response = connector.createResponse();
            response.setCoyoteResponse(res);
      //省略部分程式碼
        }

        if (connector.getXpoweredBy()) {
            response.addHeader("X-Powered-By", POWERED_BY);
        }

        boolean comet = false;
        boolean async = false;
        boolean postParseSuccess = false;

        try {
            // Parse and set Catalina and configuration specific
            // request parameters
            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
            postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                //check valves if we support async
                request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
                // Calling the container 在這裡將請求傳到Server容器中。
                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

              //省略部分程式碼

            }

    }    /**

5.Tomcat核心類

Tomcat的體系結構:

這裡寫圖片描述
一個Connector+一個Container構成一個Service,Service就是對外提供服務的元件,有了Service元件Tomcat就可以對外提供服務了。
那麼這些個元件到底是幹嘛用的呢?
Connector是一個聯結器,主要負責接收請求並把請求交給Container
Container就是一個容器,主要裝的是具體處理請求的元件。

Service主要是為了關聯Container與Connector,一個單獨的Container或者一個單獨的Connector都不能完整處理一個請求,只有兩個結合在一起才能完成一個請求的處理。

5.1 Tomcat的兩大元件

1. Connecter元件

一個Connecter將在某個指定的埠上偵聽客戶請求,接收瀏覽器的發過來的 tcp 連線請求,建立一個 Request 和 Response 物件分別用於和請求端交換資料,然後會產生一個執行緒來處理這個請求並把產生的 Request 和 Response 物件傳給處理Engine(Container中的一部分),從Engine出獲得響應並返回客戶。
Tomcat中有兩個經典的Connector,一個直接偵聽來自Browser的HTTP請求,另外一個來自其他的WebServer請求。Cotote HTTP/1.1 Connector在埠8080處偵聽來自客戶Browser的HTTP請求,Coyote JK2 Connector在埠8009處偵聽其他Web Server的Servlet/JSP請求。
Connector 最重要的功能就是接收連線請求然後分配執行緒讓 Container 來處理這個請求,所以這必然是多執行緒的,多執行緒的處理是 Connector 設計的核心。

2. Container元件

主容器,包含Tomcat的各個容器。
Container是容器的父介面,由四個容器組成,分別是Engine,Host,Context,Wrapper。其中Engine包含Host,Host包含Context,Content包含Wrapper,一個Servlet class對應一個Wrapper

6.總結

這篇文章是整理多篇文章所得,並不能保證完全正確,如有錯誤,感謝指正。

附件:
server.xml的配置:server.xml

相關推薦

Springboot打包放入tomcat執行

pom.xml調整 1.1 打包方式修改 <packaging>jar</packaging> 變更為 <packaging>war</packaging> 1.2. 新增依賴 重點:scope是provided <d

修改Springboot專案能在tomcat執行 (jar to war)

0.準備工作 Springboot專案 (pom.xml <packaging>jar</packaging>) 1.新增Jar包 <dependency>

Spring Boot-8 專案部署到獨立的tomcat執行

  Spring Boot預設提供內嵌的tomcat,所以可以直接生成jar包,用java -jar命令就可以啟動。   也可以打成war包,放到tomcat中執行。步驟如下: 步驟一:   將p

Linux環境下更換Tomcat執行的專案

首先需要進入Tomcat的bin目錄下去使用./shutdown.sh命令去關閉當前tomcat,但是由於關不完全,所以還需要用到如下的命令 檢視當前執行專案,然後再使用如下命令關掉tomcat -9後面的引數是每次都會產生變化的,所以這個是需要自己手動去觀察輸入

Springboot打成war包並在tomcat執行的部署方法

把spring-boot專案按照平常的web專案一樣釋出到tomcat容器下 一、修改打包形式 在pom.xml裡設定 <packaging>war</packaging> 二、移除嵌入式tomcat外掛 在pom.xml裡找到spring-b

Dockerfile 之 tomcat執行MyEclipse搭建Web專案(Docker系列)

本文章來自【知識林】 在之前的講解中主要講述的是如何使用已經存在的Docker映象,當然這些映象對我們的使用肯定有很大的幫助,但很多時候我們是需要執行我們自己所定製開發的應用程式,這些應用程式在Doc

SpringBoot學習-(十六)SpringBoot釋出到獨立的tomcat執行

儘管Spring Boot專案會內建一個tomcat,僅只需通過一個簡單的指令便可啟動專案,但在生產環境下,我們還是習慣將專案釋出到第三外的servlet容器中,下面將介紹如果將一個Spring Boot專案部署到第三方tomcat中執行。 1)修改工程的

linux下發布web專案到tomcat部署執行流程

1、下載tomcat,並上傳到Linux對應目錄下,解壓tomcat壓縮包。 解壓命令:tar xzvf 壓塑包名稱 2、將你的web專案打成war包上傳到tomcat下的webapps下 3、通過命令進入tomcat下的bin目錄下,執行

Springboot打成war包並在tomcat執行

把spring-boot專案按照平常的web專案一樣釋出到tomcat容器下 一、修改打包形式 在pom.xml裡設定 <packaging>war</packaging

SpringBoot專案釋出到Tomcat執行

SpringBoot雖然自帶了Tomcat , 但是因為實際開發中可能會需要把幾個專案放到一個Tomcat中. 所以做了一個測試,把Boot專案釋出到Tomcat執行. 1.修改pom.xml檔案.  打包方式改為war包  並取消自帶的tomcat 2 修改

SpringBoot釋出到獨立的tomcat執行

在開發階段我們推薦使用內嵌的tomcat進行開發,因為這樣會方便很多,但是到生成環境,我希望在獨立的tomcat容器中執行,因為我們需要對tomcat做額外的優化,這時我們需要將工程打包成war包進行釋出。工程的打包方式為war包1、將spring-boot-starter-

idea匯入maven專案並配置在tomcat執行 (轉)

1:匯入一個maven專案 一定要選擇maven. 然後一路next,最後完成 2,配置tomcat執行 。部署到tomcat我們可以有兩種方式,一種是利用tomcat外掛來進行部署,另一種是下載tomcat伺服器來來進行配置 這裡只介紹第二種 a.下

Eclipse匯入Maven Web專案並配置其在Tomcat執行

首先我通過svn將公司的專案checkout到了本地。 因為Maven遵循的是規約比配置重要的原則,所以Maven專案的結構一般是進入目錄後是一個pom.xml檔案和一個src資料夾,當然可能還存在一些README之類的這些都不重要,最關鍵的就是pom.xml和src資料夾

請求Tomcat執行流程

本來想寫完Servlet在將筆記中的Tomcat整理出來,但是發現不說清除Tomcat,Servlet根本沒法說清楚,所以,這一篇主要梳理請求在Tomcat中到底發生了什麼。 就像我們知道的Tomcat被作為WEB容器使用,當tomcat接收到請求後會發生下列

Nginx配置,請求tomcat

頁面 需要 服務器 並發 html 處理 htm shift cati 一.web服務器分為兩類   1.web服務器     1)Apache服務器     2)Nginx     3)IIS   2.web 應用服務器     1)tomcat     2)resin

Spring MVC 執行請求-->響應)流程

resolve pro 日期 格式化數字 bind sha 有效 數字 入參 *每日一句:*每天起床之前有兩個選擇,要麽繼續趴下做你沒做完的夢,要麽起床完成你沒有完成的夢想。-----** ----- 用戶想服務器發型請求,請求被Spring的前端控制器Dispatch

eclipse執行tomcat出現錯誤:-Djava.endorsed.dirs=/users/huchao/library/tomcat-9/endorsed is not supported

-Djava.endorsed.dirs=/users/huchao/library/tomcat-9/endorsed is not supported. Endorsed standards and standalone APIs in modular form will be supported vi

Ecplisetomcat執行HTML檔案自動載入

首先說什麼熱部署 今天在做一個SpringBoot的專案時候,發現一個很煩的問題,我修改了HTML然後ecplise上面在server上面執行,但是讓人傷心的是修改的HTML檔案都都需要重啟tomcat 真的太讓人難受了,不可能這樣一直重新啟動啊 修改了半天沒有看出來怎麼解決這樣的

C# 程式執行流程控制

1、C#之流程控制語句:計算機程式執行的控制流程由三種基本的控制結構控制,即順序結構,選擇結構,迴圈結構。 1) 順序結構:從上到下,按照書寫順序執行每一條語句,不會發生跳躍。 程式碼段1; // 先執行程式碼段1 程式碼段2; // 接著執行程式碼段2 ... 2)選擇結構:對

java web專案在tomcat以除錯模式執行

轉載地址:https://blog.csdn.net/gk_12/article/details/79689702 自己測試環境是: JDK1.8 Eclipse4.5 tomcat 8.5 如果只是將專案部署在tomcat中並不能進行除錯,所以還需要進行配置一下,步驟如下: