Tomcat 是一个 HTTP server。同时,它也是一个 serverlet 容器,可以执行 java 的 Servlet,也可以把 JavaServer Pages(JSP)和 JavaServerFaces(JSF)编译成 Java Servlet。它的模型图如下:

image-20211115193643613

给个生产环境 server.xml 的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version='1.0' encoding='utf-8'?>
<Server port="-1" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" /> 
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <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" 
               server="www.rendoumi.com" 
               protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxHttpHeaderSize="8192"
               acceptCount="500"
               maxThreads="1000" 
               minSpareThreads="200"
               enableLookups="false" 
               redirectPort="8443"
               connectionTimeout="20000"
               relaxedQueryChars="[]|{}@!$*()+'.,;^\`&quot;&lt;&gt;"
               disableUploadTimeout="true" 
               allowTrace="false"
               URIEncoding="UTF-8" 
               useBodyEncodingForURI="true" />  

    <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="false" autoDeploy="false">
          <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="access_log." suffix=".txt"  
                fileDateFormat="yyyy-MM-dd"
                pattern="%a|%A|%T|%{X-Forwarded-For}i|%l|%u|%t|%r|%s|%b|%{Referer}i|%{User-Agent}i " resolveHosts="false"/> 
          <Context path="" docBase="/export/servers/tomcat/webapps/web" /> 
      </Host>

    </Engine>
  </Service>
</Server>

分解开来一部分一部分的看:

Server

Server 第2行,是最大且必须唯一的组件。表示一个 Tomcat 的实例,它可以包含多个 Services,而每个 Service 都可以有自己的 Engine 和 connectors。

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

注意上面,我们把缺省的 port=“8005” 改成了 “-1”,这样防止从8005重启,避免安全问题。

Listeners

一个 Server 容器拥有若干 Listeners (行 3-7)。一个 Listener 监听并回应特定的事件.

  • 例如 GlobalResourcesLifecycleListener 就设置了 global resources,这样就可以使用 JNDI 来存取像数据库这种资源。

    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
    
Global Naming Resources

<GlobalNamingResources> (行 9-15) 定义了 JNDI (Java Naming and Directory Interface) 资源,这个是 LDAP 的东西,允许 java 软件通过目录发现对象和属性值。

缺省定义了一个 UserDatabase 的 JNDI 资源(行 10-14),这个对象其实是一个内存数据库,保存着从 conf/tomcat-users.xml 中加载上来的用户认证数据。

<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>

我们可以再定义一个 Mysql 之类的 JNDI 来保存 mysql 数据库连接信息,用来实现连接池

Services

Service 用来关联多个 ConnectorsEngine。缺省的配置是配了一个 叫做 CatalinaService ,Catalinna 缺省配了两个 Connectors,一个是8080的HTTP,一个是8009的AJP。

<Service name="Catalina"> 
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
</Service>

注意最上面的配置,我们取消了8009的AJP,这个协议现在基本没人用,取消掉也避免安全问题。

下面给出一个 SSL 8443 的 connectors 的例子

    <Connector port="8443" maxHttpHeaderSize="8192" SSLEnabled="true" server="Rendoumi"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" disableUploadTimeout="true"
               acceptCount="100" scheme="https" secure="true" clientAuth="false"
               URIEncoding="UTF-8"
               sslProtocol="SSL"
               ciphers="SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA"
               keystoreFile="/export/servers/tomcat/rendoumi.keystore"
               keystorePass="Fuck2020" />
Containers 概念

Tomcat 的容器概念,Tomcat认为 Engine, Host, ContextCluster处在同一个容器中. 最顶层的是 Engine; 最底层是Context

另外像 RealmValve,也可以放在容器中

Engine

Engine 是容器的顶端,可以包含有若干 Hosts。我们可以配置 tomcat 服务多个虚拟主机。下面配置就是服务本机的一个服务。

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

Catalina Engine 从 HTTP connector 处接收到客户端的 HTTP 请求,根据请求头中 Host: xxx 的内容,把请求路由到某个具体的 Host上。

Realm

Realm 本质是一个数据库,是用来保存用户名、密码、认证角色的东西。

<Realm className="org.apache.catalina.realm.LockOutRealm">
  <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
</Realm>
Hosts

Host 定义了虚拟主机,可以定义多个虚拟主机。其中 appBase 千万不能为空,否则就可以读到 tomcat 的所有文件,就出线上事故大 Bug 了!!!

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

我们最上面的生产配置,不自解压,也不自动部署。

Valve

Valve 是用来拦截 HTTP requests的,做预处理后再交给下一个组件,它能在 Engine, Host, Context 中被定义。下面的例子就是在 Engine 中做了拦截,定义了日志格式,交给下一个日志处理组件做处理。

缺省配置

<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" />

我们的配置修改了缺省的日志格式,多了很多明细的内容,改变了分割符:

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" 
       prefix="access_log." suffix=".txt"  
       fileDateFormat="yyyy-MM-dd"
       pattern="%a|%A|%T|%{X-Forwarded-For}i|%l|%u|%t|%r|%s|%b|%{Referer}i|%{User-Agent}i " resolveHosts="false"/> 
Context

Context定义了具体的访问路径,一定要指定 docBase !!!大家一定要记住 appBasedocBase 不同为空!!!

<Context path="" docBase="/export/servers/tomcat/webapps/web" /> 

如上,我们就配好了一个生产环境的 server.xml ,注意,这个 xml 文件是可以放在解开的tomcat压缩包 apache-tomcat-8.5.64 目录之外的。

我们再另外编写一个启动文件 start.sh:

#!/bin/sh
export JAVA_OPTS="-server -Xms1024m -Xmx2048m  -Djava.awt.headless=true -Dsun.net.client.defaultConnectTimeout=60000 -Dsun.net.client.defaultReadTimeout=60000 -Djmagick.systemclassloader=no -Dnetworkaddress.cache.ttl=300 -Dsun.net.inetaddr.ttl=300"
export CATALINA_HOME=/export/servers/tomcat/apache-tomcat-8.5.64

cd /export/servers/tomcat/
$CATALINA_HOME/bin/catalina.sh start -config "/export/servers/tomcat/server.xml"

这样,我们的启动文件和配置文件,就和 tomcat 目录分离了,这样做的好处是如果 tomcat 需要升级,那么直接解压换目录即可。

我们可以做个软链接 tomcat 链接到 apache-tomcat-8.5.64,这样更方便操作。