圖解 Spring:HTTP 請求的處理流程與機制【2】

2. HTTP 請求在 Web 容器中的處理流程

Web 容器以進程的方式在計算機上運行,咱們知道進程是系統資源分配的最小單元,線程是系統任務執行的最小單元。從這個角度看,Web 容器就像是郵包收件人所居住的樓宇或小區,HTTP 這套物流快遞體系只能將郵包投遞到樓宇前臺或者小區物業等處,而樓宇前臺或小區物業並不屬於物流快遞體系,就像 Web 容器並不屬於計算機網絡基礎設施同樣。之因此這樣分工,緣由是網絡路由信息由域名服務器 DNS、路由器等設備掌握,Web 容器內部體系結構信息只有它本身知道。從 Web 容器接收到 HTTP 請求,到將其投送至特定的應用,這期間還會經歷一個複雜的過程,瞭解這個過程對於平常開發和問題分析都會有所幫助。接下來,老兵哥我將陪着你一塊兒來剖析這個過程。web

JAVA 語言領域的 Web 容器類型很是多,包括 Tomcat、Jetty、Resin、Websphere、Weblogic、JBoss、Glassfish、GonAS 等,其中 Tomcat 是由 Apache Software Foundation 維護的開源 Web 容器。Tomcat 市場佔用率接近 60%,截止目前是最受歡迎的 Web 容器,以下圖所示橫跨 Web 服務器和 Java 應用服務器。
Tomcat 定位
咱們就以 Tomcat 爲例來看看 Web 容器的內部結構,做爲符合 JAVA Servlet 標準規範的容器,Tomcat 是一款基於組件的 Java Web 應用服務器,核心組件都是靈活可配的。Tomcat 最外層是 Catalina Servlet 容器,其餘組件是按照特定格式要求配置在這個頂層容器當中,配置文件就是安裝目錄下的:/conf/server.xml。經過這份配置文件,咱們就能夠了解 Tomcat 的體系結構,示例文件以下所示(快速瀏覽一遍,後面詳細剖析):spring

<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JasperListener" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1" maxThreads=」150″ 
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    <Connector port=」8443″ maxThreads=」150″ minSpareThreads=」25″ 
               maxSpareThreads=」75″ enableLookups=」false」 acceptCount=」100″ 
               debug=」0″ scheme=」https」 secure=」true」 
               clientAuth=」false」 sslProtocol=」TLS」 />
    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t "%r" %s %b" />
      </Host>
    </Engine>
  </Service>
</Server>

2.1 Tomcat 核心組件簡介

若是常用配置 Tomcat,那麼你對上述配置文件必定很是熟悉。爲了方便你們理解,結合本文的主題,咱們把上述這份配置文件中的關鍵節點提取出來,而後再逐一分析介紹:apache

<Server>                              
    <Service>
        <Connector />
        <Connector />
        <Engine>
            <Host>
                <Context />
            </Host>
        </Engine>        
    </Service>
</Server>

上述結構中包含了 Tomcat 的核心組件:Server 組件在最頂層,表明整個 Tomcat 容器。一個 Server 組件中能夠包含一個或多個 Service 組件。Service 在 Connector 和 Engine 外面包了一層,把它們組裝在一塊兒對外提供服務。一個 Service 能夠包含多個 Connector,可是隻能包含一個 Engine。不一樣 Connector 負責接收不一樣端口上相應協議的請求,而 Engine 負責處理請求。Engine 包含一個或多個 Host,Host 包含一個或多個 Context,Engine、Host、Context 都屬於容器組件,一個 Host 組件表明一個虛擬主機,一個 Context 組件表明在隸屬 Host 上運行的一個 Web 應用。
Tomcat體系結構瀏覽器

2.1.1 頂層類組件 Server

它是整個配置文件的惟一根元素,表明整個 Tomcat 容器,內部能夠包含多個 Service。Server 主要職責就是管理多個 Service,對外提供給客戶端訪問,同時維護全部 Service 的生命週期,包括初始化服務、結束服務、定位客戶端要訪問的 Service 等等。全部 Tomcat 組件的生命週期都是經過 Lifecycle 接口來控制的,組件只要繼承這個接口並實現其中的方法就能夠統一被父組件控制了,這樣層層遞進 Server 組件就能夠控制全部組件的生命週期了,而控制 Server 就是經過啓動和關停 Tomcat。在前面配置示例中,Server 的配置以下所示:tomcat

<Server port="8005" shutdown="SHUTDOWN">

其中,屬性 shutdown 指定關閉 Server 的指令。屬性 port 指定 Server 接收 shutdown 指令的端口號,設置爲「-1」能夠禁掉該端口。服務器

2.1.2 頂層類組件 Service

Service 主要職責就是將 Engine 與 Connector 裝配在一塊兒對外提供服務,一個 Service 能夠包含多個 Connector,但只能包含一個 Engine,其中 Connector 負責從客戶端接收請求,Engine 負責處理 Connector 接收進來的請求。如前面配置示例中,Service 的配置以下所示:網絡

<Service name="Catalina">

咱們能夠經過屬性 name 爲 Service 指定名稱,不一樣的 Service 負責監管其下屬 Connector 所綁定的端口。架構

2.1.3 鏈接器組件 Connector

Tomcat 的工做模式能夠分爲下面兩類:併發

  • 做爲 Web 服務器:請求是直接來自於客戶端 HTTP 請求(或瀏覽器)。
  • 做爲 Java Web 應用服務器:請求來自於前置 Web 服務器,一般包括:Apache、IIS、Nginx 等。Tomcat 主要優點是做爲 JSP/Servlet 容器,在處理靜態資源方面效率偏低。所以,它一般要跟 Apache、IIS、Nginx 等 Web 服務器集成使用。AJP 協議主要負責 Tomcat 和集成 Web 服務器的交互鏈接。
    工做模式2
    每一個 Service 能夠有一個或多個 Connector,不一樣工做模式下,Tomcat 須要爲各類類型的請求分別定義相應的 Connector,這樣才能正確接收客戶端對應協議的請求。定義 Connector 可使用多種屬性,某些屬性只適用於某種特定的 Connector 類型。通常說來,常見的 Connector 有 4 種類型:HTTP、SSL、AJP、Proxy。
    四種鏈接器
    做爲通訊接口,Connector 爲其所屬特定的 Service 接收外部客戶端請求,以及回送應答至外部客戶端。具體職責包括建立 Request、Response 對象用於跟外部客戶端交換數據,並將 Request 交給配套的 Engine 來處理。經過修改 Connector 的屬性取值,咱們能夠控制 Service 所監聽的網絡協議及端口號,具體示例以下:
<Connector port="8080" protocol="HTTP/1.1" maxThreads=」150″ 
           connectionTimeout="20000" 
           redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Connector port=」8443″ maxThreads=」150″ minSpareThreads=」25″ 
           maxSpareThreads=」75″ enableLookups=」false」 acceptCount=」100″ 
           debug=」0″ scheme=」https」 secure=」true」 
           clientAuth=」false」 sslProtocol=」TLS」 />
  • 配置一,客戶端能夠經過 8080 端口號使用 HTTP 協議訪問 Tomcat。
  • 配置二,客戶端能夠經過 8009 端口使用 AJP 協議訪問 Tomcat。AJP 協議主要用於跟其餘的 HTTP 服務器鏈接協做。當 Tomcat 與其餘 HTTP 服務器集成時,咱們就要用到這個鏈接器。
  • 配置三,客戶端能夠經過 8443 端口號使用 HTTPS 協議訪問 Tomcat。

鏈接器的定義能夠配置的屬性很是多,下面是經常使用屬性的說明:app

  • address:指定鏈接器監聽的地址,默認爲全部地址,即:0.0.0.0。
  • maxThreads:支持最大的併發鏈接數,默認爲:200。
  • port:監聽端口,默認爲:0。
  • protocol:鏈接器使用的協議,默認爲:HTTP/1.1,定義 AJP 協議時一般爲:AJP/1.3。
  • redirectPort:在強制要求 HTTPS 的狀況下,若是請求時 HTTP,則將會被重定向至 8443 端口。
  • connectionTimeout:鏈接的超時時間,單位爲毫秒,默認爲 60000,即 1 分鐘;
  • enableLookups:是否經過 request.getRemoteHost() 進行 DNS 查詢以獲取客戶端的主機名。
  • acceptCount:設置等待隊列的最大長度。一般,在 Tomcat 全部處理線程均處於繁忙狀態時,新請求將被放置於等待隊列中。

2.1.4 容器類組件 Engine

Engine 內部能夠包含多個 Host,它是 Service 組件中負責請求處理的組件。它從一個或多個 Connector 中接收請求並處理,並將處理結果封裝成應答交給 Connector,最終回傳給外部客戶端。在前文配置文件示例中,Engine 的配置以下所示:

<Engine name="Catalina" defaultHost="localhost">

其中,屬性 name 用於日誌和錯誤信息,其取值在整個 Server 中保證惟一。屬性 defaultHost 指定了默認的Host 名稱,當 HTTP 請求所指定的 Host 名稱不存在時,一概使用 defaultHost 指定的 Host 來處理。所以,defaultHost 的值,必須與 Engine 中的某個 Host 組件的屬性 name 取值匹配。

2.1.5 容器類組件 Host

Host 表明一個虛擬主機,它對應計算機網絡上的一個實體,即某個在 DNS 服務器上註冊過的域名或者 IP 地址,例如:www.abc.com 或 201.187.10.21。Host 內部能夠包含多個 Context,每一個 Context 表示一個 Web 應用。Host 負責安裝、展開、啓動和結束每一個 Web 應用。

客戶端在填寫收件人地址時會經過主機名來標識它但願訪問的服務器,Tomcat 將從 HTTP 請求頭的 Host 字段提取主機名,而後再匹配對應的虛擬主機。若是沒有找到匹配的,HTTP 請求將被髮送至默認主機 defaultHost。所以,默認主機不須要是在 DNS 服務器上註冊的網絡名,例如:localhost。在前面配置示例中,Host 的配置以下所示:

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">

其中,屬性 name 指定虛擬主機的名稱。屬性 appBase 指定 Web 應用所在的目錄,默認值是 webapps,這是一個相對路徑,標識 Tomcat 安裝根目錄下的 webapps 文件夾。屬性 unpackWARs 指定是否將 Web 應用的 WAR 文件解壓。若是取值爲 true,Tomcat 將以解壓後的文件結構運行該 Web 應用;若是爲 false,Tomcat 將直接使用 WAR 文件運行 Web 應用。屬性 autoDeploy 指定是否自動部署 Web 應用。

2.1.6 容器類組件 Context

Context 表明在特定虛擬主機上運行的一個 Web 應用,負責處理某個特定 Web 應用的全部請求。每一個 Web 應用要麼基於 WAR 文件,要麼基於 WAR 文件解壓後對應的文件目錄。在前文配置文件示例中,咱們沒有看到 Context 的配置,這是由於 Host 開啓了自動部署,Web 應用沒有在配置文件中配置靜態部署,而是由 Tomcat 經過特定的規則自動部署,Context 組件也將被自動建立。Context 經過屬性 path 來惟一標識自身。考慮到 Web 應用自動部署與本文主題關係不大,老兵哥我就再也不展開,若是你對此內容感興趣,能夠找資料作擴展閱讀。

2.1.7 內嵌類元素

除了前面介紹的核心組件外,Tomcat 還提供了 Listener、GlobalNamingResources、Realm、Valve 等組件,這些組件都是嵌入到核心組件當中來使用,咱們將它們歸爲內嵌組件,考慮到不涉及主題就再也不贅述。

2.2 Tomcat 處理 HTTP 請求的流程

在前面各個章節介紹的各類核心組件基礎上,咱們一塊兒來看看,當 HTTP 請求被投遞到 Tomcat 所在主機以後,Tomcat 是如何將 HTTP 請求派發給特定的 Web 應用來處理的:

  • 根據協議類型和端口號選定 Service 和 Engine:Service 下屬的 Connector 組件負責監聽接收特定協議和特定端口的請求。所以,當 Tomcat 啓動時,Service 組件就開始監聽特定的端口,如前文配置文件示例,Catalina 這個 Service 監聽了 HTTP 協議 8080 端口和 AJP 協議的 8009 端口。當 HTTP 請求抵達主機網卡的特定端口以後,Tomcat 就會根據協議類型和端口號選定處理請求的 Service,隨即 Engine 也就肯定了。經過在 Server 中配置多個 Service,能夠實現經過不一樣端口訪問同一主機上的不一樣應用。
  • 根據域名或 IP 地址選定 Host:待 Service 被選定以後,Tomcat 將在 Service 中尋找與 HTTP 請求頭中指定的域名或 IP 地址匹配的 Host 來處理該請求。若是沒有匹配成功,則採用 Engine 中配置的默認虛擬主機 defaultHost 來處理該請求。
  • 根據 URI 選定 Context:URI 中的 context-path 指定了 HTTP 請求將要訪問的 Web 應用。當請求抵達時,Tomcat 將根據 Context 的屬性 path 取值與 URI 中的 context-path 的匹配程度來選擇 Web 應用處理相應請求,例如:Web 應用 spring-demo 的 path 屬性是」/spring-demo」,那麼請求「/spring-demo/user/register」將交由 spring-demo 來處理。
    HTTP 處理流程
    最終,咱們仍是繼續用向下文這個地址發送 HTTP 請求爲例,將整個處理流程串一遍:

http://201.187.10.21:8080/spring-demo/user/register

  1. 客戶端(或瀏覽器)發送請求至主機(201.187.10.21)的端口 8080,被在該端口上監聽的 Coyote HTTP/1.1 Connector 所接收。
  2. Connector 將該請求交給它所在 Service 的 Engine 來負責處理,並等待 Engine 的迴應。
  3. Engine 得到請求以後從報文頭中提取主機名稱(201.187.10.21),在全部虛擬主機 Host 當中尋找匹配。
  4. 在未匹配到同名虛擬主機的狀況下,Engine 將該請求交給名爲 localhost 的默認虛擬主機 Host 處理。
  5. Host 得到請求以後將根據 URI(/spring-demo/user/register)中的 context-path 的取值「/spring-demo」 去匹配它所擁有的全部 Context,將請求交給表明應用 spring-demo 的 Context 來處理。
  6. Context 構建 HttpServletRequest、HttpServletResponse 對象,將其做爲參數調用應用 spring-demo,由應用完成業務邏輯執行、結果數據存儲等過程,等待應答數據。
  7. Context 接收到應用返回的 HttpServletResponse 對象以後將其返回給 Host。
  8. Host 將 HttpServletResponse 對象返回給 Engine。
  9. Engine 將 HttpServletResponse 對象返回 Connector。
  10. Connector 將 HttpServletResponse 對象返回給客戶端(或瀏覽器)。

2.3 Tomcat 體系結構演進的趨勢剖析

從上述體系結構剖析來看,Tomcat 這款 Java Web 應用服務器的功能仍是很是強大的,它能夠在一個實例進程當中同時支持多種協議,同時支持多個虛擬主機,每一個虛擬主機下還支持部署多款應用,具有強大的擴展性和靈活性。爲何它具有這樣一種體系結構呢?這其實跟 Tomcat 誕生時的基礎架構相匹配的,當時服務器是以小型機或 PC 服務器爲主,缺少如今容器這種切分資源的虛擬技術,進程是系統資源分配的最小單元。爲了更加充分地利用每臺計算機上的資源,咱們一般要在同一臺計算機上部署多款應用,可是在一臺計算機上運行多個 Tomcat 實例所帶來的複雜度是很是高的,不如在同一個 Tomcat 實例中部署多款 Web 應用,這樣在配置運維等管理上面更加便利。

在這種架構下,Tomcat 處理 HTTP 請求就須要通過上述複雜的過程,這也再次印證老兵哥我堅信的一個觀點:不存在絕對好或壞的架構,匹配當時業務場景的架構就是好架構!隨着互聯網業務的發展和雲計算的興起,爲了更好地管理大規模應用集羣,咱們須要藉助容器等虛擬化技術將大顆粒資源分割成更小的、標準的單元,每一個容器中只安裝一個 Web 容器,每一個 Web 容器中只部署一個應用,在標裝化下咱們就能夠採用雲計算的自動化操做。

按照這個趨勢發展下去,Web 容器的架構用不着這麼複雜了,其價值也會不斷弱化。之前,Tomcat 都是須要單獨安裝的,應用是後續再部署到 Tomcat 當中的。但目前在 Spring Boot 的開發模式下,Tomcat 是以 Starter 方式做爲內嵌 Web 容器,它已經再也不須要獨立安裝部署了。在愈來愈標裝化的趨勢下,Tomcat 基本上採用默認配置,用戶基本上不用太關注它了。剖析瞭解它的緣由,就是老兵哥我在開題中所說的:知其然,知其因此然。

這個演進過程就像住宅樣式的變遷,古代城市人口密度沒有這麼大,每家每戶都是獨門獨院的平房。隨着近代人口不斷涌入城市,早前住宅的空間利用率過低,沒法支撐快速增加的居住需求,這時候高層樓房就應運而生了,每棟樓房都有多個樓層,每一個樓層分割成多套房子,每套房子居住一戶家庭,Tomcat 的體系結構就相似這種高層樓房。但現代城市的人口還在不斷增加,早前的高層樓房也沒法知足居住需求了,不夠標準化,缺少物業管理,周邊配套不全,空間利用率還有待於提升,在這種狀況下樓盤小區誕生了,標準化獲得了提高,也配套了物業管理,這就相似雲計算。

本文主要價值是幫助你們梳理出端到端的全流程框架,也就是咱們常說的全局視角或者上帝視角。有了這個框架以後,咱們能夠根據本身的須要按圖索驥找相關節點的資料來研究學習,不至於陷入細節找不到方向。固然,考慮到咱們每一個人的工做學習狀況不一樣,平時遇到的問題也不一樣,本文內容沒法覆蓋全部人遇到的問題,歡迎你們留言提問,也歡迎關注個人博客或公號「IT老兵哥」交流互動,我會盡力盡快解答你們提出的問題,謝謝!

本系列其餘文章索引以下:

相關文章
相關標籤/搜索