深入淺出Tomcat/1- 來歷和配置檔案

張太國發表於2019-01-30

背景

Tomcat是一個非常重要的Web Server,已經存在多年。尤其是最近幾年,因為Spring MVC或是Spring Boot的盛行,Tomcat的地位越發重要,地位明顯升級。
 
我相信很多人一般只是停留在使用的基礎上,但是想利用Tomcat實現一些複雜的場景或者高階同功能,我們就需要進一步學習,也需要我們把Tomcat的基礎弄清楚。
 
本文將通過大量程式碼和例項詳細講解Tomcat的基礎知識,以便我們對Tomcat有個一個整體深入的認識。
 
本文的程式碼基於Tomcat 9. 程式碼地址在https://github.com/apache/tomcat

Tomcat和Catalina的來歷

大家都在使用Tomcat,但是我問Tomcat的來歷是什麼,為什麼要會用Catalina,我認為不是每個人都知道。是的,Tomcat和Catalina是2個非常重要的名詞,在Tomcat軟體和配置裡隨處可見,但是為什麼作者取這樣的名字呢?據說,當時作者Craig寫程式碼時,家裡的貓有時候不停的跳來跳去,所以最後有Tomcat的取名,那Catalina呢?作者有一個非常喜歡的島,叫Catalina Island,據說這個島儘管作者知道,但是還沒去過,不知道現在去過沒有。所以就用Catalina取名了。
在這個島上有個鎮,叫Avalon,Tomcat曾經取過Avalon的名字,只不過後來放棄了。

Tomcat配置檔案

瞭解Tomcat的結構,最直接的辦法是從Tomcat的配置檔案看起。下面是Tomcat的配置檔案(conf/server.xml),這個是Tomcat原始碼裡的預設配置檔案。
為了簡單,我刪除了一些註釋等。
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  
  <!--APR library loader. Documentation at /docs/apr.html -->
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <!-- Global JNDI resources
       Documentation at /docs/jndi-resources-howto.html
  -->
  <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <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>

  <!-- A "Service" is a collection of one or more "Connectors" that share
       a single "Container" Note:  A "Service" is not itself a "Container",
       so you may not define subcomponents such as "Valves" at this level.
       Documentation at /docs/config/service.html
   -->
  <Service name="Catalina">


    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
        <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />


    <!-- An Engine represents the entry point (within Catalina) that processes
         every request.  The Engine implementation for Tomcat stand alone
         analyzes the HTTP headers included with the request, and passes them
         on to the appropriate Host (virtual host).
         Documentation at /docs/config/engine.html -->

    <!-- You should set jvmRoute to support load-balancing via AJP ie :
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
    -->
    <Engine name="Catalina" defaultHost="localhost">


      <!-- Use the LockOutRealm to prevent attempts to guess user passwords
           via a brute-force attack -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

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


        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>
    </Engine>
  </Service>
</Server>
我們可以看到Server、Service、Connector、Engine、Host等。那麼它們之間有什麼關係呢?

 

從以上可以看出,Tomcat最頂層的容器叫Server,代表整個伺服器,Server中包一個或多個Service,該Service用來代表一個服務。同時,一個Service也包含多個Connector,Connector用來網路連線,例如HTTP,HTTPS或AJP。Service也包含一個或多個Engine等,用來管理和封裝Servlet,以及處理具體的請求等。

我們先簡要解釋一下Tomcat的這個配置檔案。

Server
Server的配置檔案如下:
<Server port="8005" shutdown="SHUTDOWN"> 
port是8005,也就是說埠8005會監聽SHUTDOWN的命令,當我們使用tomcat目錄下的bin/shutdown.sh去停止Tomcat時,就會往8005埠傳送一個SHUTDOWN的命令。當然,如果埠8005關閉了,執行shutdown.sh指令碼就會報錯,只能kill Tomcat的程式了。
 
Listener
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- Security listener. Documentation at /docs/config/listeners.html
<Listener className="org.apache.catalina.security.SecurityListener" />
-->
<!--APR library loader. Documentation at /docs/apr.html -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /> 
先看第一個VersionLoggerListener,其實就是列印Tomcat的一些相關資訊,例如版本號,OS資訊,Java版本資訊等。
@Override
public void lifecycleEvent(LifecycleEvent event) {
    if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) {
        log();
    }
}
 
 
private void log() {
    log.info(sm.getString("versionLoggerListener.serverInfo.server.version",
            ServerInfo.getServerInfo()));
    log.info(sm.getString("versionLoggerListener.serverInfo.server.built",
            ServerInfo.getServerBuilt()));
    log.info(sm.getString("versionLoggerListener.serverInfo.server.number",
            ServerInfo.getServerNumber()));
    log.info(sm.getString("versionLoggerListener.os.name",
            System.getProperty("os.name")));
    log.info(sm.getString("versionLoggerListener.os.version",
            System.getProperty("os.version")));
    log.info(sm.getString("versionLoggerListener.os.arch",
            System.getProperty("os.arch")));
    log.info(sm.getString("versionLoggerListener.java.home",
            System.getProperty("java.home")));
    log.info(sm.getString("versionLoggerListener.vm.version",
            System.getProperty("java.runtime.version")));
    log.info(sm.getString("versionLoggerListener.vm.vendor",
            System.getProperty("java.vm.vendor")));
    log.info(sm.getString("versionLoggerListener.catalina.base",
            System.getProperty("catalina.base")));
    log.info(sm.getString("versionLoggerListener.catalina.home",
            System.getProperty("catalina.home")));
 
    if (logArgs) {
        List<String> args = ManagementFactory.getRuntimeMXBean().getInputArguments();
        for (String arg : args) {
            log.info(sm.getString("versionLoggerListener.arg", arg));
        }
    }
 
    if (logEnv) {
        SortedMap<String, String> sortedMap = new TreeMap<>(System.getenv());
        for (Map.Entry<String, String> e : sortedMap.entrySet()) {
            log.info(sm.getString("versionLoggerListener.env", e.getKey(), e.getValue()));
        }
    }
 
    if (logProps) {
        SortedMap<String, String> sortedMap = new TreeMap<>();
        for (Map.Entry<Object, Object> e : System.getProperties().entrySet()) {
            sortedMap.put(String.valueOf(e.getKey()), String.valueOf(e.getValue()));
        }
        for (Map.Entry<String, String> e : sortedMap.entrySet()) {
            log.info(sm.getString("versionLoggerListener.prop", e.getKey(), e.getValue()));
        }
    }

 

 
再看看AprLifecycleListener。其實主要是實現在Tomcat的生命週期裡,APR的初始化,建立和銷燬等。
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
它們都會實現介面LifecycleListener。該介面為某些事件定義一個listener,這裡的事件,其實就是元件的啟動和停止事件。但是元件需要實現Tomcat生命週期的介面。這個在後面講。
 
接下來就是GlobalNamingResources,它定義一些全域性的JNDI資源,例如資料庫連線等,JNDI意思是Java Naming and Directory interface。
 
然後就是Service,name為Catalina.繼續往下看,接下來就是Connector,它主要用來處理網路連線,封裝訊息包,不同的協議會有不同的處理方式,例如port為8080,協議為HTTP/1.1,會有對應的ProtocolHander來處理,這會在後面解釋。可以擁有多個Connector。
下面就是Engine,有點類似虛擬主機的意思,有個defaultHost屬性,是指在找不到虛擬主機時用的。Realm主要用來做安全域,後面也會降到。
 
Valve是Tomcat一個非常重要的概念,和Pipeline配合使用,在這裡設定的是一個關於Tomcat log的Valve,主要用於將Tomcat的日誌以某種格式列印到往到檔案,className是其實現類,prefix是日誌檔案的字首,suffix是字尾,2者組合起來就是檔名了。Pattern是日誌內容的pattern,可以將request的屬性列印到檔案裡,具體如何列印還得參看Tomcat官方文件。
 
上面的配置檔案只提及到部分元件或標籤,後面會介紹更多的配置資訊。

相關文章