Web应用服务器Tomcat

Web应用服务器Tomcat

微信搜索 zze_coding 或扫描 👉 二维码关注我的微信公众号获取更多资源推送:

1. 介绍

1.1. Tomcat 是什么

Tomcat 是一个开源的 Java Web 应用服务器,实现了 Java EE(Java Platform Enterprise Edition)的部分技术规范,比如 Java Servlet、Java Server Page、JSTL、Java WebSocket。Java EE 是 Sun 公 司为企业级应用推出的标准平台,定义了一系列用于企业级开发的技术规范,除了上述的之外,还有 EJB、Java Mail、JPA、JTA、JMS 等,而这些都依赖具体容器的实现。

Tomcat 不是一个完整意义上的 Jave EE 服务器,它甚至都没有提供对哪怕是一个主要 Java EE API 的实现;但由于遵守 apache 开源协议,Tomcat 却又为众多的 Java 应用程序服务器嵌入自己的产品中构建商业的 Java 应用程序服务器,如 JBoss 和 JOnAS。

尽管 Tomcat 对 Jave EE API 的实现并不完整,然而很企业也在渐渐抛弃使用传统的 Java EE 技术(如 EJB)转而采用一些开源组件来构建复杂的应用。这些开源组件如 SpringMVC、Spring 和 MyBatis,而 Tomcat 能够对这些组件实现完美的支持。

除了 Tomcat 外,还有其它可应用于 Java 语言开发的程序的应用服务器,下面列出部分:

服务器是否开源是否收费
Tomcat
Jetty
Jboss/WildFly
GlassFish
WebSphere
WebLogic

上图对比了 Java EE 容器的实现情况,Tomcat 和 Jetty 都只提供了 Java Web 容器必需的 Servlet 和 JSP 规范,开发者要想实现其他的功能,需要自己依赖其他开源实现。

Glassfish 是由 SUN 公司推出,Java EE 最新规范出来之后,首先会在 Glassfish 上进行实现,所以是研究 Java EE 最新技术的首选。

最常见的情况是使用 Tomcat 作为 Java Web 服务器,使用 Spring 提供的开箱即用的强大的功能,并依赖其他开源库来完成负责的业务功能实现。

Tomcat 官方站点:https://tomcat.apache.org/

1.2. Tomcat 中的 Session

由于HTTP 是一种无状态的协议,在用户的一次连接请求响应完成后,服务器端将无法识别在后续的连接请求中再次识别此用户,交将其所有请求关联起来。

为了解决这个问题,Java Container 通过一个临时的 Cookie 来为此用户保存一个标识,此标识即所谓的用户 Session 标识。

1.3. JIT 技术

当 JSP 在第一次调用之后,JSP 会被编译成一个 Servlet 类,在后续的操作中则可以直接使用此类,从而避免了对每一次调用的都要重新分析和编译。
因此,类似 Servlet,JSP 的执行需要在 Container 中完成。JSP 的 Container 跟 Servlet 的 Container 基本相同,但在 JSP 执行之前,需要一些额外的步骤如与 Servlet 代码建立会话等。

Tomcat 包含了一个叫做 Catalina 的 Servlet Sontainer(执行 Servlet 和编译过的 JSP)和一个 JSP 编译器(Jasper)。事实上,一个包含了 JSP 编译器和 Servlet 容器的应用程序组合就可被称作 Java Web 容器。

JSP 和 Servlet 的最大区别在于,Servlet 通常需要事先编译好,而 JSP 则并非必须事先编译。这意味着 Servlet 通常放置于私有资源区域,而 JSP 则通常以嵌入代码的方式包含于 HTML 页面文件中,这些 HTML 文件通常放置在公开资源区域。

1.4. Tomcat 的组成

Tomcat 的组成如下图:

image.png

  • Server:Tomcat 的一个实例,通常一个 JVM 只能包含一个 Tomcat 实例;因此,一台物理服务器上可以在启动多个 JVM 的情况下在每一个 JVM 中启动一个 Tomcat 实例,每个实例分属于一个独立的管理端口。这是一个顶级组件;
  • Service:一个服务组件通常包含一个引擎和与此引擎相关联的一个或多个连接器。给服务命名可以方便管理员在日志文件中识别不同服务产生的日志。一个 Server 可以包含多个 Service 组件,但通常情下只为一个 Service 指派一个 Server;
  • Connector:负责连接客户端(可以是浏览器或 Web 服务器)请求至 Servlet 容器内的 Web 应用程序,通常指的是接收客户发来请求的位置及服务器端分配的端口。默认端口通常是 HTTP 协议的 8080,管理员也可以根据自己的需要改变此端口。一个引擎可以配置多个连接器,但这些连接器必须使用不同的端口。默认的连接器是基于 HTTP/1.1 的 Coyote。同时,Tomcat 也支持 AJP、JServ 和 JK2 连接器;
  • Container:Catalina,Servlet 容器,内部有多层容器组成,用于管理 Servlet 生命周期,调用 Servlet 相关方法。
  • Loader:封装了 Java ClassLoader,用于 Container 加载类文件;
  • Realm:Tomcat 中为 web 应用程序提供访问认证和角色管理的机制;
  • JMX:Java SE 中定义技术规范,是一个为应用程序、设备、系统等植入管理功能的框架,通过 JMX 可以远程监控 Tomcat 的运行状态;
  • Jasper:Tomcat 的 Jsp 解析引擎,用于将 Jsp 转换成 Java 文件,并编译成 class 文件。;
  • Session:负责管理和创建 Session,以及 Session 的持久化(可自定义),支持 session 的集群;
  • Pipeline:在容器中充当管道的作用,管道中可以设置各种 valve(阀门),请求和响应在经由管 道中各个阀门处理,提供了一种灵活可配置的处理请求和响应的机制;
  • Naming:命名服务,JNDI, Java 命名和目录接口,是一组在 Java 应用中访问命名和目录服务的 API。命名服务将名称和对象联系起来,使得我们可以用名称访问对象,目录服务也是一种命名 服务,对象不但有名称,还有属性。Tomcat 中可以使用 JNDI 定义数据源、配置信息,用于开发与部署的分离;

Container 的组成如下图:

image.png

  • Engine:引擎通是指处理请求的 Servlet 引擎组件,即 Catalina Servlet 引擎,它检查每一个请求的 HTTP 首部信息以辨别此请求应该发往哪个 Host或 Context,并将请求处理后的结果返回的相应的客户端。严格意义上来说,容器不必非得通过引擎来实现,它也可以是只是一个容器。如果 Tomcat 被配置成为独立服务器,默认引擎就是已经定义好的引擎。而如果 Tomcat 被配置为 Apache Web 服务器的提供 Servlet 功能的后端,默认引擎将被忽略,因为 Web 服务器自身就能确定将用户请求发往何处。一个引擎可以包含多个 Host 组件;
  • Host:主机组件类似于 Apache 中的虚拟主机,但在 Tomcat 中只支持基于 FQDN 的“虚拟主机”,一个引擎至少要包含一个主机组件;
  • Context:Context 组件是最内层次的组件,它表示 Web 应用程序本身。配置一个 Context 最主要的是指定 Web 应用程序的根目录,以便 Servlet 容器能够将用户请求发往正确的位置。Context 组件也可包含自定义的错误页,以实现在用户访问发生错误时提供友好的提示信息;
  • Wrapper:最底层的容器,是对 Servlet 的封装,负责 Servlet 实例的创建、执行和销毁。一个Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。 Wrapper 的实现类是 StandardWrapperStandardWrapper 还实现了拥有一个 Servlet 初始化信息的 ServletConfig,由此看出 StandardWrapper 将直接和 Servlet 的各种信息打交道;

1.5. 连接器协议

Tomcat 的 Web 服务器连接器支持两种协议:AJP 和 HTTP,它们均定义了以二进制格式在 Web 服务器和 Tomcat 之间进行数据传输,并提供相应的控制命令。

AJP(Apache JServ Protocol)协议:目前正在使用的 AJP 协议的版本是通过 JK 和 JK2 连接器提供支持的 AJP13,它基于二进制的格式在 Web 服务器和 Tomcat 之间传输数据,而此前的版本 AJP10 和 AJP11 则使用文本格式传输数据。

2. 使用

2.1. 下载

1、因 Tomcat 本身是使用 Java 研发,所以它的运行依赖于 Java,所以需要先安装 JDK,点击下载 jdk-8u202-linux-x64.rpm
2、点击下载 apache-tomcat-8.0.53.tar.gz

当然,上述的 JDK 和 Tomcat 你也都可以通过官网下载。

2.2. 安装及启动

1、安装 JDK 并设置 JAVA_HOME 环境变量:

$ rpm -ivh jdk-8u202-linux-x64.rpm
$ echo 'export JAVA_HOME=/usr/java/latest' > /etc/profile.d/java.sh && . /etc/profile.d/java.sh

rpm 包方式安装默认会将 JDK 的 bin 目录下的可执行文件映射到 /usr/bin/ 下,所以这里不用配置 PATH 变量。

2、解压安装 Tomcat:

# 解压
$ tar xf apache-tomcat-8.0.53.tar.gz -C /usr/local/
$ cd /usr/local/
# 创建软链接
$ ln -s apache-tomcat-8.0.53/ tomcat
# 配置 Tomcat 的环境变量
$ vim /etc/profile.d/tomcat.sh
export CATALINA_HOME=/usr/local/tomcat
export PATH=$CATALINA_HOME/bin:$PATH
# 重读变量
$ . /etc/profile.d/tomcat.sh

3、JDK 和 Tomcat 按上述配置成功后,可通过下面 Tomcat 提供的命令查看到 JDK 版本和 Tomcat 的版本:

$ 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/java/latest
Using CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Server version: Apache Tomcat/8.0.53
Server built:   Jun 29 2018 14:42:45 UTC
Server number:  8.0.53.0
OS Name:        Linux
OS Version:     3.10.0-862.el7.x86_64
Architecture:   amd64
JVM Version:    1.8.0_221-b11
JVM Vendor:     Oracle Corporation

4、启动:

$ 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/java/latest
Using CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Tomcat started.

5、Tomcat 默认监听 8080 端口,检查是否已监听:

$ ss -tanl | grep 8080
LISTEN     0      100         :::8080                    :::*  

6、使用浏览器访问 Tomcat 主机的 8080 端口,如下:

2.3. 目录说明

Tomcat 安装包解压后的目录如下:

$ ll /usr/local/tomcat/ | grep '^d'
# 脚本及启动时用到的类
drwxr-x--- 2 root root  4096 Apr 23 21:05 bin
# 配置文件
drwx------ 3 root root   254 Apr 23 21:18 conf
# 类库
drwxr-x--- 2 root root  4096 Apr 23 21:05 lib
# 日志
drwxr-x--- 2 root root   197 Apr 23 21:18 logs
# 临时目录
drwxr-x--- 2 root root    30 Apr 23 21:05 temp
# 应用程序默认的部署目录
drwxr-x--- 7 root root    81 Apr  3 22:07 webapps
# 工作目录
drwxr-x--- 3 root root    22 Apr 23 21:18 work

其中配置文件的目录的下的主要文件说明如下:

$ ll /usr/local/tomcat/conf/
drwxr-x--- 3 root root     23 Apr 23 21:18 Catalina
# Java 相关的安全策略配置文件,在系统资源级别上提供访问控制的能力,当使用 -security 选项启动 tomcat 实例时会读取此配置文件来实现其安全运行策略,
-rw------- 1 root root  13446 Apr  3 22:09 catalina.policy
# Tomcat 内部 package 的定义及访问相关的控制,也包括对通过类装载器装载的内容的控制,Tomcat 6 在启动时会事先读取此文件的相关设置,还可用于设定 Java 属性、类加载器路径、以及一些 JVM 性能相关的调优参数等;
-rw------- 1 root root   7661 Apr  3 22:09 catalina.properties
# 每个 webapp 都可以有专用的配置文件,这些配置文件通常位于 webapp 应用程序目录下的 WEB-INF 目录中,用于定义会话管理器、JDBC 等;conf/context.xml 是为各 webapp 提供默认配置;
-rw------- 1 root root   1338 Apr  3 22:09 context.xml
-rw------- 1 root root   1149 Apr  3 22:09 jaspic-providers.xml
-rw------- 1 root root   2313 Apr  3 22:09 jaspic-providers.xsd
# 日志相关的配置信息;
-rw------- 1 root root   3916 Apr  3 22:09 logging.properties
# 主配置文件,包含 Service、Connector、Engine、Realm、Valve、Hosts 主组件的相关配置信息;
-rw------- 1 root root   7588 Apr  3 22:09 server.xml
# Realm 认证时用到的相关角色、用户和密码等信息,Tomcat 自带的 manager 默认情况下会用到此文件,在 Tomcat中添加/删除用户,为用户指定角色等将通过编辑此文件实现;
-rw------- 1 root root   2164 Apr  3 22:09 tomcat-users.xml
-rw------- 1 root root   2633 Apr  3 22:09 tomcat-users.xsd
# 遵循 Servlet 规范标准的配置文件,用于配置 Servlet,并为所有的 Web 应用程序提供包括 MIME 映射等默认配置信息;
-rw------- 1 root root 171756 Apr  3 22:09 web.xml

2.4. catalina.sh 的使用

通过上述内容我们已经知道可以通过执行 catalina.sh start 来启动 Tomcat 了,它可使用的选项可通过 --help 选项来查看,如下:

$ catalina.sh --help
Using CATALINA_BASE:   /usr/local/tomcat
Using CATALINA_HOME:   /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME:        /usr/java/latest
Using CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Usage: catalina.sh ( commands ... )
commands:
  debug             以 debug 模式启动
  debug -security   以 debug 模式附带安全管理器启动
  jpda start        在 JPDA debugger 下启动
  run               前台启动
  run -security     前台以附带安全管理器的形式启动
  start             后台启动
  start -security   后台以附带安全管理器的形式启动
  stop              停止服务,默认会延迟 5 秒执行
  stop n            停止服务,延迟 n 秒执行
  stop -force       停止服务,默认会延迟 5 秒执行,如果停止失败则会使用 kill 强行杀进程
  stop n -force     停止服务,延迟 n 秒执行,如果停止失败则会使用 kill 强行杀进程
  configtest        检查 server.xml 配置是否有语法错误
  version           查看版本信息
Note: Waiting for the process to end and use of the -force option require that $CATALINA_PID is defined

2.5. WebApp 的组织结构

对于一个 Java Web 应用程序而言,其通常由 Servlets、JSP 和其它文件等共同组成。这些文件通常被打包成 WAR(Web Application Archive)格式,并以 .war 作为打包后的文件扩展名。
而 Servlet 规范则定义了在 WAR 内部组织这些文件的标准目录结构。其目录和功用如下:

# 下面以应用 `app1` 部署在 `webapps` 下为例进行说明:
/usr/local/tomcat/webapps/app1/
	/: Web 应用程序的根目录,所有可被公开访问的文件均放置于此处,如 HTML、JSP 和图片文件等;
	WEB-INF/:此目录为私有资源目录,其内部的所有文件和子目录均不能被公开访问;包含着此 Web 应用程序的配置文件 web.xml(程序结构描述符文件)通常放置于此目录;
	META-INF/:当前 webapp 的私有资源目录,通常存放当前 webapp 自用的 context.xml;
	classes/: 当前 Web 应用程序的类文件的存放目录;
	lib/: 可被打包为 JAR 格式的类文件通常放置于此目录;
	index.jsp:webapp 的主页

Java 的 Web 应用有专门的归档格式,它们可根据不同的后缀名进行区分:

  • .war:webapp;
  • .jar:EJB 的类;
  • .rar:资源适配器;
  • .ear:企业级应用程序;

2.6. 创建一个测试应用程序

1、创建 webapp 特有的目录结构。

# 进入到 webapps 目录
$ cd /usr/local/tomcat/webapps/
$ mkdir myapp/{lib,classes,WEB-INF,META-INF} -p

2、编写测试使用的 JSP 页面:

$ vim myapp/index.jsp
<%@ page language="java" %>
<%@ page import="java.util.*" %>
<html>
        <head>
                <title> JSP Test Page </title>
        </head>
        <body>
                <% out.println("Hello World. "); %>
        </body>
</html>

3、启动 Tomcat,使用浏览器访问 /myapp,效果如下:

image.png

默认情况下在 Tomcat 中的根应用程序的目录需要在 webapps 下以 ROOT 命名,非根应用程序则需要通过 /<应用目录名> 访问。
JSP 的本质其实就是 Servlet,而 Servlet 其实就是一个 Java 类,我们可以在 Tomcat 的工作目录查看到上述测试 JSP 页转换后的 Java 类文件和字节码文件,如下:

# Catalina:引擎;localhost:虚拟主机;myapp:应用名称;
$ tree work/Catalina/localhost/myapp/
work/Catalina/localhost/myapp/
└── org
    └── apache
        └── jsp
            ├── index_jsp.class
            └── index_jsp.java

3 directories, 2 files

2.7. 部署 WebApp 相关操作

2.7.1. 部署

部署(deploy)其实就是将 WebApp 的源文件放置于目标目录,并配置 Tomcat 服务器能够基于 context.xml 文件中定义的路径来访问 WebApp,然后将其特有类通过 ClassLoader 装载至 Tomcat。
部署 WebApp 有两种方式:

  1. 自动部署(auto deploy);
  2. 手动部署;

而手动部署又可分为两类:

  1. 冷部署:把 WebApp 复制到指定位置,而后才启动 Tomcat;
  2. 热部署:借助部署工具在不停止 Tomcat 的前提下进行部署操作,如 Tomcat 自带的 manager、ant 脚本、tcd(tomcat client deployer)等;

2.7.2. 反部署

有部署就有反部署(undeploy),反部署指的就是停止 WebApp,并从 Tomcat 实例拆除其部分文件和部署名。

2.7.3. 停止

还可对 WebApp 执行 stop 操作,即让 WebApp 不再向用户提供服务。

2.7.4. 启动

当然,也可对处于停止状态的 WebApp 执行 start 操作让其恢复启动状态。

2.7.5. 重新部署

重新部署(redeploy)其实就是对 WebApp 执行反部署后又执行部署操作。

2.8. Tomcat 自带的应用程序

直接访问 Tomcat 的根路径,响应页面如下:

image.png

可以看到红框中的三个按钮,这其实就是 Tomcat 自带的应用程序的入口,这三个应用程序的功用如下:

  • Server Status:如其名,用来查看 Tomcat 服务器相关状态信息;
  • Manager App:WebApp 管理工具;
  • Host Manager:Virtual Hosts 管理工具;

我们随便点击一个入口按钮,会发现返回了如下访问拒绝页面:

image.png

根据其提示信息可以看到,我们需要在 conf/tomcat-users.xml 中配置一个拥有访问 Web 页面权限的用户,再通过这个用户才能正常访问其提供的应用程序。

修改 conf/tomcat-users.xml 如下:

$ vim 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">
  
  <role rolename="manager-gui"/>
  <!-- 赋予 tomcat manager-gui 角色 -->
  <user username="tomcat" password="tomcat" roles="manager-gui"/>
</tomcat-users>

重启生效:

$ catalina.sh stop && catalina.sh start

接下就可以通过用户名 tomcat、密码 tomcat 通过认证访问这三个应用程序了,下面对这几个程序进行一一说明。

2.8.1. Server Status

image.png

如其名,通过 Server Status 可以看到很详细的 Tomcat 状态以及 JVM 的状态,其中包括 JVM 的新生代(Eden 区和存活区)及老年代内存使用状态以及缓存相关等状态信息。

2.8.2. Manager App

image.png

可以看到 Tomcat 自带的应用程序以及在上面我们做测试用的部署的测试程序 myapp,我们可以在这个页面对这些程序进行管理,比如启动、停止、部署、反部署 和重新部署。

2.8.3. Host Manager

image.png

如上 Host Manager 就是用来管理 Tomcat 中的虚拟主机,可以通过它完成查看虚拟主机列表、新添加虚拟主机等操作。

2.9. 主配置文件详解

<?xml version='1.0' encoding='utf-8'?>
<!-- 
    这会让 Tomcat 启动一个 Server 实例(即一个 JVM),它监听在 8005 端口以接收 SHUTDOWN 命令。
    各 Server 的定义不能使用同一个端口,这意味着如果在同一个物理机上启动了多个 Server实例,必须配置它们使用不同的端口。
    这个端口的定义用于为管理员提供一个关闭此实例的便捷途径,因此,管理员可以直接 telnet 至此端口使用 SHUTDOWN 命令关闭此实例。
    不过,基于安全角度的考虑,这通常不允许远程进行。 
    相关属性:
        className: 用于实现此 Server 容器的完全限定类的名称,默认为 org.apache.catalina.core.StandardServer;
        port: 接收 shutdown 指令的端口,默认仅允许通过本机访问,默认为 8005;
        shutdown:发往此 Server 用于实现关闭 Tomcat 实例的命令字符串,默认为 SHUTDOWN;
-->
<Server port="8005" shutdown="SHUTDOWN">
    <!-- Listener 用于创建和配置 LifecycleListener 对象,而 LifecycleListener 通常被开发人员用来创建和删除容器。 -->
    <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" />

    <!-- 
        应用于整个服务器的 JNDI 映射,此可以避免每个 Web 应用程序都需要在各自的 web.xml 创建,这在 Web 应用程序以 WAR 的形式存在时尤为有用。它通常可以包含三个子元素:
            1) Environment;
            2) Resource;
            3) ResourceEnvRef;
     -->
    <GlobalNamingResources>
        <!-- 定义 Tomcat 用户认证管理基于的数据库资源 -->
        <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 主要用于关联一个引擎和与此引擎相关的连接器,每个连接器通过一个特定的端口和协议接收入站请求交将其转发至关联的引擎进行处理。
        因此,Service 要包含一个引擎、一个或多个连接器。 
        这定义了一个名为 Catalina 的 Service,此名字也会在产生相关的日志信息时记录在日志文件当中。
        相关属性:
            className:用于实现 Service 的类名,一般都是 org.apache.catalina.core.StandardService。
            name:此服务的名称,默认为 Catalina;
    -->
    <Service name="Catalina">
        <!-- 定义 Tomcat 的线程池 -->
        <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
            maxThreads="150" minSpareThreads="4"/>

        <!-- 
            进入 Tomcat 的请求可以根据 Tomcat 的工作模式分为如下两类:
                Tomcat 作为应用程序服务器,请求来自于前端的代理 Web 服务器,这可能是 Apache, IIS, Nginx 等;
                Tomcat 作为独立服务器,请求来自于 Web 浏览器;

            Tomcat 应该考虑工作情形并为相应情形下的请求分别定义好需要的连接器才能正确接收来自于客户端的请求。一个引擎可以有一个或多个连接器,以适应多种请求方式。

            定义连接器可以使用多种属性,有些属性也只适用于某特定的连接器类型。一般说来,常见于server.xml中的连接器类型通常有4种:
                1) HTTP连接器
                2) SSL连接器
                3) AJP 1.3连接器
            定义连接器时可以配置的属性非常多,但通常定义 HTTP 连接器时必须定义的属性只有 port,定义 AJP 连接器时必须定义的属性只有 protocol,因为默认的协议为 HTTP。以下为常用属性的说明:
                1) address:指定连接器监听的地址,默认为所有地址,即 0.0.0.0;
                2) maxThreads:支持的最大并发连接数,默认为 200;
                3) port:监听的端口,默认为 0;
                4) protocol:连接器使用的协议,默认为 HTTP/1.1,定义AJP协议时通常为 AJP/1.3;
                5) redirectPort:如果某连接器支持的协议是 HTTP,当接收客户端发来的 HTTPS 请求时,则转发至此属性定义的端口;
                6) connectionTimeout:等待客户端发送请求的超时时间,单位为毫秒,默认为 60000,即 1 分钟;
                7) enableLookups:是否通过 request.getRemoteHost() 进行DNS查询以获取客户端的主机名;默认为 true;
                8) acceptCount:设置等待队列的最大长度;通常在 Tomcat 所有处理线程均处于繁忙状态时,新发来的请求将被放置于等待队列中;
            如下定义的是 HTTP 连接器:
         -->
        <Connector port="8080" protocol="HTTP/1.1"
                connectionTimeout="20000"
                redirectPort="8443" />
        
        <!-- 在连接器上使用指定的线程池 -->
        <Connector executor="tomcatThreadPool"
                port="8080" protocol="HTTP/1.1"
                connectionTimeout="20000"
                redirectPort="8443" />
        
        <!-- 下面是一个定义了多个属性的SSL连接器: -->
        <Connector port="8443"
            maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
            enableLookups="false" acceptCount="100" debug="0" scheme="https" secure="true"
            clientAuth="false" sslProtocol="TLS" />

        <!-- 定义 AJP 连接器 -->
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

        <!-- 
            Engine 是 Servlet 处理器的一个实例,即 Servlet 引擎。Engine 需要 defaultHost 属性来为其定义一个接收所有发往非明确定义虚拟主机的请求的 host 组件。
            Engine 容器中可以包含 Realm、Host、Listener 和 Valve 子容器。
            相关属性:
                1) defaultHost:Tomcat 支持基于 FQDN 的虚拟主机,这些虚拟主机可以通过在 Engine 容器中定义多个不同的 Host 组件来实现;
                    但如果此引擎的连接器收到一个发往非非明确定义虚拟主机的请求时则需要将此请求发往一个默认的虚拟主机进行处理,因此,在 Engine 中定义的多个虚拟主机的主机名称中至少要有一个跟 defaultHost 定义的主机名称同名;
                2) name:Engine组件的名称,用于日志和错误信息记录时区别不同的引擎;
        -->
        <Engine name="Catalina" defaultHost="localhost">
            <!-- 
                一个 Realm 表示一个安全上下文,它是一个授权访问某个给定 Context 的用户列表和某用户所允许切换的角色相关定义的列表。因此,Realm 就像是一个用户和组相关的数据库。
                定义 Realm 时唯一必须要提供的属性是 classname,它是 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.LockOutRealm">
                <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
                    resourceName="UserDatabase"/>
            </Realm>

            <!-- 下面是一个使用 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" />
            <!-- 
                位于 Engine 容器中用于接收请求并进行相应处理的主机或虚拟主机。
                常用属性说明:
                    1) appBase:此 Host 的 webapps 目录,即存放非归档的 web 应用程序的目录或归档后的 WAR 文件的目录路径,可以使用基于 $CATALINA_HOME 的相对路径;
                    2) autoDeploy:在 Tomcat 处于运行状态时放置于 appBase 目录中的应用程序文件是否自动进行 deploy,默认为 true;
                    3) unpackWars:在启用此 webapps 时是否对 WAR 格式的归档文件先进行展开,默认为 true;
             -->
            <Host name="localhost"  appBase="webapps"
                unpackWARs="true" autoDeploy="true">
                <!-- 
                    Valve 类似于过滤器,它可以工作于 Engine 和 Host/Context 之间、Host 和 Context之间以及 Context 和 Web 应用程序的某资源之间。一个容器内可以建立多个 Valve,而且 Valve 定义的次序也决定了它们生效的次序。
                        AccessLogValve:访问日志 Valve;
                        ExtendedAccessValve:扩展功能的访问日志 Valve;
                        JDBCAccessLogValve:通过 JDBC 将访问日志信息发送到数据库中;
                        RequestDumperValve:请求转储 Valve;
                        RemoteAddrValve:基于远程地址的访问控制;
                        RemoteHostValve:基于远程主机名称的访问控制;
                        SemaphoreValve:用于控制 Tomcat 主机上任何容器上的并发访问数量;
                        JvmRouteBinderValve:在配置多个 Tomcat 为以 Apache 通过 mod_proxy 或 mod_jk 作为前端的集群架构中,当期望停止某节点时,可以通过此 Valve 将用记请求定向至备用节点;使用此 Valve,必须使用 JvmRouteSessionIDBinderListener;
                        ReplicationValve:专用于 Tomcat 集群架构中,可以在某个请求的 session 信息发生更改时触发 session 数据在各节点间进行复制;
                        SingleSignOn:将两个或多个需要对用户进行认证 webapp 在认证用户时连接在一起,即一次认证即可访问所有连接在一起的 webapp;
                        ClusterSingleSingOn:对 SingleSignOn 的扩展,专用于 Tomcat 集群当中,需要结合 ClusterSingleSignOnListener 进行工作;
                 -->
                <!-- 定义访问日志的格式,directory 用来指定日志目录,它的值也是基于 CATALINA_HOME 环境变量的相对路径 -->
                <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" />

                <!-- 
                    RemoteHostValve 和 RemoteAddrValve 可以分别用来实现基于主机名称和基于 IP 地址的访问控制,控制本身可以通过 allow 或 deny 来进行定义,这有点类似于 Apache 的访问控制功能;
                    相关属性:
                        1) className:相关的 Java 实现的类名,相应于分别应该为 org.apache.catalina.valves.RemoteHostValve 或 org.apache.catalina.valves.RemoteAddrValve;
                        2) allow:以逗号分开的允许访问的 IP 地址列表,支持正则表达式,因此,点号“.”用于 IP 地址时需要转义,仅定义 allow 项时,非明确 allow 的地址均被 deny;
                        3) deny: 以逗号分开的禁止访问的 IP 地址列表,支持正则表达式,使用方式同 allow;
                    如下面的 Valve 则实现了仅允许本机访问 /probe:
                 -->
                <Context path="/probe" docBase="probe">
                    <Valve className="org.apache.catalina.valves.RemoteAddrValve"
                        allow="127\.0\.0\.1"/>
                </Context>
                
                <!--
                    每一个 Context 定义也可以使用一个单独的 XML 文件进行,其文件的目录为 $CATALINA_HOME/conf/<engine name>/<host name>。
                    可以用于 Context 中的 XML 元素有 Loader,Manager,Realm,Resources 和 WatchedResource。 
                    常用的属性定义有:
                        1) docBase:相应的 Web 应用程序的存放位置;也可以使用相对路径,起始路径为此 Context 所属 Host 中 appBase 定义的路径;切记,docBase 的路径名不能与相应的 Host 中 appBase 中定义的路径名有包含关系,比如,如果 appBase 为 deploy,而 docBase 绝不能为 deploy-bbs 类的名字;
                        2) path:相对于 Web 服务器根路径而言的 URI;如果为空 "",则表示为此 WebApp 的根路径;如果 context 定义在一个单独的 XML 文件中,此属性不需要定义;
                        3) reloadable:是否允许重新加载此 Context 相关的 Web 应用程序的类,默认为 false;
                    Context 在某些意义上类似于 apache 中的路径别名,一个 Context 定义用于标识 Tomcat 实例中的一个Web应用程序,如下面的定义: -->
                <Context path="" docBase="/web/">
            </Host>

            <!-- 
                主机别名定义。
                如果一个主机有两个或两个以上的主机名,额外的名称均可以以别名的形式进行定义,如下:
             -->
            <Host name="www.zze.xyz" appBase="webapps" unpackWARs="true">
                <Alias>zze.xyz</Alias>
            </Host>
        </Engine>
  </Service>
</Server>
<!-- 
其它节点说明:
    WatchedResource:
        WatchedResource 可以用于 Context 中监视指定的 WebApp 程序文件的改变,并且能够在监视到文件内容发生改变时重新装载此文件。
    Loader:
        Java 的动态装载功能是其语言功能强大表现之一,Servlet 容器使用此功能在运行时动态装载 Servlet 和它们所依赖的类,Loader 可以用于 Context 中控制 Java 类的加载。
    Manager:
        Manger 对象用于实现 HTTP 会话管理的功能,Tomcat6 中有 5 种 Manger 的实现:
            1) StandardManager:Tomcat6 的默认会话管理器,用于非集群环境中对单个处于运行状态的 Tomcat 实例会话进行管理。当 Tomcat 关闭时,这些会话相关的数据会被写入磁盘上的一个名叫 SESSION.ser 的文件,并在 Tomcat 下次启动时读取此文件;
            2) PersistentManager:当一个会话长时间处于空闲状态时会被写入到 swap 会话对象,这对于内存资源比较吃紧的应用环境来说比较有用;
            3) DeltaManager:用于 Tomcat 集群的会话管理器,它通过将改变了会话数据同步给集群中的其它节点实现会话复制。这种实现会将所有会话的改变同步给集群中的每一个节点,也是在集群环境中用得最多的一种实现方式;
            4) BackupManager:用于 Tomcat 集群的会话管理器,与 DeltaManager 不同的是,某节点会话的改变只会同步给集群中的另一个而非所有节点;
            5) SimpleTcpReplicationManager:Tomcat4 时用到的版本,过于老旧了。
    Stores:
        PersistentManager 必须包含一个 Store 元素以指定将会话数据存储至何处。这通常有两种实现方式:FileStore 和 JDBCStore。
    Resources:
        经常用于实现在 Context 中指定需要装载的但不在 Tomcat 本地磁盘上的应用资源,如 Java 类,HTML 页面,JSP 文件等。
    Cluster:
        专用于配置 Tomcat 集群的元素,可用于 Engine 和 Host 容器中。在用于 Engine 容器中时,Engine 中的所有 Host 均支持集群功能。
        在 Cluster 元素中,需要直接定义一个 Manager 元素,这个 Manager 元素有一个其值为 org.apache.catalina.ha.session.DeltaManager 或 org.apache.catalina.ha.session.BackupManager 的 className 属性。
        同时,Cluster 中还需要分别定义一个 Channel 和 ClusterListener 元素。
            Channel:
                用于 Cluster 中给集群中同一组中的节点定义通信“信道”。Channel 中需要至少定义 Membership、Receiver 和 Sender 三个元素,此外还有一个可选元素 Interceptor。
            Membership:
                用于 Channel 中配置同一通信信道上节点集群组中的成员情况,即监控加入当前集群组中的节点并在各节点间传递心跳信息,而且可以在接收不到某成员的心跳信息时将其从集群节点中移除。
                Tomcat6 中 Membership 的实现是 org.apache.catalina.tribes.membership.McastService。
            Sender:
                用于 Channel 中配置“复制信息”的发送器,实现发送需要同步给其它节点的数据至集群中的其它节点。发送器不需要属性的定义,但可以在其内部定义一个 Transport 元素。
            Transport:
                用于 Sender 内部,配置数据如何发送至集群中的其它节点。Tomcat6 有两种 Transport 的实现:
                    1) PooledMultiSender:基于 Java 阻塞式 IO,可以将一次将多个信息并发发送至其它节点,但一次只能传送给一个节点。
                    2) PooledParallelSener:基于 Java 非阻塞式 IO,即 NIO,可以一次发送多个信息至一个或多个节点。
            Receiver
                用于 Channel 定义某节点如何从其它节点的 Sender 接收复制数据,Tomcat6 中实现的接收方式有两种 BioReceiver 和 NioReceiver。
 -->

2.10. 配置虚拟主机

server.xmlEngine 节下添加如下 Host 节点:

$ vim /usr/local/tomcat/conf/server.xml 
<!-- 定义一个基于域名的虚拟主机,即访问 myapp.zze.xyz 会映射到如下虚拟主机,appBase 指定程序目录 -->
<Host name="myapp.zze.xyz" appBase="/code/myapp/" unpackWARs="true" autoDeploy="true">
    <!-- path 为空则表示请求根路径即可,docBase 为空则表示默认就使用 appBase 指定的目录 -->
    <Context path="" docBase="" reloadable="true" />
    <!-- 单独为该虚拟主机定义日志规则 -->
    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="/code/logs/"
       prefix="myapp.zze.xyz" suffix=".txt"
       pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>

将之前的测试程序文件拷贝到上述定义的 appBase 目录中:

# 创建程序目录和日志目录
$ mkdir /code/{myapp,logs}
# 拷贝
$ cp -r /usr/local/tomcat/webapps/myapp/* /code/myapp/

重启 Tomcat,测试通过 myapp.zze.xyz 域名访问该虚拟主机:

image.png

2.11. 访问控制

RemoteHostValveRemoteAddrValve 可以分别用来实现基于主机名称和基于 IP 地址的访问控制,控制本身可以通过 allowdeny 来进行定义,这有点类似于 Apache 的访问控制功能。

这俩 Valve 是基于正则表达式来对访问的主机名或 IP 地址进行匹配的,下面以上面配置的虚拟主机为例做修改。

如果要控制虚拟主机仅能被本机访问,只需要在 Host 节点下添加如下 Valve 节:

<Valve className="org.apache.catalina.valves.RemoteAddrValve"
                    allow="127\.0\.0\.1"/>

如果仅允许来自 10.0.1.0/24 网段的主机访问,则修改其 allow 属性的值如下:

<Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="10\.0\.1\.[0-9]+" />

2.12. 反向代理 Tomcat

下面演示一下通过 Httpd 反向代理 Tomcat 的请求。

为啥不使用 Nginx 呢?因为这里只是做演示,Nginx 代理 Tomcat 和代理其它 Web 服务器没什么区别,直接 proxy_pass http://server 即可。
而 Httpd 和 Tomcat 同出一家都属于 Apache 旗下,相对来说它们的亲缘性、粘合度比较高,还能基于 ajp 协议进行通信(基于二进制通信,效率更高),所以下面就用 Httpd 反向代理做演示。

安装 httpd:

$ yum install httpd -y

我使用的操作系统是 CentOS 7,httpd 在默认的 base 源中的版本为 2.4.6,如果是 CentOS 6 则默认的 httpd 版本为 2.2,后续的配置方式是不同的。

2.12.1. 基于 HTTP

配置基于 HTTP 协议的反向代理,Httpd 默认就支持,这个就不多说。

配置反向代理的虚拟主机:

$ vim /etc/httpd/conf.d/tomcat.conf   
<VirtualHost *:80>
        ServerName myapp.zze.xyz
        ProxyVia On
        # 关闭正向代理
        ProxyRequests Off
        # 保留主机名转发给 Tomcat,此项必须开启,因为此时的 Tomcat 配置的是基于主机名的虚拟主机
        ProxyPreserveHost On
        <Proxy *>
                Require all granted
        </Proxy>
        ProxyPass / http://10.0.1.200:8080/
        ProxyPassReverse / http://10.0.1.200:8080/
        <Location />
                Require all granted
        </Location>
</VirtualHost>

重启 Httpd 服务,测试浏览器访问:

image.png

2.12.2. 基于 AJP

如果要修改 Httpd 基于 ajp 协议进行代理有两个必要条件,一是 Tomcat 基于 ajp 协议监听了对应地址,二是 Httpd 必须装载对应模块。

对于条件一,Tomcat 默认在 8009 端口就监听了 ajp 协议的请求,其对应连接器在 server.xml 中配置如下:

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

对于条件二,我们需要检查 Httpd 是否装载了 AJP 模块,查看:

$ httpd -M | grep -i 'ajp'
 proxy_ajp_module (shared)

其对应装载配置在 /etc/httpd/conf.modules.d/00-proxy.conf 中。

满足上述条件之后,代理主机就很好配置啦,只需要将 ProxyPass 时的协议改一下就 OK 啦,如下:

$ vim /etc/httpd/conf.d/tomcat.conf   
<VirtualHost *:80>
        ServerName myapp.zze.xyz
        ProxyVia On
        # 关闭正向代理
        ProxyRequests Off
        # 保留主机名转发给 Tomcat,此项必须开启,因为此时的 Tomcat 配置的是基于主机名的虚拟主机
        ProxyPreserveHost On
        <Proxy *>
                Require all granted
        </Proxy>
        ProxyPass / ajp://10.0.1.200:8009/
        ProxyPassReverse / ajp://10.0.1.200:8009/
        <Location />
                Require all granted
        </Location>
</VirtualHost>

重启 Httpd 服务,测试浏览器访问:

image.png

2.13. 基于 AJP 的负载均衡

准备如下主机:

主机名IP描述
proxy10.0.1.200httpd 代理主机
tomcat110.0.1.201tomcat web 主机
tomcat210.0.1.202tomcat web 主机

在 tomcat1 和 tomcat2 两台主机上提供使用 Tomcat 提供 Web 服务,能根据响应区分出是不同主机即可:

$ curl 10.0.1.201:8080
<html>
        <head>
                <title> JSP Test Page - web1</title>
        </head>
        <body>
                Hello World. - web1
        </body>
</html>

$ curl 10.0.1.202:8080
<html>
        <head>
                <title> JSP Test Page - web2</title>
        </head>
        <body>
                Hello World. - web2 
        </body>
</html>

在 proxy 主机安装上 httpd 2.4+,配置如下虚拟主机:

$ vim /etc/httpd/conf.d/tomcat.conf
<proxy balancer://lbcluster1>
    # 如果不使用会话绑定则下面的 jvmRoute 项可省略
    # 需要在对应 Tomcat 的 server.xml 中的 Engine 节需要添加属性 jvmRoute="TomcatA"
    BalancerMember ajp://10.0.1.201:8009 loadfactor=10 route=TomcatA
    # 需要在对应 Tomcat 的 server.xml 中的 Engine 节需要添加属性 jvmRoute="TomcatB"
    BalancerMember ajp://10.0.1.202:8009 loadfactor=10 route=TomcatB
    # 如果需要使用基于 Cookie 的会话绑定,解开下面注释即可
    # ProxySet stickysession=ROUTEID
</proxy>

<VirtualHost *:80>
    ServerName myapp.zze.xyz
    ProxyVia On
    # 关闭正向代理
    ProxyRequests Off 
    ProxyPreserveHost On
    <Proxy *>
        Require all granted
    </Proxy>
    # apache 实现负载均衡的调度方法,默认是 byrequests,即基于权重将统计请求个数进行调度,bytraffic 则执行基于权重的流量计数调度,bybusyness 通过考量每个后端服务器的当前负载进行调度。
    ProxyPass / balancer://lbcluster1/ lbmethod=byrequests
    ProxyPassReverse / balancer://lbcluster1/ lbmethod=byrequests
    <Location />
        Require all granted
    </Location>
</VirtualHost>

测试访问 proxy 主机会发现已经完成了负载均衡。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.zze.xyz/archives/tomcat.html

Buy me a cup of coffee ☕.