历史

起始于SUN的一个Servlet的参考实现项目Java Web Server,作者是James Duncan Davidson,后将项目贡献给了ASF。和ASF现有的项目合并,并开源成为顶级项目,官网http://tomcat.apache.org

Tomcat仅仅实现了Java EE规范中与Servlet. JSP相关的类库,是JavaEE不完整实现。

著名图书出版商O’Reilly约稿该项目成员,Davidson希望使用一个公猫作为封面,但是公猫已经被另一
本书使用,书出版后封面是一只雪豹。

1999年发布初始版本是 Tomcat3.0,实现了 Servlet2.2和sP1.1规范。

Tomcat4x发布时,内建了 Catalina( Servlet容器)和 asper (SP engine)等

商用的有 BM Web Sphere、 Oracle WebLogic(原属于BEA公司)、 Oracle oc4、 Glassfish、 jBoss等。
开源实现有 Tomcat、 Jetty、 Resin。

安装

目录结构

目录 说明
bin 服务启动、停止相关
conf 配置文件
lib 库目录
logs 日志目录
webapps 应用程序,应用部署目录
work jsp编译后的结果文件

配置文件

文件名 说明
server.xml 主配置文件
web.xml 每个webapp只有 “部署”后才能被访问,它的部署方式通常由web.xml进行定义,其存放位置为WEB-INF/目录中;此文件为所有的webapps提供默认部署相关的配置
content.xml 每个webapp都可以专用的配置文件,它通常由专用的配置文件context.xml来定义,其存放位置为WEB-INF/目录中;此文件为所有的webapps提供默认配置
tomcat-users.xml 用户认证的账号和密码文件
catalina.policy 当使用-security选项启动tomact时,用于为tomcat设置安全策略
catalina.properties Java属性的定义文件,用于设定类加载器路径,以及一些语JVM调优相关参数
logging.properties 日志系统相关的配置。log4j

组件分类

顶级组件

Server,代表整个Tomcat容器

服务类组件

Service,组织Engine和Connector,里面只能包含一个Engine

连接器组件

Connector,有HTTP、HTTPS、AJP协议的连接器

容器类

Engine、Host、Context都是容器类组件,可以嵌入其它组件,内部配置如何运行应用程序。

内嵌类

可以内嵌到其他组件内,valve、logger、realm、loader、manager等。以logger举例,在不同容器组件内定义。

集群类组件

listener、cluster

Tomcat内部组成

image-20200716083629756

名称 说明
Server Tomcat运行的进程实例
Connector 负责客户端的HTTP,HTTPS,AJP等协议的连接。一个Connector只属于一个Engine
Service 用来组织Engine和Connector的关系
Engine 响应并处理用户请求。一个引擎上可以绑定多个Connector
Host 虚拟主机
Context 应用的上下文,配置路径映射path->directory

AJP (Apache Jserv protocol)是一种基于TCP的二进制通讯协议。

核心组件

  • Tomcat启动一个Server进程。可以启动多个Server,但一般只启动一个
  • 创建一个Service提供服务。可以创建多个Service,但一般也只创建一个
    • 每个Service中,是Engine和其连接器Connector的关联配置
  • 可以为这个Server提供多个连接器Connector,这些Connector使用了不同的协议,绑定了不同的端口。其作用就是处理来自客户端的不同的连接请求或响应
  • Service内部还定义了Engine,引擎才是真正的处理请求的入口,其内部定义多个虚拟主机Host
    • Engine对请求头做了分析,将请求发送给相应的虚拟主机
    • 如果没有匹配,数据就发往Engine上的defaultHost缺省虚拟主机
    • Engine上的缺省虚拟主机可以修改
  • Host定义虚拟主机,虚拟主机有name名称,通过名称匹配
  • Context定义应用程序单独的路径映射和配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8085" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20088"
redirectPort="8443” />
<Connector port="8009" protocol="AJP/1.3"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost"appBase="webapps"
unpackWARS="true" autoDeploy="true">
</Host>
</Engine>
</Service>
</Server>

举例:
假设来自客户的请求为: http://localhost:8080/test/index.jsp

  • 浏览器端的请求被发送到服务端端口8080,Tomcat进程监听在此端口上。通过侦听的HTTP/1.1Connector获得此请求。
  • Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的响应
  • Engine获得请求localhost:8080/test/index.jsp,匹配它所有虚拟主机Host
  • Engine匹配到名为localhost的Host。即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机
  • localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context
  • Host匹配到路径为/test的Context
  • path=/test的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet
  • Context匹配到URLPATTERN为*.jsp的servlet,对应于JspServlet类构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用spServlet的doGet或doPost方法。
  • Context把执行完了之后的HttpServletResponse对象返回给Host
  • Host把HttpServletResponse对象返回给Engine
  • Engine把HttpServletResponse对象返回给Connector
  • Connector把HttpServletResponse对象返回给浏览器端

应用部署

根目录

Tomcat中默认网站根目录是CATALINA_BASE/webapps/
在Tomcat中部署主站应用程序和其他应用程序,和之前WEB服务程序不同。

Nginx

假设在nginx中部署2个网站应用eshop、bbs,假设网站根目录是/var/www/html,那么部署可以是这样的。
eshop解压缩所有文件放到/var/www/html/目录下。
bbs的文件放在/var/www/html/bbs下。

Tomcat

Tomcat中默认网站根目录是CATALINA_BASE/webapps/
在Tomcat的webapps目录中,有个非常特殊的目录ROOT,它就是网站默认根目录。
将eshop解压后的文件放到这个ROOT中。
bbs解压后文件都放在CATALINA_BASE/webapps/bbs目录下。

每一个虚拟主机的目录都可以使用appBase配置自己的站点目录,里面都可以使用ROOT目录作为主站目录。

JSP WebApp目录结构

  • 主页配置:一般指定为index.jsp或index.html
  • WEB-INF/:当前WebApp的私有资源路径,通常存储当前应用使用的web.xml和context.xml配置文件
  • META-INF/:类似于WEB-INF
  • classes/:类文件,当前webapp需要的类
  • lib/:当前应用依赖的jar包

主页实验

默认情况下,/usr/local/tomcat/webapps/ROOT/下添加一个index.html文件,观察访问到了什么?
将/usr/local/tomcat/conf/web.xml中的下面<welcome-file-list>标签内容(默认页),复制到/usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml中,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">

<display-name>Welcome to Tomcat</display-name>
<description>
Welcome to Tomcat
</description>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

配置修改后,观察首页变化

WebApp归档格式

  • war: WebApp打包
  • jar:EJB类打包文件
  • rar:资源适配器类打包文件
  • ear:企业级WebApp打包

传统应用开发测试后,通常打包为war格式,这种文件部署到了Tomcat的webapps下,还可以自动展开。

1
<Host name="localhost" appBase="webapps" unpackMARs="true" autoDeploy="true">

部署Deploy

  • 部署:将webapp的源文件放置到目标目录,通过web.xml和context.xml文件中配置的路径就可以访问该webapp,通过类加载器加载其特有的类和依赖的类到JVM上。
    • 自动部署Auto Deploy: Tomcat发现多了这个应用就把它加载并启动起来
    • 手动部署
      • 冷部署:将webapp放到指定目录,才去启动Tomcat
      • 热部署: Tomcat服务不停止,需要依赖manager、ant脚本、tcd (tomcat client deployer)等工具
  • 反部署undeploy:停止webapp的运行,并从JVM上清除已经加载的类,从Tomcat应用目录中移除部署的文件
  • 启动start:是webapp能够访问
  • 停止stop: webapp不能访问,不能提供服务,但是JVM并不清除它

实验

  1. 添加一个文件,test.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>jsp例子</title>
</head>
<body>
后面的内容是服务器端动态生成字符串,最后拼接在一起
<%
out.println( "hello jsp");
%>
<br>
<%=request.getRequestURL()%>
</body>
</html>

先把test.jsp放到ROOT下去,试试看,访问http://YourIP:8080/test.jsp
立即可以看到,这是通过路径映射找到相应的test.jsp后,转换成test_jsp.java,在编译成test_jsp.class。/usrlloca/tomcat/work/Catalina/localhost/ROOTlorg/apacheljsp下有转换后的文件。

  1. 添加一个应用
1
2
3
4
5
6
7
8
9
10
11
12
cd 

常见开发项目目录组成
mkdir projects/myapp/{WEB-INF,classes,lib} -pv

常见应用首页,内容就用上面的test.jsp
vim projects/myapp/index.jsp

手动复制项目目录到webapps目录下去
cp -r projects/myapp/ /usr/local/tomcat/webapps/

使用http://YourIP:8080/myapp/访问试试看

配置详解

server.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
</Host>
</Engine>
</Service>
</Server>
1
<Server port="8005" shutdown="SHUTDOWN">

8005是Tomcat的管理端口,默认监听在127.0.0.1上。SHUTDOWN这个字符串接收到后就会关闭此
Server。

1
2
3
4
5
#telnet 127.0.0.1 8005
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
SHUTDOWN

这个管理功能建议禁用,改shutdown为一串猜不出的字符串。

1
<Server port="8005" shutdown="44ba3c71d57f494992641b258b965f28">

1
2
3
4
5
6
7
8
9
10
<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>

用户认证,配置文件是conf/tomcat-users.xml。
打开tomcat-users.xml,我们需要一个角色manager-gui。

1
2
3
4
5
6
7
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"/>
<role rolename="admin-gui"/>
<user username="kinmfer" password="123123" roles="manager-gui,admin-gui"/>
</tomcat-users>

Tomcat启动加载后,这些内容是常驻内存的。如果配置了新的用户,需要重启Tomcat

文件路径/usr/local/tomcat/webapps/manager/META-INF/context.xml

1
2
3
4
5
<Context antiResourceLocking="false" privileged="true" >
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
<Manager sessionAttributeValueClassNameFilter="java\.lang\.(?:Boolean|Integer|Long|Number|String)|org\.apache\.catalina\.filters\.CsrfPreventionFilter\$LruCache(?:\$1)?|java\.util\.(?:Linked)?HashMap"/>
</Context>

看正则表达式就知道是本地访问了,由于当前访问地址是192.168.x.x,可以修改正则为

1
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|192.*"

再次测试,成功


1
<Service name="Catalina">

一般情况下,一个Server实例配置一个Service,name属性相当于该Service的ID。


1
2
3
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

连接器配置。

redirectPort,如果访问HTTPS协议,自动转向这个连接器。但大多数时候,Tomcat并不会开启
HTTPS,因为Tomcat往往部署在内部,HTTPS性能较差


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

引擎配置。

defaultHost指向内部定义某虚拟主机。缺省虚拟主机可以改动,默认localhost.。


1
<Host name="localhost"appBase="webapps" unpackWARS="true"autoDeploy="true">

虚拟主机配置。
name必须是主机名,用主机名来匹配。
appBase,当前主机的网页根目录,是相对于CATALINA_HOME,也可以使用绝对路径
unpackWARs是否自动解压war格式
autoDeploy热部署,自动加载并运行应用

虚拟主机配置实验

尝试再配置一个虚拟主机,并将myapp部署到/data/webapps目录下

1
2
<Host name="node1.kinmfer.com" appBase="/data/webapps/" unpackWARS="true"
autoDeploy="false" />
1
2
3
4
5
6
7
常见虚拟主机根目录
mkdir /data/webapps -pv

cp -r ~/ projects/myapp/ /data/webapps/ROOT

bin/shutdown.sh
bin/startup.sh

刚才在虚拟主机中主机名定义tomcat.kinmfer.com/,所以需要主机在本机手动配置一个域名解析
如果是windows,修改在C:\Windows\System32\drivers\etc下的hosts文件,需要管理员权限。
使用http://tomcat.kinmfer.com:8080/访问试试看。也可以在tomcat的host-manager中观察。

Context配置

Context作用:

  • 路径映射
  • 应用独立配置,例如单独配置应用日志、单独配置应用访问控制
1
<Context path="/test" docBase="/data/test" reloadable="false"/>

path指的是访问的路径
docBase,可以是绝对路径,也可以是相对路径(相对于Host的appBase)
reloadable,true表示如果WEB-NF/classes或META-INF/lib目录下.class文件有改动,就会将WEB应用重新加载,性能消耗很大。生成环境中,会使用false来禁用。

将~/projects/myapp/下面的项目文件复制到/data/下

1
2
3
cp -r ~/projects/myapp /data/myappv1
cd /data
ln -sv myappv1 test

可以修改一下index.jsp方便区别

Tomcat的配置文件server.xml中修改如下

1
2
3
4
<Host name="tomcat.kinmfer.com"  appBase="/data/webapps"
unpackWARs="true" autoDeploy="true">
<Context path="/test" docBase="/data/test" reloadable="false"/>
</Host>

使用http://tomcat.kinmfer.com:8080/test/

**注意:**这里特别使用了软链接,原因就是以后版本升级,需要将软链接指向myappv2,重启Tomcat。
如果新版上线后,出现问题,重新修改软链接到上一个版本的目录,并重启,就可以实现回滚。