第 8-1 章 Linux Tomcat 服务基础配置
8.1、Tomcat 术语
8.1.1、java 和 jdk 概念
无论是何种程序,要能在计算机上运行,必须能转换为二进制的机器语言才能和硬件进行交互,在机器语言的上层是汇编语言,再上层是 C/C++ 这样较底层的语言,由于它们严重依赖于平台架构,所以一般只能运行在程序源代码已编译的机器上,可移植性比较差。
Java 是一种面向对象的语言,它的层次比 C 更高一点,层次指的是离硬件更远一点。它将写好的程序文件(.java)通过 java 编译器 javac 编译成字节码类型的 class 文件(.class),在编译过程中涉及词法分析、语法分析、语义分析、字节码生成等过程,最终生成字节码的 class 文件。
class 文件是 Java 的类文件,是编译成功后的字节码文件,字节码由 Java 虚拟机 JVM 解释执行,它将每一条字节码送给解释器,再翻译成机器语言,从而在特定的机器上运行。Java 通过 JVM 的方式实现了一次编译到处运行的功能。
即:XXX.java(源码) --> javac(编译器) --> XXX.class(字节码) --> JVM --> 机器语言(依赖于不同平台) --> 执行
JRE(Java Runtime Environment) 是包含了 JVM 和其他一些简单功能的 JAVA 运行环境,它适用于只运行 java 程序时。
JDK(Java Development Kit) 比 JRE 包含了更多东西,它除了能作为 JAVA 运行环境,还提供了很多用于开发的工具,所以它适用于开发程序时使用。
JAVA SE 是 java 开发标准版,里面有 JDK,Java EE 是企业版,本质上 ee 只是比 se 多提供了几个类库而已。
8.1.2、jsp 和 servlet 概念
在 web 应用程序上,早期 Java 的实现方式是服务器端放置应用程序,客户端访问时将其下载到客户端本地并执行,这样不仅不安全,而且要求客户有 java 运行环境,这种实现方式是 applet。
与 applet 相对的是 servlet,但它是服务端程序。后来,java 将应用程序放在服务器端,客户端请求此应用程序时,服务端通过 servlet 类库分析 http 协议,将请求的应用程序在服务端执行,然后将结果组织起来返回给客户端,但此时 servlet 能分析的 http 协议非常简单,且和 html 的组织方式非常不友好,它要求 java 程序员首先得懂 html 开发(实际上现在还是如此,java 程序员至少要懂简单的 html/css/javascript 等前端技术),于是后来出现了 JSP 类库。
- JSP 可以简单的将 java 代码嵌入在 html 文档中,它们能够很友好地结合,结合后的文档类型为 .jsp 文件。
- 当客户端请求应用程序资源时,JSP 类库负责解析 .jsp 文件中的 jsp 部分并通过 jasper 组件将其翻译成 servlet 的 java 源代码,然后再编译成 class 文件并交给 JVM 来执行。
- 实际上,jsp 本质就是 servlet,jsp 类只不过是继承于 servlet 类并添加了一些和 html 友好结合的特性,最终它还是要翻译成 servlet 代码。
JSP 的本质还是 Servlet,每个 JSP 页面就是一个 Servlet 对象(当然也可能引用了其他 servlet 对象),Servlet 再负责响应用户的动态请求数据(其实也包括静态数据,因为 jasper 翻译 jsp 代码时,静态标签也被翻译到 servlet 的 java 源文件中以待输出)。对于 Tomcat 而言,JSP 页面生成的 Servlet 放在 work 路径对应的 Web 应用下。
考虑到 tomcat 和 httpd、nginx 等 http 服务程序的对比,有两点需要明确:
- 一个 java 程序只有一个进程,但是可以有多个线程,也就是说 java 程序的开发是基于线程的。那唯一的进程就是 JVM 进程,每个应用程序都开启一个 JVM 进程,根据开发时设计的多线程代码,在这个 JVM 进程中会启动多个线程。它不像 httpd 或 nginx,能开启多进程(对于 tomcat 而言,这意味着多个不同的应用程序甚至意味着开启多个 tomcat 实例)。
- tomcat 可以处理动态请求,也可以处理静态资源请求。但无论是动态资源,还是静态资源的请求,都是经过 servlet 处理并响应给客户端的,只不过请求静态资源时使用的是默认的 servlet。虽然它能像 httpd 和 nginx 一样处理静态资源,但显然,它既要处理动态请求,又要处理静态请求,压力会很大。因此 tomcat 前一般使用 httpd 或 nginx 专门处理静态请求,而动态请求则通过反向代理的方式代理至 tomcat。
8.1.3、web 服务器、web 容器、应用程序服务器
web 服务器用于提供 web 服务,要求能解析 http 协议,通常认为提供静态内容的服务器是 web 服务器。如 apache httpd、nginx 等。
对于 java 而言,web 容器是能提供 servlet 容器的服务器,它们是等价的概念。常见的有 tomcat、weblogic、websphere、jboss。其中 tomcat 只提供 servlet 容器,它们在功能上是等价的。除 tomcat 外,后面 3 种 web 容器还提供除 servlet 容器外的 EJB 容器,专用于大型分布式程序开发。
应用程序服务器是用于提供应用服务的服务器。这是业务逻辑上的概念划分。更具体一点的说,它提供 WEB 容器(servlet 容器)、EJB 容器以及其他功能。
它们之间的关系和功能大致如下:
web 服务器提供 web 服务,主要处理 http 请求并响应给客户端,并可以将动态请求委托给其他程序,如 cgi 脚本、jsp 脚本、asp 脚本等进行处理;
web 容器即 servlet 容器主要负责处理基于 http 请求的动态 jsp 内容;EJB 容器主要提供和后端数据库服务、其他服务进行交互的功能;
应用服务器通常来说包括 servlet 容器或 EJB 容器,它们都运行于支持 Java 的应用服务器中,因此 tomcat/weblogic/websphere/jboss 都算是应用服务器。
8.1.4、tomcat 体系结构
tomcat 是 jdk+servlet(严格地说是+jsp) 实现的精简版的 java ee,由于它只在 jdk 的基础上附加了 jsp 和 servlet 类库,所以它的应用范围主要是 web 应用。tomcat 项目目前由 apache 软件基金会维护。
它是一种应用程序服务器,只提供 servlet 容器,同时还提供 apache 解析静态 HTML,只不过它的处理能力不如独立的 apache 服务器。类似的应用程序服务还有 websphere/weblogic/jetty/resin/jboss 等,它们都是在 jdk 基础上附加各种类库实现不同程度的 java ee(tomcat=jdk+servlet)。
对于 tomcat 来说,它高度模块化,通过各个组件实现各种功能。它的体系结构如下图所示:
其中:
- server 是顶级类,一个 server 算是一个 tomcat 实例,在此层次中可定义 tomcat 服务的监听端口。
- service 是 server 下的子组件,用于封装绑定 connector 和 containor,并为它们提供一个名称属性。有了 service 就可以提供相关的服务,如监听 TCP 连接请求、处理 http 请求。注意 server 是管理整个 tomcat 实例的层次,它和提供服务没有关系。
- connector 是连接器,定义 http 协议(默认)以及该协议的监听端口。连接器用于接收客户端请求并将 containor 处理的数据返回给客户端。
- containor 称为容器,它和 connector 连接器进行绑定。该容器内有 4 个子容器,包括:engine 容器、host 容器、context 容器、Wrapper 容器。容器用于分析、处理请求,并构建响应给 connector 以发送给客户端。它和 connector 是 tomcat 的心脏组件。
- engine 容器定义 servlet 引擎,用于定义引擎的名称、默认的虚拟主机。引擎用于分析 http 请求并将请求转发给对应的虚拟主机。
- host 容器用于定义虚拟主机。
- context 容器用于定义 webapp,一个 context 定义一个 webapp。它是真正管理 servlet 容器的层次。
- wrapper 容器对应的是真正的 servlet 容器,一个 wrapper 代表一个 servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,一个 context 只能包含一个 wrapper。在配置文件中,无法配置该容器的属性。
- 还有一些其他组件,如 session 管理组件、JMX 等。
一个 server 可以有多个 service。一个 service 可以有多个 connector 和唯一的 containor。containor 是容器类,从 containor 层次开始,真正进入 servlet 容器相关的过程。它包含了唯一的 engine 容器,engine 容器中包含了一个或多个 host 容器,host 容器中包含了一个或多个 context 容器,context 容器中包含了唯一的 wrapper。它们的组织结构大致如下:
<server>
<service>
<connector />
<engine>
<host>
<context />
<context />
</host>
<host>
<context />
</host>
</engine>
</service>
<service>
......
</service>
</server>
8.2、安装 tomcat
tomcat 依赖于 jdk,所以需要先安装 jdk。tomcat 和 jdk 版本之间存在对应关系,应该考虑好要安装哪个版本的 jdk 以及哪个版本的 tomcat。
官方给出的对应关系网址为:https://tomcat.apache.org/whichversion.html。
本文 jdk 以 jdk-8u311为例,表示版本为 8 的第 311 次更新,tomcat 以 apache-tomcat-8.5.72为例。
8.2.1、安装 jdk
下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html
以 rpm 包的为例:
[root@fname1 ~]# yum localinstall jdk-8u311-linux-aarch64.rpm -y
安装路径为 /usr/java。在该路径有 3 个文件,其中两个是版本号的软链接。
[root@fname1 ~]# ll /usr/java/
total 0
lrwxrwxrwx 1 root root 16 Nov 9 09:13 default -> /usr/java/latest
drwxr-xr-x 9 root root 207 Nov 9 09:13 jdk1.8.0_311-aarch64
lrwxrwxrwx 1 root root 30 Nov 9 09:13 latest -> /usr/java/jdk1.8.0_311-aarch64
[root@fname1 ~]#
- 通过这种软链接方式,以后有新版 jdk 要安装,直接改 latest 的链接对象即可。
在安装目录的 Bin 目录下,有很多可执行程序,包括 javac(java编译器),java(java 主程序,其中包括 JVM),jps(查看当前 java 进程及 pid,所以可以查看 java 进程数)。
[root@fname1 ~]# cd /usr/java/latest/bin/
[root@fname1 bin]# ls
appletviewer java java-rmi.cgi jhat jrunscript keytool rmic servertool xjc
extcheck javac jcmd jinfo jsadebugd native2ascii rmid tnameserv
idlj javadoc jconsole jjs jstack orbd rmiregistry unpack200
jar javah jdb jmap jstat pack200 schemagen wsgen
jarsigner javap jdeps jps jstatd policytool serialver wsimport
[root@fname1 bin]#
执行 java -version
可以验证 jdk 工具是否安装成功。
[root@fname1 jdk1.8.0_311-aarch64]# bin/java -version
java version "1.8.0_311"
Java(TM) SE Runtime Environment (build 1.8.0_311-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.311-b11, mixed mode)
[root@fname1 jdk1.8.0_311-aarch64]#
使用 rpm 包安装 java jdk 时,早期需要手动配置 java 环境变量。比如设置 JAVA_HOME 环境变量并导出 java 程序所在目录的 PATH 环境变量,比如:
shell> echo 'export JAVA_HOME=/usr/java/latest' > /etc/profile.d/jdk.sh
shell> echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /etc/profile.d/jdk.sh
shell> . /etc/profile.d/jdk.sh
实际上我们使用 jdk-8u311-linux-aarch64.rpm 这个版本安装时,已自动通过软链接方式实现了环境变量的设置。
[root@fname1 ~]# ll /usr/bin/java*
lrwxrwxrwx 1 root root 22 Nov 9 09:13 /usr/bin/java -> /etc/alternatives/java
lrwxrwxrwx 1 root root 23 Nov 9 09:13 /usr/bin/javac -> /etc/alternatives/javac
lrwxrwxrwx 1 root root 25 Nov 9 09:13 /usr/bin/javadoc -> /etc/alternatives/javadoc
lrwxrwxrwx 1 root root 23 Nov 9 09:13 /usr/bin/javah -> /etc/alternatives/javah
lrwxrwxrwx 1 root root 23 Nov 9 09:13 /usr/bin/javap -> /etc/alternatives/javap
lrwxrwxrwx 1 root root 30 Nov 9 09:13 /usr/bin/java-rmi.cgi -> /etc/alternatives/java-rmi.cgi
[root@fname1 ~]#
8.2.2、安装 tomcat
直接解压即可:
[root@fname1 ~]# tar xf apache-tomcat-8.5.72.tar.gz -C /usr/local/
[root@fname1 ~]# ln -svf /usr/local/apache-tomcat-8.5.72/ /usr/local/tomcat
‘/usr/local/tomcat’ -> ‘/usr/local/apache-tomcat-8.5.72/’
[root@fname1 ~]#
安装完后,有以下几个文件和目录:
[root@fname1 ~]# ll /usr/local/tomcat/*/ -d
drwxr-x--- 2 root root 4096 Nov 9 09:33 /usr/local/tomcat/bin/
drwx------ 2 root root 238 Oct 1 23:15 /usr/local/tomcat/conf/
drwxr-x--- 2 root root 4096 Nov 9 09:33 /usr/local/tomcat/lib/
drwxr-x--- 2 root root 6 Oct 1 23:15 /usr/local/tomcat/logs/
drwxr-x--- 2 root root 30 Nov 9 09:33 /usr/local/tomcat/temp/
drwxr-x--- 7 root root 81 Oct 1 23:15 /usr/local/tomcat/webapps/
drwxr-x--- 2 root root 6 Oct 1 23:15 /usr/local/tomcat/work/
[root@fname1 ~]#
logs 目录是日志目录。temp 是临时目录。webapps 是存放 web 程序的根目录。work 目录是存放编译后生成的 class 文件的目录。
bin 目录下有很多脚本文件,有 .sh 脚本,也有 .bat 脚本。
[root@fname1 ~]# ls /usr/local/tomcat/bin/
bootstrap.jar commons-daemon-native.tar.gz setclasspath.sh tool-wrapper.bat
catalina.bat configtest.bat shutdown.bat tool-wrapper.sh
catalina.sh configtest.sh shutdown.sh version.bat
catalina-tasks.xml daemon.sh startup.bat version.sh
ciphers.bat digest.bat startup.sh
ciphers.sh digest.sh tomcat-juli.jar
commons-daemon.jar setclasspath.bat tomcat-native.tar.gz
[root@fname1 ~]#
-
catalina.sh 类似于 SysV 服务管理脚本,支持 stop、start 和 configtest,但不支持 restart 和 reload,如
catalina.sh start
。 -
startup.sh 等价于
catalina.sh start
。 -
shudown.sh 等价于
catalina.sh stop
。 -
configtest.sh 等价于
catalina.sh configtest
。 -
daemon.sh 是通过指定各种 PATH 参数将 tomcat 以 daemon 的方式运行,要指定的参数 path 较多。
catalina.sh 执行 start 也是在后台运行 tomcat 的,所以没有必要使用此脚本来实现 daemon 模式的 tomcat。
在安装目录下的 conf 目录下有几个配置 xml 格式的配置文件。
[root@fname1 ~]# ls /usr/local/tomcat/conf/
catalina.policy context.xml jaspic-providers.xsd server.xml tomcat-users.xsd
catalina.properties jaspic-providers.xml logging.properties tomcat-users.xml web.xml
[root@fname1 ~]#
其中 server.xml 是主配置文件,tomcat-users.xml 是状态监控和 gui 界面管理的身份认证配置文件,后面会有相关配置说明;
web.xml 是为 webapp 提供默认属性配置的文件,在 tomcat 启动时会先加载 webapp 属性的自定义配置文件 /WEB-INF/web.xml,然后再加载此文件提供默认属性,一般此文件都不用做任何修改,要定义属性时修改 /WEB-INF/web.xml 即可。
对于此处的 web.xml,唯一需要知道的是其内设置了主页文件名。
[root@fname1 ~]# grep -C 1 index /usr/local/tomcat/conf/web.xml
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
[root@fname1 ~]#
安装后,设置环境变量:
echo "export CATALINA_HOME=/usr/local/tomcat" > /etc/profile.d/tomcat.sh
echo 'export PATH=$CATALINA_HOME/bin:$PATH' >> /etc/profile.d/tomcat.sh
. /etc/profile.d/tomcat.sh
执行 bin 目录下的 version.sh 检查 PATH 环境变量是否正确:
[root@fname1 ~]# version.sh
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /usr
Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Using CATALINA_OPTS:
Server version: Apache Tomcat/8.5.72
Server built: Oct 1 2021 15:15:33 UTC
Server number: 8.5.72.0
OS Name: Linux
OS Version: 4.18.0-193.28.1.el7.aarch64
Architecture: aarch64
JVM Version: 1.8.0_311-b11
JVM Vendor: Oracle Corporation
[root@fname1 ~]#
最后启动 tomcat:
[root@fname1 ~]# catalina.sh start
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /usr
Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Using CATALINA_OPTS:
Tomcat started.
[root@fname1 ~]#
[root@fname1 ~]# netstat -tnlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 837/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 1077/master
tcp6 0 0 127.0.0.1:8005 :::* LISTEN 8481/java
tcp6 0 0 :::8080 :::* LISTEN 8481/java
tcp6 0 0 :::22 :::* LISTEN 837/sshd
tcp6 0 0 ::1:25 :::* LISTEN 1077/master
[root@fname1 ~]#
[root@fname1 ~]# curl -I http://localhost:8080
HTTP/1.1 200
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 09 Nov 2021 02:11:15 GMT
[root@fname1 ~]#
8.3、tomcat 配置
8.3.1、虚拟主机提供 web 服务
该示例通过设置虚拟主机来提供 web 服务,因为是入门示例,所以设置极其简单,只需修改 $CATALINA_HOME/conf/server.xml
文件为如下内容即可,本文的 tomcat 安装在 /usr/local/tomcat 下,因此 $CATALINA_HOME=/usr/local/tomcat
。其中大部分都采用了默认设置,只是在 engine 容器中添加了两个 Host 容器。
[root@fname1 ~]# cat /usr/local/tomcat/conf/server.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.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"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<!-- 从此处开始添加以下两个Host容器作为虚拟主机 -->
<!-- 定义一个在$CATALINA_HOME之外的虚拟主机 -->
<Host name="www.brinnatt.com" appBase="/www/brinnatt"
unpackWARs="true" autoDeploy="true">
<Context path="" docBase="/www/brinnatt" reloadable="true" />
<Context path="/yidam" docBase="yidam" reloadable="true" />
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="brinnatt_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
<!-- 定义一个在$CATALINA_HOME/webapps下的虚拟主机 -->
<Host name="www.charman.com" appBase="webapps/charman"
unpackWARs="true" autoDeploy="true">
<Context path="" docBase="" reloadable="true" />
<Context path="/yidam" docBase="yidam" reloadable="true" />
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="charman_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
<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>
[root@fname1 ~]#
- 除了 engine 中定义的默认 localhost 虚拟主机,另外布置了两个虚拟主机 www.brinnatt.com 和 www.charman.com,它们的程序目录分别为 /www/brinnatt 和
$CATALINA_HOME/webapps/charman
,所以需要提前建立好这两个目录。 - 另外,在 context 中定义了 docBase,对于 uri 路径 /yidam,它的文件系统路径分别为 /www/brinnatt/yidam 目录和
$CATALINA_HOME/webapps/charman/yidam
,所以也要在上面两个程序目录中定义好 yidam 目录。 - 除此之外,还分别为这 3 个虚拟主机定义了日志,它们的路径为相对路径 logs,相对于
$CATALINA_HOME
。
再提供 appBase 目录和 docBase 目录:
[root@fname1 ~]# mkdir -pv /www/brinnatt/yidam
mkdir: created directory ‘/www’
mkdir: created directory ‘/www/brinnatt’
mkdir: created directory ‘/www/brinnatt/yidam’
[root@fname1 ~]#
[root@fname1 ~]# mkdir -pv /usr/local/tomcat/webapps/charman/yidam
mkdir: created directory ‘/usr/local/tomcat/webapps/charman’
mkdir: created directory ‘/usr/local/tomcat/webapps/charman/yidam’
[root@fname1 ~]#
再提供测试用的 index.jsp 文件。内容大致如下,分别复制到以下四个目录中:
/www/brinnatt
/www/brinnatt/yidam
/usr/local/tomcat/webapps/charman
/usr/local/tomcat/webapps/charman/yidam
并将 out.println 的输出内容分别对应修改,使能够区分读取的是哪个 index.jsp。
<%@ page language="java" %>
<%@ page import="java.util.*" %>
<html>
<body>
<% out.println("hello world from [brinnatt|charman] [Root|yidam]"); %>
</body>
</html>
最后重启 catalina:
[root@fname1 ~]# catalina.sh stop
[root@fname1 ~]# catalina.sh start
在测试主机上添加 www.{brinnatt,charman}.com 的 host 记录。例如在 windows 上,在 C:\Windows\System32\drivers\etc\hosts 中添加如下记录:
192.168.122.45 www.brinnatt.com www.charman.com
在浏览器中进行测试,结果如下:
8.3.2、tomcat 体系结构
tomcat 高度模块化,各个模块之间有嵌套的父子关系。如果使用配置文件来描述,可以大致简化为如下:
<server>
<service>
<connector PORT />
<engine>
<host name=www.brinnatt.com appBase=/www/brinnatt >
<context path="" docBase=/www/brinnatt />
<context path="/yidam" docBase=/www/brinnatt/yidam />
</host>
<host>
<context />
</host>
</engine>
</service>
</server>
-
server
组件是管理 tomcat 实例的组件,可以监听一个端口,从此端口上可以远程向该实例发送 shutdown 关闭命令。 -
service
组件是一个逻辑组件,用于绑定 connector 和 container,有了 service 表示可以向外提供服务,就像是一般的 daemon 类服务的 service。可以认为一个 service 就启动一个 JVM,更严格地说,一个 engine 组件才对应一个 JVM(定义负载均衡时,jvmRoute 就定义在 Engine 组件上用来标识这个 JVM),只不过 connector 也工作在 JVM 中。 -
connector
组件是监听组件,它有四个作用:- 开启监听套接字,监听外界请求,并和客户端建立 TCP 连接;
- 使用 protocolHandler 解析请求中的协议和端口等信息,如 http 协议、AJP 协议;
- 根据解析到的信息,使用 processer 将分析后的请求转发给绑定的 Engine;
- 接收响应数据并返回给客户端。
-
container
是容器,它是一类组件,在配置文件(如 server.xml) 中没有体现出来。它包含 4 个容器类组件:engine 容器、host 容器、context 容器和 wrapper 容器。 -
engine
容器用于从 connector 组件处接收已建立的 TCP 连接,还用于接收客户端发送的 http 请求并分析请求,然后按照分析的结果将相关参数传递给匹配出的虚拟主机。engine 还用于指定默认的虚拟主机。 -
host
容器定义虚拟主机,由于 tomcat 主要是作为 servlet 容器的,所以为每个 webapp 指定了它们的根目录 appBase。 -
context
容器主要是根据 path 和 docBase 获取一些信息,将结果交给其内的 wrapper 组件进行处理(它提供 wrapper 运行的环境,所以它叫上下文 context)。一般来说,都采用默认的标准 wrapper 类,因此在 context 容器中几乎不会出现 wrapper 组件。 -
wrapper
容器对应 servlet 的处理过程。它开启 servlet 的生命周期,根据 context 给出的信息以及解析 web.xml 中的映射关系,负责装载相关的类,初始化 servlet 对象 init()、执行 servlet 代码 service() 以及服务结束时 servlet 对象的销毁 destory()。 -
executor
组件为每个 Service 组件提供线程池,使得各个 connector 和 Engine 可以从线程池中获取线程处理请求,从而实现 tomcat 的并发处理能力。一定要注意,Executor 的线程池大小是为 Engine 组件设置,而不是为 Connector 设置的,Connector 的线程数量由 Connector 组件的 acceptorThreadCount 属性来设置。
如果要在配置文件中设置该组件,则必须设置在 Connector 组件的前面,以便在 Connector 组件中使用
executor
属性来引用配置好的 Executor 组件。如果不显式设置,则采用 Connector 组件上的默认配置,默认配置如下:- maxThreads:最大线程数,默认值 200。
- minSpareThreads:最小空闲线程数,默认值 25。
- maxIdleTime:空闲线程的线程空闲多长时间才会销毁,默认值 60000 即 1 分钟。
- prestartminSpareThreads:是否启动 executor 时就直接创建等于最小空闲线程数的线程,默认值为 false,即只在有连接请求进入时才会创建。
根据上面描述的 tomcat 组件体系结构,处理请求的大致过程其实很容易推导出来:
Client(request) --> Connector --> Engine --> Host --> Context --> Wrapper(response data) --> Connector(response header) --> Client
tomcat 作为简单的 web 服务程序大致如此,但它的核心毕竟是处理 servlet 和 jsp,它必须得管理好每个 webapp。因此,对于 tomcat 来说,必须要掌握部署 webapp 的方式。在 tomcat 上部署 webapp 时,必须要理解 context 的概念。
对于 tomcat 而言,每个 context 都应该算是一个 webapp,其路径由 docBase 决定,该目录存放的是归档的 war 文件或未归档的 webapp 相关文件,而 host 容器中的 appBase 则是虚拟主机整理 webapp 的地方,一个 appBase 下可以有多个 webapp,即多个 context。
为了便于理解,我们把 tomcat 配置与 nginx 配置对比记忆,这种对比只是在逻辑上有相似的思路,具体实现上没有任何关联。
server {
listen PORT; # 对应于 connector
server_name www.brinnatt.com; # 对应于<host name=www.brinnatt.com>
location / { # 对应于context path=""
root html; # 对应于docBase
}
location /yidam { # 对应于context path="/yidam"
root html/yidam;
}
}
8.3.3、appBase 和 docBase 详细说明
<host name=www.brinnatt.com appBase=/www/brinnatt >
<context path="" docBase=/www/brinnatt />
<context path="/yidam" docBase=/www/brinnatt/yidam />
</host>
appBase 是虚拟主机存放 webapp 的目录,它可以是相对路径,也可以是绝对路径。如果是相对路径,则相对于 $CATALINA_HOME
,严格并准确地说是 $CATALINA_BASE
。
path 是 URI 的匹配路径,相当于 nginx 的 location 后的路径。tomcat 要求每个虚拟主机必须配置一个空字符串的 path,该条 context 作为 URI 无法被明确匹配时的默认 context,它相当于 nginx 中 location / {}
的作用。
docBase 则是每个 webapp 的存放目录(或者是已归档的 war 文件),它可以是相对路径,也可以是绝对路径,提供相对路径时它相对于 appBase。该目录一般在 appBase 的目录下,但并不规定一定要放在 appBase 下。对于 web 服务来说,它相当于 nginx 的 root 指令,但对于 webapp 来说,一个 context 就相当于一个 webapp,而 docBase 正是 webapp 的路径。
默认规则,有以下几种情况:
- 明确定义了
<context path="" docBase=webappPATH>
,此时默认 context 的处理路径为 webappPATH。 - 明确定义了
<context path="">
,但却没给定 docBase 属性,此时该默认 context 处理路径为 appBase/ROOT 目录,注意 ROOT 为大写。 - 完全没有定义
path=""
的 context 时,即 host 容器中没有明确的 path="",此时将隐式定义一个默认 context,处理路径为 appBase/ROOT 目录。 - 定义了 path 但没有定义 docBase 属性时,docBase 将根据 path 推断出它的路径。推断的规则如下:(注:此时推断的不是默认 context,而是对应 context 的 docbase)
context path context name 推断出的docBase路径
--------------------------------------------------
/foo /foo foo
/foo/bar /foo/bar foo/bar
Empty String Empty String ROOT
显然,没有给定 path="" 或缺少 docbase 时,都以 ROOT 作为目录。以下是几个定义示例:
# 虚拟主机中没有定义任何context,将以appBase下的ROOT作为默认处理路径
<Host appBase="webapps">
</Host>
# 没有定义path=""的context,但定义了path非空的context,也将以ROOT作为默认处理路径
# 如果下面的Context容器中省略docBase属性,则推断出该context的docBase路径为appBase/yidam
<Host appBase="webapps">
<Context path="/yidam" docBase="webappPATH" />
</Host>
# 某个context定义了path="",该context将作为默认context
# 但该默认context如果没有定义docBase,将推断出其docBase路径为appBase/ROOT
<Host appBase="webapps">
<Context path="" docBase="webappPATH" />
</Host>
# 某个context定义了path="",该context将作为默认context
# 下面的默认context明确定义了docBase
<Host appBase="webapps">
<Context path="" docBase="webappPATH" />
</Host>
举个直观的例子,如果某个 Host 配置如下:
<Host name="www.charman.com" appBase="/www/charman" unpackWARs="true" autoDeploy="true">
<Context path="/yidam" docBase="yidam" reloadable="true" />
</Host>
- 那么浏览器访问
http://www.charman.com:8080/yidam/
将请求/www/charman/yidam/index.jsp
。 - 由于没有定义 path="" 的 Context 组件,因此浏览器访问
http://www.charman.com:8080
将请求 /www/charman/ROOT/index.jsp
。注意,是 ROOT 目录。 - 如果加上
<Context path="" docBase="" reloadable="true" />
,则访问http://www.charman.com:8080
将请求/www/charman/index.jsp
。注意,不是 ROOT 目录,而是相对于 appBase 的根目录,即 /www/charman。
尽管本文解释了很多关于 appBase 和 docBase 的设置,但一般都会采用大众所熟知的配置方式:appBase 设置为 "webapps",即 $CATALINA_HOME/webapps
,而 docBase 设置为 webapps 下的 webapp 应用名。
这样配置不仅符合 eclipse 部署 webapp 时默认的部署目录结构(eclipse 部署应用时,将 WebContent 下的内容复制到 docBase 下,将 servlet java 源代码编译后的 class 文件复制到 WEB-INF/classes 目录下),更利于维护 webapp 和相关配置。例如:
<Context docBase="MyWeb" path="/MyWeb" reloadable="true" />
<Context docBase="SecondWeb" path="/SecondWeb" reloadable="true" />
<Context docBase="WEB" path="/WEB" reloadable="true" />
但这样的配置有个缺点,因为项目名称一般都会带有大写字母,使得在浏览器访问时,也要带有大写字母。例如输入 http://www.charman.com/MyWeb/index.jsp
。因此,可采用另一种配置方式:设置 Host 的 appBase 为 webapps 下的某个目录,然后在 path 上配置 uri 匹配路径。如下:
<Host name="www.charman.com" appBase="webapps/MyWeb" unpackWARs="true" autoDeploy="true">
<Context path="/yidam" docBase="yidam" reloadable="true" />
<Context path="" docBase="" reloadable="true" />
</Host>
8.3.4、webapp 目录体系结构
webapp 有特定的组织格式,是一种层次型目录结构,通常包含了 servlet 代码文件、jsp 页面文件、类文件、部署描述符文件等等。
这些文件可能是以目录的形式存放,也可能会打包成各种归档格式的文件,如 jar、war 等。但 jsp 有规定,在 web 应用程序的根目录下,一般要有下面几个目录:
- /WEB-INF:此 webapp 的私有资源目录,从浏览器上是无法访问此目录资源的,通常 web.xml 放置于此目录。
- /WEB-INF/classes:此 webapp 自有的类。
- /WEB-INF/lib:此 webapp 自有能够打包为 jar 格式的类。
- /META-INF:并非标准的 webapp 目录,有的应用程序才有。当该应用程序想独立定义自己的 context.xml 时可放入此目录,也是私有目录。
每个 webapp 要想被 tomcat 加载,一种方法是程序目录放在 $catalina.home/webapps
下,另一种方式是配置该 webapp 相关的 context 配置,使 tomcat 能找到此 webapp。正如前文所说,webapp 目录一般都会放在 $catalina.home/webapps
下。
简单部署示例:
- 对于 war 类归档程序:将归档文件复制到
$CATALINA_BASE/webapps/
目录中,并重启 tomcat 即可,tomcat 会自动展开 war 归档。例如官方提供了一个 sample.war 作为 tomcat 学习初级示例( https://tomcat.apache.org/tomcat-8.5-doc/appdev/sample/sample.war ),下载后只需将其放入 webapps 下即可。 - 在测试 tomcat 的过程中,很多时候是未归档程序,这时可以手动创建目录来实现部署。需要创建的目录有 webapps/yourapp,此目录下还要创建 WEB-INF 目录,在 WEB-INF 目录中还要创建 classes 和 lib 目录。然后将 jsp 文件放在对应目录下即可,如写一个测试的 index.jsp 放在 yourapp 目录下。而对于已经开发完毕的 webapp,因为 eclipse 在发布测试 webapp 时已经设置好目录,因此只要将 webapp 的目录复制到 webapps 目录下即可。
8.3.5、server.xml 详解
tomcat 配置文件中配置的是各个组件的属性,全局配置文件为 $CATALINA_HOME/conf/server.xml
,主要的组件有以下几项:Server,Service,Connector,Engine,Host,Alias,Context,Valve 等。
配置完配置文件后需要重启 tomcat,但在启动后一定要检查 tomcat 是否启动成功,因为即使出错,很多时候它都不会报错,可从监听端口判断。配置方法见 官方手册,在页面的左边有各个组件的链接。
tomcat 的配置文件都是 xml 文件,以下是 xml 文件的常见规则:
- 文件第一行设置 xml 标识,表示该文件是 xml 格式的文件。例如
<?xml version="1.0" encoding="UTF-8"?>
。 - xml 文件的注释方法为
<!-- XXX -->
,这可以是单行注释,也可以多行注释,只要前后注释符号能对应上,中间的内容都是注释。 - 定义属性时有两种方式:单行定义和多行定义。例如:
<!-- 单行定义的方式 -->
<NAME key=value />
<!-- 多行定义的方式 -->
<NAME key=value>
</NAME>
下面各组件的配置中有些地方使用了相对于 $CATALINA_BASE
的相对路径,它和 $CATALINA_HOME
小有区别。如果只有一个 tomcat 实例,则它们是等价的,都是 tomcat 的安装路径。如果有多个 tomcat 实例,则 $CATALINA_HOME
表示的是安装路径,而 $CATALINA_BASE
表示的是各实例所在根目录。关于 tomcat 多实例,见 running.txt 中对应的说明。
8.3.5.1、顶级元素 server
server 组件定义的是一个 tomcat 实例。默认定义如下:
<Server port="8005" shutdown="SHUTDOWN">
</Server>
- 它默认监听在 8005 端口以接收 shutdown 命令。要启用多个 tomcat 实例,将它们监听在不同的端口即可。这个端口的定义为管理员提供一个关闭实例的便捷途径,可以直接 telnet 至此端口使用 SHUTDOWN 命令关闭此实例。不过基于安全角度的考虑,通常不允许远程进行。
- Server 的相关属性:
className
:用于实现此组件的 java 类的名称,这个类必须实现接口 org.apache.catalina.Server。不给定该属性时将采用默认的标准类 org.apache.catalina.core.StandardServer;address
:监听端口绑定的地址。如不指定,则默认为 Localhost,即只能在 localhost 上发送 SHUTDOWN 命令;port
:接收 shutdown 指令的端口,默认仅允许通过本机访问,默认为 8005;shutdown
:通过 TCP/IP 连接发往此 Server 用于实现关闭 tomcat 实例的命令字符串。
- 在 server 组件中可嵌套一个或多个 service 组件。
8.3.5.2、顶级元素 service
定义了 service 就能提供服务了。service 组件中封装 connector 和 container,它同时也表示将此 service 中的 connector 和 container 绑定起来,即由它们组成一个 service 向外提供服务。默认定义如下:
<Service name="Catalina">
</Service>
- Service 相关的属性:
className
:用于实现 service 的类名,这个类必须实现 org.apache.catalina.Service 接口。不给定该属性时将采用默认的标准类 org.apache.catalina.core.StandardService。name
:此 service 的显示名称,该名称主要用于在日志中进行标识 service。一般来说无关紧要,默认为 Catalina。
8.3.5.3、执行器 executor
执行器定义 tomcat 各组件之间共享的线程池。在以前,每个 connector 都会独自创建自己的线程池,但现在,可以定义一个线程池,各组件都可以共享该线程池,不过主要是为各 connector 之间提供共享。
注意,executor 创建的是共享线程池,如果某个 connector 不引用 executor 创建的线程池,那么该 connector 仍会根据自己指定的属性创建它们自己的线程池。
连接器必须要实现 org.apache.catalina.Executor 接口。它是一个嵌套在 service 组件中的元素,为了挑选所使用的 connector,该元素还必须定义在 connector 元素之前。
默认的定义如下:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
- 其中该组件的属性有:
className
:用于实现此组件的 java 类的名称,这个类必须实现接口 org.apache.catalina.Executor。不给定该属性时将采用默认的标准类 org.apache.catalina.core.StandardThreadExecutor;name
:该线程池的名称,其他组件需要使用该名称引用该线程池。
- 标准类的属性包括:
threadPriority
:线程优先级,默认值为 5。daemon
:线程是否以 daemon 的方式运行,默认值为 true。namePrefix
:执行器创建每个线程时的名称前缀,最终线程的名称为 namePrefix+threadNumber。maxThreads
:线程池激活的最大线程数量。默认值为 200。minSpareThreads
:线程池中最少空闲的线程数量。默认值为 25。maxIdleTime
:在空闲线程关闭前的毫秒数。除非激活的线程数量小于或等于 minSpareThreads 的值,否则会有空闲线程的出现。默认值为 60000,即空闲线程需要保留 1 分钟的空闲时间才被杀掉。maxQueueSize
:可执行任务的最大队列数,达到队列上限时的连接请求将被拒绝。prestartminSpareThreads
:在启动 executor 时是否立即创建 minSpareThreads 个线程数,默认为 false,即在需要时才创建线程。
例如在 connector 中指定所使用的线程池,方式如下:
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
8.3.5.4、连接器 connector
连接器用于接收客户端发送的请求并返回响应给客户端。一个 service 中可以有多个 connector。有多种 connector,常见的为 http/1.1,http/2 和 ajp(apache jserv protocol)。在 tomcat 中,ajp 连接协议类型专用于 tomcat 前端是 apache 反向代理的情况下。
因此 tomcat 可以扮演两种角色:
- Tomcat 仅作为应用程序服务器:请求来自于前端的 web 服务器,这可能是 Apache, IIS, Nginx 等;
- Tomcat 既作为 web 服务器,也作为应用程序服务器:请求来自于浏览器。
Tomcat 应该考虑工作情形并为相应情形下的请求分别定义好需要的连接器才能正确接收来自于客户端的请求。这里先介绍 HTTP/1.1 连接器的属性设置。ajp 后面再做介绍。
HTTP 连接器表示支持 HTTP/1.1 协议的组件。设置了该连接器就表示 catalina 启用它的独立 web 服务功能,当然,肯定也提供它必须的 servlets 和 jsp 执行功能。在一个 service 中可以配置一个或多个连接器,每个连接器都可以将请求转发给它们相关联的 engine 以处理请求、创建响应。
每个流入的请求都需要一个独立的线程来接收。当并发请求数量超出 maxThreads 指定的值时,多出的请求将被堆叠在套接字中,直到超出 acceptCount 指定的值。超出 accpetCount 的请求将以 "connection refused" 错误进行拒绝。
默认的定义如下:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-
HTTP 连接器的属性实在太多,详细配置方法见 官方手册。通常定义 HTTP 连接器时必须定义的属性只有 "port"。
address
:指定连接器监听的地址,默认为所有地址,即 0.0.0.0。maxThreads
:支持的最大并发连接数,默认为 200;如果引用了 executor 创建的共享线程池,则该属性被忽略。acceptCount
:设置等待队列的最大长度;通常在 tomcat 所有处理线程均处于繁忙状态时,新发来的请求将被放置于等待队列中;maxConnections
:允许建立的最大连接数。acceptCount 和 maxThreads 是接受连接的最大线程数。存在一种情况,maxConnections 小于 acceptCount 时,超出 maxConnections 的连接请求将被接收,但不会与之建立连接。port
:监听的端口,默认为 0,此时表示随机选一个端口,通常都应该显式指定监听端口。protocol
:连接器使用的协议,用于处理对应的请求。默认为 HTTP/1.1,此时它会自动在基于 Java NIO 或 APR/native 连接器之间进行切换。定义 AJP 协议时通常为 AJP/1.3。redirectPort
:如果某连接器支持的协议是 HTTP,当接收客户端发来的 HTTPS 请求时,则转发至此属性定义的端口。connectionTimeout
:等待客户端发送请求的超时时间,单位为毫秒,默认为 60000,即 1 分钟;注意,这时候连接已经建立。keepAliveTimeout
:长连接状态的超时时间。超出该值时,长连接将关闭。enableLookups
:是否通过 request.getRemoteHost() 进行 DNS 查询以获取客户端的主机名;默认为 true,应设置为 false 防止反解客户端主机;compression
:是否压缩数据。默认为 off。设置为 on 时表示只压缩 text 文本,设置为 force 时表示压缩所有内容。应该在压缩和 sendfile 之间做个权衡。useSendfile
:该属性为 NIO 的属性,表示是否启用 sendfile 的功能。默认为 true,启用该属性将会禁止 compression 属性。
- 当协议指定为 HTTP/1.1 时,默认会自动在 NIO/APR 协议处理方式上进行按需切换。如要显式指定协议,方式如下:
<connector port="8080" protocol="HTTP/1.1">
<connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol">
<connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol">
<connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol">
- 其中 NIO 是 C/C++ 的非阻塞 IO 复用模型在 JAVA 中的 IO 实现,NIO2 即 AIO 是异步 NIO,即异步非阻塞 IO:
- NioProtocol :non blocking Java NIO connector
- Nio2Protocol:non blocking Java NIO2 connector
- AprProtocol :the APR/native connector
下面是一个定义了多个属性的 SSL 连接器:
<Connector port="8443"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" acceptCount="100" debug="0" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" />
8.3.5.5、容器类 engine
engine 是 service 组件中用来分析协议的引擎机器,它从一个或多个 connector 上接收请求,并将请求交给对应的虚拟主机进行处理,最后返回完整的响应数据给 connector,通过 connector 将响应数据返回给客户端。
只有一个 engine 元素必须嵌套在每个 service 中,且 engine 必须在其所需要关联的 connector 之后,这样在 engine 前面的 connector 都可以被此 engine 关联,而在 engine 后面的 connector 则被忽略,因为一个 service 中只允许有一个 engine。
定义方式大致如下:
<Engine name="Catalina" defaultHost="localhost">
</Engine>
<Engine name="Standalone" defaultHost="localhost" jvmRoute="TomcatA">
</Engine>
- 常用的 engine 属性有:
className
:实现 engine 的类,该类必须实现 org.apache.catalina.Engine 接口。不给定该属性时将采用默认的标准类 org.apache.catalina.core.StandardEngine。defaultHost
:指定处理请求的默认虚拟主机。在 Engine 中定义的多个虚拟主机的主机名称中至少有一个跟 defaultHost 定义的主机名称同名。name
:Engine 组件的名称,用于记录日志和错误信息,无关紧要的属性,可随意给定。jvmRoute
:在启用 session 粘性时指定使用哪种负载均衡的标识符。所有的 tomcat server 实例中该标识符必须唯一,它会追加在 session 标识符的尾部,因此能让前端代理总是将特定的 session 转发至同一个 tomcat 实例上。- 注意,jvmRoute 同样可以使用 jvmRoute 的系统属性来设置。如果此处设置了 jvmRoute,则覆盖 jvmRoute 系统属性。关于 jvmRoute 的使用,在后面 tomcat ajp 负载均衡的文章中介绍。
engine 是容器中的顶级子容器,其内可以嵌套一个或多个 Host 作为虚拟主机,且至少一个 host 要和 engine 中的默认虚拟主机名称对应。除了 host,还可以嵌套 releam 和 valve 组件。
8.3.5.6、容器类 host
host 容器用来定义虚拟主机。engine 从 connector 接收到请求进行分析后,会将相关的属性参数(筛选方式是从请求首部的 host 字段和虚拟主机名称进行匹配)传递给对应的虚拟 host 进行处理。
如果没有合适的虚拟主机,则传递给默认虚拟主机。因此每个容器中必须至少定义一个虚拟主机,且必须有一个虚拟主机和 engine 容器中定义的默认虚拟主机名称相同。
大致定义方式如下:
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
</Host>
- 常用属性说明:
className
:实现 host 容器的类,该类必须实现 org.apache.catalina.Host 接口。不给定该属性时将采用默认的标准类 org.apache.catalina.core.StandardHost。name
:虚拟主机的主机名,忽略大小写(初始化时会自动转换为小写)。可以使用前缀星号通配符,如"*.a.com"
。使用了星号前缀的虚拟主机的匹配优先级低于精确名称的虚拟主机。appBase
:此 Host 的 webapps 目录,即 webapp 部署在此虚拟主机上时的存放目录。包括非归档的 web 应用程序目录和归档后的 WAR 文件的目录。使用相对路径时基于$CATALINA_BASE
。xmlBase
:部署在此虚拟主机上的 context xml 目录。startStopThreads
:启动 context 容器时的并行线程数。如果使用了自动部署功能,则再次部署或更新时使用相同的线程池。autoDeploy
:在 Tomcat 处于运行状态时放置于 appBase 目录中的应用程序文件是否自动进行 deploy 或自动更新部署状态。这等于同时开启了 deployOnStartup 属性和 reload/redeploy webapp 的功能。触发自动更新时将默认重载该 webapp。默认为 true。unpackWars
:在执行此 webapps 时是否先对归档格式的 WAR 文件解压再运行,设置为 false 时则直接执行 WAR 文件;默认为 true。设置为 false 时会损耗性能。workDir
:该虚拟主机的工作目录。每个 webapp 都有自己的临时 IO 目录,默认该工作目录为$CATALINA_BASE/work
。
大多数时候都只需设置虚拟主机名称 name 和 appBase 属性即可,其余采用默认,默认时会自动部署 webapp。有时候还需要管理多个站点名称,即主机别名。可以使用 Alias 为 Host 指定的主机名定义主机别名。如:
<Host name="web.a.com" appBase="webapps" unpackWARs="true">
<Alias>www.a.com</Alias>
</Host>
自动部署指的是自动装载 webapp 以提供相关 webapp 的服务。
8.3.5.7、容器类 context
connector 和 container 是整个 tomcat 的心脏,而 context 则是 container 的心脏,更是 tomcat 心脏的心脏。它是真正管理 servlet 的地方,它的配置影响了 servlet 的工作方式。
一个 context 代表一个 webapp。servlet 中规定,每个 webapp 都必须基于已归档的 WAR(WEB application archive)文件或基于非归档相关内容所在目录。
catalina 基于对请求 URI 与 context 中定义的 path 进行最大匹配前缀的规则进行挑选,从中选出使用哪个 context 来处理该 HTTP 请求。这相当于 nginx 的 location 容器,catalina 的 path 就相当于 location 的 path,它们的作用是相同的。
每个 context 都必须在虚拟主机容器 host 中有一个唯一的 context name。context 的 path 不需要唯一,因为允许同一个 webapp 不同版本的共存部署。此外,必须要有一个 context 的 path 为 0 长度的字符串(如 <Context path="" docBase="ROOT"/>
),该 context 是该虚拟主机的默认 webapp,用于处理所有无法被虚拟主机中所有 context path 匹配的请求。
关于 context name,它是从 context path 推断出来的,不仅如此,其余几个属性如 context basefile name 也是由此推断出来的。规则如下:
-
如果 path 不为空,则 context name 等于 context path,basefile name 取 path 中去除前缀 "/" 后的路径,且所有 "/" 替换为 "#"。
-
如果 path 为空,则 context name 也为空,而 basefile 为 ROOT(注意是大写)。
context path context name basefile name deploy examples
-----------------------------------------------------------------
/foo /foo foo foo.xml,foo.war,foo
/foo/bar /foo/bar foo#bar foo#bar.xml,foo#bar.war,foo#bar
Empty String Empty String ROOT ROOT.xml,ROOT.war,ROOT
配置 context 时,强烈建议不要定义在 server.xml 中,因为定义在 conf/server.xml 中时,只能通过重启 tomcat 来重载生效,也就是说无法自动部署应用程序了。但大多数人出于习惯和方便,还是会直接写在 server.xml 中,这并没有什么问题,无非是重启一下而已。
可以考虑定义在 /META-INF/context.xml
中,如果此时设置了 copyXML
属性,在部署时会将此 context.xml
复制到 $CATALINA_BASE/conf/enginename/hostname/
下,并重命名为 "basefile name.xml"
。也可以直接定义在 $CATALINA_BASE/conf/enginename/hostname/
下的 .xml
文件中,该路径的 xml
优先级高于 /META-INF/context.xml
。
还可以定义默认的 context.xml 文件,包括两种:
- 定义在
$CATALINA_BASE/conf/context.xml
中,该默认 context 对所有 webapp 都生效; - 定义在
$CATALINA_BASE/conf/[enginename]/[hostname]/context.xml.default
中,该默认 context 只对该虚拟主机中的所有 webapp 生效。
定义方式大致如下:
<Host name="www.a.com" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context path="" docBase="ROOT"/>
<Context path="/bbs" docBase="web/bbs" reloadable="true"/>
</Host>
- 其中第一个 context 的 path 为空字符串,表示它是默认的 context。当浏览器中输入 www.a.com 时,由于无法匹配第二个 context,所以被默认即第一个 context 处理。
- 当浏览器中输入 www.a.com/bbs 时,将被第二个 context 处理,它将执行 web/bbs 所对应的 webapp,并返回相关内容。
- 在 context 容器中可以定义非常多的属性,详细内容见 官方手册,以下是常见的几个属性:
className
:实现 host 容器的类,该类必须实现 org.apache.catalina.Context 接口。不给定该属性时将采用默认的标准类 org.apache.catalina.core.StandardContext。cookies
:默认为 true,表示启用 cookie 来标识 session。docBase
:即 DocumentRoot,是该 webapp 的 context root,即归档 WAR 文件所在目录或非归档内容所在目录。可以是绝对路径,也可以是相对于该 webapp appBase 的相对路径。path
:定义 webapp path。注意,当 path="" 时,表示默认的 context;另外只有在 server.xml 中才需要定义该属性,其他所有情况下都不能定义该属性,因为会根据 docBase 和 context 的 xml 文件名推断出 path。reloadable
:是否监控 /WEB-INF/class 和 /WEB-INF/lib 两个目录中文件的变化,变化时将自动重载。在测试环境下该属性很好,但在真实生产环境部署应用时不应该设置该属性,因为监控会大幅增加负载,因此该属性的默认值为 false。wrapperClass
:实现 wrapper 容器的类,wrapper 用于管理该 context 中的 servlet,该类必须实现 org.apache.catalina.Wrapper 接口,如果不指定该属性则采用默认的标准类。xmlNamespaceAware
:和 web.xml 的解析方式有关。默认为 true,设置为 false 可以提升性能。xmlValidation
:和 web.xml 的解析方式有关。默认为 true,设置为 false 可以提升性能。
8.3.5.8、被嵌套类 realm
realm 定义的是一个安全上下文,就像是以哪种方式存储认证时的用户和组相关的数据库。有多种方式可以实现数据存放:
- JAASRealm:基于 Java Authintication and Authorization Service 实现用户认证;
- JDBCRealm:通过 JDBC 访问某关系型数据库表实现用户认证;
- JNDIRealm:基于 JNDI 使用目录服务实现认证信息的获取;
- MemoryRealm:查找 tomcat-user.xml 文件实现用户信息的获取;
- UserDatabaseRealm:基于 UserDatabase 文件(通常是 tomcat-user.xml) 实现用户认证,它实现是一个完全可更新和持久有效的 MemoryRealm,因此能够跟标准的 MemoryRealm 兼容;它通过 JNDI 实现;
下面是一个常见的使用 UserDatabase 的配置:
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
下面是一个使用 JDBC 方式获取用户认证信息的配置:
<Realm className="org.apache.catalina.realm.JDBCRealm" debug="99"
driverName="org.gjt.mm.mysql.Driver"
connectionURL="jdbc:mysql://localhost/authority"
connectionName="test" connectionPassword="test"
userTable="users" userNameCol="user_name"
userCredCol="user_pass"
userRoleTable="user_roles" roleNameCol="role_name" />
8.3.5.9、被嵌套类 valve
Valve 中文意思是阀门,类似于过滤器,它可以工作于 Engine 和 Host/Context 之间、Host 和 Context 之间以及 Context 和 Web 应用程序的某资源之间。一个容器内可以建立多个 Valve,而且 Valve 定义的次序也决定了它们生效的次序。
有多种不同的 Valve:
- AccessLogValve:访问日志 Valve;
- ExtendedAccessValve:扩展功能的访问日志 Valve;
- JDBCAccessLogValve:通过 JDBC 将访问日志信息发送到数据库中;
- RequestDumperValve:请求转储 Valve;
- RemoteAddrValve:基于远程地址的访问控制;
- RemoteHostValve:基于远程主机名称的访问控制;
- SemaphoreValve:用于控制 Tomcat 主机上任何容器上的并发访问数量;
- ReplicationValve:专用于 Tomcat 集群架构中,可以在某个请求的 session 信息发生更改时触发 session 数据在各节点间进行复制;
- SingleSignOn:在认证用户时将两个或多个需要对用户进行认证的 webapp 连接在一起,即一次认证即可访问所有连接在一起的 webapp;
- ClusterSingleSingOn:对 SingleSignOn 的扩展,专用于 Tomcat 集群当中,需要结合 ClusterSingleSignOnListener 进行工作;
其中 RemoteHostValve 和 RemoteAddrValve 可以分别用来实现基于主机名称和基于 IP 地址的访问控制,控制本身可以通过 allow 或 deny 来进行定义,这有点类似于 Apache 的访问控制功能。如下面的 Valve 实现了仅允许本机访问 /probe:
<Context privileged="true" path="/probe" docBase="probe">
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.0\.0\.1"/>
</Context>
其中相关属性定义有:
- className:在对应位置的后缀上加上
".valves.RemoteHostValve"
或".valves.RemoteAddrValve"
; - allow:以逗号分开的允许访问的 IP 地址列表,支持正则,点号
.
用于 IP 地址时需要转义;仅定义 allow 项时,非明确 allow 的地址均被 deny; - deny:以逗号分开的禁止访问的 IP 地址列表,支持正则;使用方式同 allow;仅定义 deny 项时,非明确 deny 的地址均被 allow;
另外一个常用的 Valve 为 AccessLogValve,定义方式大致如下:
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
- 其中 prefix 和 suffix 表示日志文件的前缀名称和后缀名称。pattern 表示记录日志时的信息和格式。
8.4、tomcat 图形管理和身份认证
tomcat 和大多数服务程序的管理不一样,tomcat 更适合使用图形管理界面进行管理,例如在不停止 tomcat 的情况下动态部署新的 webapp 或重新加载 webapp。如果不使用图形管理工具,tomcat 虽然也可以配置自动部署(autoDeploy="true"),但我们却无法掌握它重新部署的时刻。
图形管理工具的官方手册:Manager App HOW-TO。
直接在浏览器中输入 tomcat 所在机器的 ip 地址及其 connector 监听端口,即可进入 tomcat 的图形管理界面,也是欢迎界面。这个欢迎页面是 tomcat engine 中的默认 Host 组件 localhost 提供的页面,该资源的路径为 $catalina_home/webapps/ROOT/index.jsp
。
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
这 3 个按钮对应的是 tomcat 默认就安装好的 3 个管理工具:状态查看工具、webapp 管理工具和虚拟主机管理工具。其中前 2 个工具都由名为 manager 的 webapp 提供,第三个工具由名为 host-manager 的 webapp 提供,因此后文将认为只提供了两个管理程序:manager 和 host-manager。它们的路径都在 webapps 目录下。
[root@fname1 tomcat]# ls webapps/
charman docs examples host-manager manager ROOT
[root@fname1 tomcat]#
点击这 3 个按钮,分别可以进入对应的管理界面。但第一次点击,会出现 "403 Access denied" 错误,并提示要去配置 conf/tomcat-users.xml,添加相应权限的角色。
对于这两个管理程序而言,总共有以下几种预定义角色:其中后两项角色是上一图中 "Host Manager" 需要的角色。
- manager-gui - allows access to the HTML GUI and the status pages
- manager-script - allows access to the text interface and the status pages
- manager-jmx - allows access to the JMX proxy and the status pages
- manager-status - allows access to the status pages only
- admin-gui - allows access to the HTML GUI
- admin-script - allows access to the text interface
对于使用 manager-gui 角色的用户,强烈建议不要再为其赋予 manager-script 和 manager-jmx 角色。
这些角色名称 rolename 已经预定义在各管理程序内的 web.xml 中:
[root@fname1 tomcat]# grep 'role-name' webapps/manager/WEB-INF/web.xml
<role-name>manager-gui</role-name>
<role-name>manager-script</role-name>
<role-name>manager-jmx</role-name>
<role-name>manager-gui</role-name>
<role-name>manager-script</role-name>
<role-name>manager-jmx</role-name>
<role-name>manager-status</role-name>
<role-name>manager-gui</role-name>
<role-name>manager-script</role-name>
<role-name>manager-jmx</role-name>
<role-name>manager-status</role-name>
[root@fname1 tomcat]# grep 'role-name' webapps/host-manager/WEB-INF/web.xml
<role-name>admin-script</role-name>
<role-name>admin-gui</role-name>
<role-name>admin-gui</role-name>
<role-name>admin-script</role-name>
[root@fname1 tomcat]#
因此,在各管理程序的身份验证文件中可以直接引用这几个名称。那么如何配置身份验证,使得只有通过验证的用户才能使用这些管理工具?根据前面的错误页面提示,只需在 $catalina_home/conf/tomcat-user.xml
中配置即可。
例如,使用预定义好的角色进行配置:
[root@fname1 tomcat]# pwd
/usr/local/tomcat
[root@fname1 tomcat]# cat conf/tomcat-users.xml
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<user username="brinnatt" password="welcome" roles="manager-gui,admin-gui"/>
</tomcat-users>
[root@fname1 tomcat]#
再重启 tomcat 即可。但是,这样的配置在 tomcat 7 版本能成功,在 tomcat8 上不会成功。查看 catalina.home下的 .RUNING 文件,提示如下内容:
For example, the standard manager web application can be kept in
CATALINA_HOME/webapps/manager and loaded into CATALINA_BASE by using
the following trick:
* Copy the CATALINA_HOME/webapps/manager/META-INF/context.xml
file as CATALINA_BASE/conf/Catalina/localhost/manager.xml
* Add docBase attribute as shown below.
The file will look like the following:
<?xml version="1.0" encoding="UTF-8"?>
<Context docBase="${catalina.home}/webapps/manager"
antiResourceLocking="false" privileged="true" >
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.0\.0\.1" />
</Context>
- 它要求我们将 CATALINA_HOME/webapps/manager/META-INF/context.xml 复制到 CATALINA_BASE/conf/Catalina/localhost/manager.xml,并修改其内容。
- 既然知道了操作过程,所以在 tomcat 8 中也就不用去复制了,这里直接创建文件并向里面写入内容即可,注意文件名一定不能错误。
[root@fname1 tomcat]# mkdir -p conf/Catalina/localhost
[root@fname1 tomcat]# cat conf/Catalina/localhost/manager.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context docBase="${catalina.home}/webapps/manager"
antiResourceLocking="false" privileged="true" >
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="^.*$" />
</Context>
[root@fname1 tomcat]#
- 此处 allow 定义的是允许所有主机访问此管理状态页面。还可以使用
allow="127\.0\.0\.1"
定义只有本机才能访问,allow="192\.168\.100.*"
定义只有 192.168.100 网段的主机才能访问。
定义了 manager.xml 文件后,也仅能访问前两个页面,因为前两个页面是 manager 程序提供的,访问时需要在 uri 中使用 /manager,或直接点击上面的按钮。以下是管理 webapp 的图形管理工具页面,从图中可以看出,可以轻松管理某个 webapp 的启动、停止、部署、重新部署、部署本地项目等。
第三个页面是管理虚拟主机的图形页面,访问时的 uri 部分是 /host-manager。但要使用它,也需要将 context.xml 复制到特定路径下,并修改一点内容。如下:
[root@fname1 tomcat]# cp conf/Catalina/localhost/manager.xml conf/Catalina/localhost/host-manager.xml
[root@fname1 tomcat]# sed -i s/manager/host-manager/ conf/Catalina/localhost/host-manager.xml
下图是虚拟主机的管理页面:
以上是将文件 manager.xml 或 host-manager.xml 复制到 CATALINA_BASE/conf/Catalina/localhost/ 下,这是管理所有虚拟主机,如果想要单独管理某虚拟主机,则可以将文件按照 $CATALINA_BASE/conf/[enginename]/[hostname]
路径进行复制。