深入淺出Tomcat/1- 來歷和配置文件


背景

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官方文檔。
 
上面的配置文件只提及到部分組件或標簽,后面會介紹更多的配置信息。


免責聲明!

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



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