【Tomcat】Tomcat

Tomcat 简介

Tomcat 是由 Apache 软件基金会属下Jakarta项目开发的Servlet容器,按照Sun Microsystems提供的技术规范,实现了对ServletJavaServer PageJSP)的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台、安全局管理和Tomcat阀等。由于Tomcat本身也内含了HTTP服务器,因此也可以视作单独的Web服务器。Tomcat提供了一个Jasper编译器用以将JSP编译成对应的Servlet。

Servlet接口和Servlet容器这一整套规范叫作Servlet规范。Tomcat按照Servlet规范的要求实现了Servlet容器,同时它们也具有HTTP服务器的功能。

Tomcat 本质上是一个 Servlet 容器。开发人员只需要实现 Servlet 接口并注入到 Servlet 容器中,剩下的跳转响应都交给 Tomcat 实现。*

历史

Tomcat 最初由Sun公司的软件架构师 James Duncan Davidson 开发,名称为 JavaWebServer

  • 1999年 ,在 Davidson 的帮助下,该项目于1999年于 apache 软件基金会旗下的 JServ 项目合并,并发布第一个版本(3.x),即是现在的Tomcat,该版本实现了 Servlet2.2 和 JSP 1.1 规范
  • 2001年,Tomcat 发布了4.0版本,作为里程碑式的版本,Tomcat 完全重新设计了 其架构,并实现了 Servlet 2.3 和 JSP1.2规范
  • 目前 Tomcat 已经更新到 9.0.x 版本,但是目前企业中的Tomcat服务器, 主流版本还是 7.x 和 8.x 版本

常见 web 服务器软件

  • webLogic:oracle公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
  • webSphere:IBM公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
  • JBOSS:JBOSS公司的,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
  • Tomcat:Apache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaEE规范 servlet/jsp。开源的,免费的

Tomcat 架构

https://www.bilibili.com/video/BV1dJ411N7Um?p=1

HTTP 工作原理

HTTP 协议是浏览器与服务器之间的数据传送协议。作为应用层协议,HTTP 是基于 TCP/IP 协议来传递数据的(HTML文件、图片、查询结果等),HTTP 协议不涉及数据包(Packet)传输,主要规定了客户端和服务器之间的通信格式

image-20220130103856954

具体流程:

  • 用户通过浏览器进行了一个操作,比如输入网址并回车,或者是点击链接,接着浏览器获取了这个事件。
  • 浏览器向服务端发出TCP连接请求。
  • 服务程序接受浏览器的连接请求,并经过TCP三次握手建立连接。
  • 浏览器将请求数据打包成一个HTTP协议格式的数据包。
  • 浏览器将该数据包推入网络,数据包经过网络传输,最终达到端服务程序。
  • 服务端程序拿到这个数据包后,同样以HTTP协议格式解包,获取到客户端的意图。
  • 得知客户端意图后进行处理,比如提供静态文件或者调用服务端程序获得动态结果。
  • 服务器将响应结果(可能是HTML或者图片等)按照HTTP协议格式打包。
  • 服务器将响应数据包推入网络,数据包经过网络传输最终达到到浏览器。
  • 浏览器拿到数据包后,以HTTP协议的格式解包,然后解析数据,假设这里的数据是 HTML。
  • 浏览器将HTML文件展示在页面上。

那我们想要探究的Tomcat作为一个HTTP服务器,在这个过程中都做了些什么事情呢?主要是接收连接解析请求数据处理请求发送响应这几个步骤。

HTTP 服务器请求处理

浏览器发给服务端的是一个HTTP格式的请求,HTTP服务器收到这个请求后,需要调用服务端程序来处理,所谓的服务端程序就是你写的Java类,一般来说不同的请求需要由不同的Java类来处理。

image-20220130104501350

  • 图1,表示HTTP服务器直接调用具体业务类,它们是紧耦合的。
  • 图2,HTTP服务器不直接调用业务类,而是把请求交给Servlet 容器来处理,容器通过 Servlet 接口调用业务类。因此Servlet接口和Servlet容器的出现,达到了HTTP服务器与业务类解耦的目的。

Servlet接口和Servlet容器这一整套规范叫作Servlet规范Tomcat按照Servlet规范的要求实现了Servlet容器,同时它们也具有HTTP服务器的功能。作为Java程序员,如果我们要实现新的业务功能,只需要实现一个Servlet,并把它注册到Tomcat(Servlet容器)中,剩下的事情就由Tomcat帮我们处理了。

Servlet 容器工作流程

为了解耦,HTTP服务器不直接调用Servlet,而是把请求交给Servlet容器来处理,那Servlet容器又是怎么工作的呢?

  • 当客户请求某个资源时,HTTP服务器用一个ServletRequest对象把客户的请求信息封装起来,然后调用Servlet容器service方法
  • Servlet容器拿到请求后,根据请求的URL和Servlet的映射关系,找到相应的Servlet
  • 如果Servlet还没有被加载,就用反射机制创建这个Servlet,并调用Servlet的init方法来完成初始化
  • 接着调用Servlet的service方法来处理请求,把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端。

image-20220130105249865

HTTP 服务器具体实现就是下面要介绍的 Connector,其负责将客户端发来的 HTTP 请求进行解析转换成 ServletRequest 对象。

Tomcat 整体架构

Tomcat 要实现两个核心功能:

  • 处理客户端发来的Socket连接,负责网络字节流Request/Response对象之间的转化。
  • 加载和管理Servlet,以及具体处理Request请求。

因此Tomcat设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流容器负责内部处理

image-20220130111057831

从图中可以看出,一个 Tomcat 服务器可以配置多个服务 Service(默认配置了一个名为 "Catalina" 的 Service)。每个 Service 中又包含了多个连接器 Connector一个Servlet容器

  • 多个连接器是为了实现支持多种 I/O 模型应用层协议
  • Servlet容器管理着所有Servlet实例对象。其内有一个引擎 Engine,且包含多个主机 Host 与多个 Context。每个 Context 代表一个 Web 应用,其内有许多的 Servlet 实例对象。

其中,Tomcat 的 Connector 连接器框架为 Coyote。Servlet 容器为 Catalina 容器

Servlet 容器又可被称为 Catalina 容器、Container 容器

单独的连接器或者容器都不能对外提供服务,需要把它们组装起来才能工作,组装后这个整体叫作Service组件。注意,Service本身没有做什么重要的事情,只是在连接器和容器外面多包了一层,把它们组装在一起。Tomcat内可能有多个Service,这样的设计也是出于灵活性的考虑。通过在Tomcat中配置多个Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。

Tomcat 的 Connector 连接器采用NIO中的多路复用模型。一个Acceptor负责监听所有Socket请求,然后每收到一个新的请求,就从线程池中占用一个线程执行后续代码(传递给 Adapter,再发送给 Container 容器)。Acceptor 继续监听其他请求,不会浪费时间执行响应代码(响应代码新开辟线程执行)

示例

https://blog.csdn.net/hancoder/article/details/118466983

示例 1:多协议访问

img

示例 2:

img

其中,Server标签设置的端口号为8005,shutdown="SHUTDOWN",表示在8005端口监听 SHUTDOWN 命令,如果接收到了就会关闭Tomcat。一个Server有多个Service,Service左边的内容(Engine)都属于Container的,Service下边是Connector,负责监听TCP请求。

连接器框架 Coyote

Coyote 简介

Coyote是Tomcat的Connector连接器框架的名称,是Tomcat服务器提供的供客户端访问的外部接口。客户端通过Coyote与服务器建立连接、发送请求并接受响应。

Coyote封装了底层的网络通信(Socket 请求及响应处理),为Catalina容器(Servlet容器/Cotainer容器)提供了统一的接口,使Catalina容器与具体的请求协议及IO操作方式完全解耦

  • Coyote将Socket输入转换封装为Request对象,交由Catalina容器进行处理,处理请求完成后,Catalina容器通过Coyote提供的Response对象将结果写入输出流
  • Catalina容器只负责接收Coyote包装处理后的Request对象,对其进行业务处理后再将响应结果Response对象传给Coyote,Coyote在进行解析后再返回HTTP请求给客户端

Servlet 容器又可被称为 Catalina 容器、Container 容器

Coyote作为独立的模块,只负责具体协议和IO的相关操作,与Servlet规范实现没有直接关系,因此即便是RequestResponse对象也并未实现Servlet规范对应的接口,而是在Catalina容器中将他们进一步封装为ServletRequestServletResponse

示意图:

图中的 Catalina 就是 Container 容器

image-20220130140058239

Request -> ServletRequest 的转换是由 Coyote 框架的适配器组件 Adapter 完成的(适配器模式)。

IO 模型与协议

在Coyote中,Tomcat支持多种I/O模型和应用层协议,具体包含了以下IO模型和应用层协议:

自 8.5/9.0 版本起,Tomcat 移除了对BIO的支持改为使用NIO

IO 模型(传输层):

IO 模型 描述
NIO 非阻塞IO,采用JAVA NIO实现
NIO2 异步IO,采用JDK7最新的NIO2类库实现
APR 采用Apache可移植运行库实现,是C/C++编写的本地库,如果选择此方案需要单独安装APR库

Tomcat 支持的应用层协议

应用层协议 描述
HTTP1.1 大部分web应用采用的协议
HTTP2 HTTP2大幅度提升了web性能,自tomcat 9版本以后支持
AJP 用于和Web服务器(Apache)集成,以实现对静态资源的优化和集群部署,当前支持AJP/1.3

两种协议分层:

image-20220130141231762

在 8.0 之前,Tomcat 默认采用的I/O方式为BIO,之后改为NIO。无论 NIO、NIO2 还是 APR, 在性能方面均优于以往的BIO。如果采用APR,甚至可以达到 Apache HTTP Server 的影响性能。

连接器组件

Coyote中的连接组件分为四部分:

image-20220130142639010

EndPoint

EndPoint是Coyote的通信端点,即通信监听的接口,是具体Socket接收和发送处理器,是对传输层的抽象,因此EndPoint是用来实现TCP/IP协议的。

Tomcat 并没有EndPoint接口,而是提供了一个抽象类AbstractEndpoint ,里面定义了两个内部类:AcceptorSocketProcessor

  • Acceptor用于监听Socket连接请求。
  • SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在run()方法里调用协议处理组件Processor进行处理。为了提高处理能力,SocketProcessor被提交到线程池来执行。而这个线程池叫作执行器(Executor)。

该模式就类似于NIO中的多路复用模型。一个Acceptor负责监听所有Socket请求,然后每收到一个新的请求,就从线程池中占用一个线程执行后续代码(传递给 Adapter,再发送给 Container 容器)。Acceptor 继续监听其他请求,不会浪费时间执行响应代码(响应代码新开辟线程执行)

Processor

Processor是HTTP协议的处理接口如果说EndPoint是用来实现TCP/IP协议的,那么Processor用来实现HTTP协议Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat RequestResponse对象,并通过Adapter将其提交到容器处理,Processor是对应用层协议的抽象。

ProtocolHandler

ProtocolHandler是Coyote协议接口, 通过EndpointProcessor,实现针对具体协议的处理能力。Tomcat 按照协议和I/O 提供了6个实现类 : AjpNioProtocolAjpAprProtocolAjpNio2ProtocolHttp11NioProtocolHttp11Nio2ProtocolHttp11AprProtocol。我们在配置tomcat/conf/server.xml 时,至少要指定具体的ProtocolHandler,当然也可以指定协议名称。例如: HTTP/1.1。如果安装了APR,那么将使用Http11AprProtocol,否则使用Http11NioProtocol

Adapter

由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat定义了自己的Request类来“存放”这些请求信息。ProtocolHandler接口负责解析请求并生成Tomcat Request类。但是这个Request对象不是标准的ServletRequest,也就意味着,不能用Tomcat Request作为参数来调用容器。

Tomcat设计者的解决方案是引入Adapter,这是适配器模式的经典运用,连接器调用AdapterSevice()方法,传入的是Tomcat Request对象,Adapter负责将Tomcat Request转成ServletRequest,再调用容器的Service方法。

总结

img

Connector就是使用ProtocolHandler(协议处理器)来处理请求的,不同的ProtocolHandler代表不同的连接类型,比如:Http11Protocol使用的是普通Socket来连接的,Http11NioProtocol使用的是NioSocket来连接的。

其中ProtocolHandler由包含了三个部件:EndpointProcessorAdapter

  • Endpoint 用来处理底层Socket的网络连接,因此Endpoint是用来实现TCP/IP协议的
  • Processor 用于将Endpoint接收到的Socket封装成RequestProcessor是用来实现HTTP协议的
  • Adapter 用于将Request交给Container进行具体的处理(将请求适配到Servlet容器)。

此外Endpoint的抽象实现AbstractEndpoint里面定义的AcceptorAsyncTimeout两个内部类和一个Handler接口:

  • Acceptor用于监听请求
  • AsyncTimeout用于检查异步Request的超时
  • Handler用于处理接收到的Socket,在内部调用Processor进行处理。

Catalina 容器

Catalina 容器即为 Container 容器、Servlet 容器。Tomcat 的配置文件中,默认 Service 的名称为 "Catalina";默认 Engine 的名称也为 "Catalina"。说明 Catalina 只是对 Service 和 Container 容器起的一个别名罢了,不要弄混 Service 和 Container 容器的区别。

Tomcat 本质上就是一款 Servlet 容器(Servlet 可以理解为一种规范), 因此 Catalina 才是 Tomcat 的核心,其他模块都是为Catalina提供支撑的。 比如:通过Coyote模块提供通信连接,Jasper模块提供JSP引擎,Naming提供JNDI 服务,Juli提供日志服务。

image-20220130185854077

Tomcat的主要组件结构如下:Server表示着整个服务器,Server下面有多个服务Service,每个服务都包含着多个连接器组件Connector(Coyote 实现)和一个容器组件Container。

image-20220130190809580

Tomcat的Container中设计了4种容器,分别是EngineHostContextWrapper。这4种容器不是平行关系,而是父子关系。Tomcat通过一种分层的架构,使得Servlet容器具有很好的灵活性。

image-20220130190849670

其中,

  • 一个Container中只有一个EngineEngine是整个Container容器的引擎,用来管理多个站点
  • 一个Engine中可以有多个Host一个Host代表一个站点(虚拟主机),一个Host里有可以包含多个Context
  • Context就是一个Web应用,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件;
  • 一个Web应用中可以包含多个WrapperWrapper就是Servlet。

img

我们也可以再通过Tomcat的server.xml配置文件来加深对Tomcat容器的理解:

image-20220130194623089

Tomcat采用了组件化的设计,它的构成组件都是可配置的,其中最外层的是Server,其他组件按照一定的格式要求配置在这个顶层容器中。

示例:

img

Tomcat的容器是具有父子关系的,形成一个树形结构,Tomcat就是用组合模式来管理这些容器的。具体实现方法是,所有容器组件都实现了Container接口,因此组合模式可以使得用户对单容器对象和组合容器对象的使用具有一致性。这里单容器对象指的是最底层的Wrapper,组合容器对象指的是上面的ContextHost或者EngineContainer接口扩展了LifeCycle接口,LifeCycle接口用来统一管理各组件的生命周期

image-20220130194711901


模板方法:将共有的方法抽取到父类当中。在子类里实现自己独特的方法内容。

Tomcat 源码中使用了模板方法的设计模式。上图这些 StandardXxx 类都继承自 LifecycleBase 类。在调用该类的 init() 方法时将调用其定义的 initInternals() 方法(是 abstract 的), 其子类都各自重写了自己的 initInternals()方法。

LifecycleBase 类中:

image-20220202212159083

image-20220202212240707

其子类都重写了该方法:

image-20220202212331950


Container 如何处理请求的

Container处理请求是使用Pipeline-Valve管道来处理的(Valve是阀门之意)

Pipeline-Valve是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将处理后的请求返回,再让下一个处理着继续处理。

但是,Pipeline-Valve使用的责任链模式和普通的责任链模式有些不同!区别主要有以下两点:

  • 每个Pipeline都有特定的Valve,而且是在管道的最后一个执行,这个Valve叫做BaseValve,BaseValve是不可删除的;
  • 在上层容器的管道的BaseValve中会调用下层容器的管道。

我们知道Container包含四个子容器,而这四个子容器对应的BaseValve分别在:StandardEngineValveStandardHostValveStandardContextValveStandardWrapperValve

img

  • Connector在接收到请求后会首先调用最顶层容器的Pipeline来处理,这里的最顶层容器的Pipeline就是EnginePipeline(Engine的管道)
  • Engine的管道中依次会执行EngineValve1、EngineValve2等等,最后会执行StandardEngineValve,在StandardEngineValve中会调用Host管道,然后再依次执行Host的HostValve1、HostValve2等,最后在执行StandardHostValve,然后再依次调用Context的管道和Wrapper的管道,最后执行到StandardWrapperValve
  • 当执行到StandardWrapperValve的时候,会在StandardWrapperValve中创建FilterChain,并调用其doFilter()方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的FilterdoFilter()方法和Servletservice()方法,这样请求就得到了处理
  • 当所有的Pipeline-Valve都执行完之后,并且处理完了具体的请求,这个时候就可以将返回的结果交给Connector了,Connector在通过Socket的方式将结果返回给客户端

Tomcat 启动流程

image-20220130202122132

  • 调用 bin/startup.bat(在linux 目录下 , 需要调用 bin/startup.sh)
  • 在startup.bat 脚本中,调用了catalina.bat。
  • 在catalina.bat 脚本文件中,调用了BootStrap中的main方法。
  • BootStrapmain方法中调用了 init 方法 ,来创建Catalina及初始化类加载器。
  • BootStrapmain方法中调用了 load 方法 ,在其中又调用了Catalinaload方法。
  • Catalinaload方法中 , 需要进行一些初始化的工作, 并需要构造Digester对象,用于解析 XML。
  • 然后在调用后续组件的初始化操作 。加载Tomcat的配置文件,初始化容器组件 ,监听对应的端口号, 准备接受客户端请求

源码解析

由于所有的组件均存在初始化、启动、停止等生命周期方法,拥有生命周期管理的特性,所以Tomcat在设计的时候, 基于生命周期管理抽象成了一个接口 Lifecycle ,而组件 ServerServiceContainerExecutorConnector 组件, 都实现了一个生命周期的接口,从而具有了以下生命周期中核心方法:

  • init():初始化组件
  • start():开始组件
  • stop():停止组件
  • destroy():销毁组件

image-20220130203247456

各组件实现

上面我们提到的ServerServiceEngineHostContext都是接口, 下图中罗列了这些接口的默认实现类。当前对于 Endpoint组件来说,在Tomcat中没有对应的Endpoint接口, 但是有一个抽象类 AbstractEndpoint ,其下有三个实现类: NioEndpointNio2EndpointAprEndpoint,这三个实现类,分别对应于前面讲解连接器Coyote时, 提到的链接器支持的三种IO模型:NIO,NIO2,APR ,Tomcat8.5版本中,默认采用的是 NioEndpoint

image-20220130203426222

总结

从启动流程图中以及源码中,我们可以看出Tomcat的启动过程非常准化,统一按照生命周期管理接口Lifecycle的定义进行启动。首先调init()方法进行组件的逐级初始化操作,然后再调用start()方法进行启动。

每一级的组件除了完成自身的处理外,还要负责调用子组件响应的生命周期管理方法,组件与组件之间是松耦合的,因为我们可以很容易的通过配置文件进行修改和替换

Tomcat 请求处理流程

Tomcat中维护了一个Mapper组件将用户请求的URL定位到一个Servlet,它的工作原理是:Mapper组件里保存了Web应用的配置信息,其实就是容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里Servlet映射的路径,这些配置信息就像是一个多层次的Map。

当一个请求到来时,Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去查找,就能定位到一个Servlet。一个请求URL最后只会定位到一个Wrapper容器,也就是一个Servlet。下面的示意图中,就描述了当用户请求链接 http://www.itcast.cn/bbs/findAll 之后,是如何找到最终处理业务逻辑的Servlet:

image-20220130204002258

上面这幅图只是描述了根据请求的URL如何查找到需要执行的Servlet,下面从Tomcat的设计架构层面来分析Tomcat了请求处理的过程:

image-20220130204451489

  • Connector组件Endpoint中的Acceptor监听客户端套接字连接并接收Socket。将连接交给线程池Executor处理,开始执行请求响应任务。
  • Processor组件读取消息报文,解析请求行、请求体、请求头,封装成Request对象。
  • CoyoteAdaptor组件负责将Connector组件和Engine容器关联起来,把生成的Request对象转换成ServletRequest,后面还会把ServletResponse转成HttpResponse对象返回回去。
  • Mapper组件根据请求行的URL值和请求头的Host值匹配由哪个Host容器、Context容器、Wrapper容器处理请求。
  • Engine容器的管道开始处理,管道中包含若干个Valve、每个Valve负责部分处理逻辑。执行完Valve后会执行基础的StandardEngineValve,负责调用Host容器的Pipeline。
  • Host容器的管道开始处理,流程类似,最后执行Context容器的Pipeline。
  • Context容器的管道开始处理,流程类似,最后执行Wrapper容器的Pipeline。
  • Wrapper容器的管道开始处理,流程类似,最后执行Wrapper容器对应的Servlet对象的处理方法。

Tomcat中的各个组件各司其职,组件之间松耦合,确保了整体架构的可伸缩性和可拓展性, 在Tomcat中,每个Container组件采用责任链模式来完成具体的请求处理。在Tomcat中定义了Pipeline和 Valve两个接口,Pipeline用于构建责任链,后者代表责任链上的每个处理器。Pipeline中维护了一个基础的Valve,它始终位于Pipeline的末端(最后执行),封装了具体的请求处理和输出响应的过程。当然,我们也可以调用addValve()方法,为Pipeline添加其他的Valve,后添加的Valve位于基础的Valve之前,并按照添加顺序执行。Pipiline通过获得首个Valve来启动整合链条的执行。其实就是代码的同步执行。

image-20220130232015897

总结

Mapper 组件负责根据 Connector 传来的 URL 找到对应的 Servlet,然后由 Container 逐步传给对应的 Wrapper,在经过所有的过滤器 Filter 后再执行对应 Servlet 的 service() 方法(每个请求处理都交给一个新线程执行)。

Jasper

Jasper 简介

对于基于JSP 的web应用来说,我们可以直接在JSP页面中编写 Java代码,添加第三方的 标签库,以及使用EL表达式。但是无论经过何种形式的处理,最终输出到客户端的都是 标准的HTML页面(包含js ,css…),并不包含任何的java相关的语法。也就是说,我们可以把jsp看做是一种运行在服务端的脚本。那么服务器是如何将 JSP页面转换为 HTML页面的呢?

Jasper模块是Tomcat的JSP核心引擎,我们知道JSP本质上是一个Servlet。Tomcat使用Jasper对JSP语法进行解析,生成Servlet并生成Class字节码,用户在进行访问jsp时,会访问Servlet,最终将访问的结果直接响应在浏览器端 。另外,在运行的时候,Jasper还会检测JSP文件是否被修改,如果修改,则会重新编译JSP文件。

Tomcat 并不会在启动Web应用的时候自动编译JSP文件, 而是在客户端第一次请求时, 才编译需要访问的JSP文件。

JSP 编译过程

Tomcat 在默认的web.xml中配置了一个org.apache.jasper.servlet.JspServlet,用于处理所有的.jsp.jspx结尾的请求,该Servlet实现即是运行时编译的入口。

JspServlet 处理流程图:

image-20220130232455469

JSP文件生成的对应Servlet对象:

  • 对于每一行的静态内容(HTML),调用 out.write 输出在浏览器上。
  • 对于 <% ... %> 中的 Java 代码 ,将直接转换为 Servlet 类中的代码。 如果在 Java 代码中嵌入了静态文件, 则同样调用 out.write 输出。

Tomcat 配置文件

Tomcat 目录结构

image-20220130103737751

Tomcat 服务器的配置主要集中于 tomcat/conf 下的 catalina.policycatalina.propertiescontext.xmlserver.xmltomcat-users.xmlweb.xml 文件。

server.xml是Tomcat的核心配置,tomcat-user.xml主要是配置一些访问manager页面、域安全用户角色的信息,web.xml和webapp中的web.xml功能是一致的,控制一些访问Servlet的参数等等,但是全局的。

更多详细介绍见博客 https://blog.csdn.net/qq_15758463/article/details/120287536?spm=1001.2014.3001.5501与文档 https://blog.yuyunzhao.cn/document/Tomcat.pdf

server.xml

server.xml中,默认Service的名称为"Catalina";默认Engine的名称也为"Catalina"s

server.xml是Tomcat的核心配置,包含了Tomcat的Servlet容器(Catalina)的所有配置。由于配置的属性特别多,我们在这里主要讲解其中的一部分重要配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Server>
<Listener/>
<GlobaNamingResources>
...
</GlobaNamingResources>
<Service>
<Executor/>
<Connector/>
<Engine>
<Logger/>
<Realm/>
<Host>
<Logger/>
<Context/>
</Host>
</Engine>
</Service>
</Server>

Server是server.xml的根元素,用于创建一个Server实例,默认使用的实现类是org.apache.catalina.core.StandardServer。

1
2
3
4
5
6
7
8
9
<Server port="8005" shutdown="SHUTDOWN">
<Listener/>
<GlobaNamingResources>
...
</GlobaNamingResources>
<Service>
...
</Service>
</Server>
  • port : Tomcat监听的关闭服务器的端口。
  • shutdown: 关闭服务器的指令字符串。

Server内嵌的子元素为ListenerGlobalNamingResourcesService

Listener 标签

默认配置的5个Listener的含义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!‐‐ 用于以日志形式输出服务器 、操作系统、JVM的版本信息 ‐‐>
<Listener className="org.apache.catalina.startup.VersionLoggerListener"/>

<!‐‐ 用于加载(服务器启动) 和 销毁 (服务器停止) APR。 如果找不到APR库, 则会
输出日志, 并不影响Tomcat启动 ‐‐>
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />

<!‐‐ 用于避免JRE内存泄漏问题 ‐‐>
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />

<!‐‐ 用户加载(服务器启动) 和 销毁(服务器停止)全局命名服务 ‐‐>
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

<!‐‐ 用于在Context停止时重建Executor池中的线程,以避免ThreadLocal 相关的内存泄漏 ‐‐>
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

GlobalNamingResources 标签

GlobalNamingResources 中定义了全局命名服务:

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>

Service 标签

Service元素用于创建 Service 实例,默认使用 org.apache.catalina.core.StandardService。默认情况下,Tomcat 仅指定了Service的名称,值为"Catalina"。Service可以内嵌的元素为: ListenerExecutorConnectorEngine。其中:

  • Listener 用于为Service添加生命周期监听器
  • Executor 用于配置Service共享线程池
  • Connector 用于配置Service包含的连接器
  • Engine 用于配置Service中连接器对应的Servlet容器引擎

一个Server服务器,可以包含多个Service服务。

1
2
3
<Service name="Catalina">
...
</Service>

Executor 标签

默认情况下,Service并未添加共享线程池配置,如果不配置共享线程池,那么Catalina各组件在用到线程池时会独立创建。 如果我们想添加一个共享的线程池,可以在下添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
<Executor name="tomcatThreadPool" 线程池名称,用于 Connector中指定。
namePrefix="catalina‐exec‐" 所创建的每个线程的名称前缀,一个单独的线程名称为 namePrefix+threadNumber
maxThreads="200" 池中最大线程数。
minSpareThreads="100" 核心线程数
maxIdleTime="60000" 线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为60001分钟),单位毫秒。
maxQueueSize="Integer.MAX_VALUE" 在被执行前最大线程排队数目 需要根据硬件实际调整 如果太大可能内存溢出
prestartminSpareThreads="false" 启动线程池时是否启动 minSpareThreads部分线程。默认值为false,即不启动。
threadPriority="5" 线程池中线程优先级,默认值为5,值从110
线程池实现类,未指定情况下,默认实现类为org.apache.catalina.core.StandardThreadExecutor
如果想使用自定义线程池首先需要实现org.apache.catalina.Executor接口。
className="org.apache.catalina.core.StandardThreadExecutor"/>

Connector 标签

Connector 用于创建连接器实例。默认情况下,server.xml 配置了两个连接器,一个支持HTTP协议一个支持AJP协议。因此大多数情况下,我们并不需要新增连接器配置,只是根据需要对已有连接器进行优化。

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

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
  • port:端口号,Connector用于创建服务端Socket并进行监听, 以等待客户端请求链接。如果该属性设置为0,Tomcat将会随机选择一个可用的端口号给当前Connector使用。
  • protocol :当前Connector支持的访问协议默认为 HTTP/1.1 ,并采用自动切换机制选择一个基于 Java NIO 的连接器或者基于本地APR的连接器(根据本地是否含有Tomcat的本地库判定)。如果不希望采用上述自动切换的机制, 而是明确指定协议,可以使用以下值:

Http协议:

1
2
3
org.apache.coyote.http11.Http11NioProtocol  非阻塞式 Java NIO 链接器
org.apache.coyote.http11.Http11Nio2Protocol 非阻塞式 JAVA NIO2 链接器
org.apache.coyote.http11.Http11AprProtocol APR 链接器

AJP协议 :

1
2
3
org.apache.coyote.ajp.AjpNioProtocol  非阻塞式 Java NIO 链接器
org.apache.coyote.ajp.AjpNio2Protocol 非阻塞式 JAVA NIO2 链接器
org.apache.coyote.ajp.AjpAprProtocol APR 链接器

  • connectionTimeOutConnector 接收连接后的等待超时时间, 单位为毫秒。 -1 表示不超时。
  • redirectPort:当前Connector 不支持SSL(https)请求, 接收到了一个请求, 并且也符合 security-constraint:安全约束, 需要SSL传输,Catalina自动将请求重定向到指定的端口。
  • executor: 如果有使用共享线程池可以指定上面讲的共享线程池的名称
  • URIEncoding : 用于指定编码URI的字符编码,Tomcat8.x版本默认的编码为 UTF-8 ,Tomcat7.x版本默认为ISO-8859-1。

完整的协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
<Connector port="8080"
protocol="HTTP/1.1"
executor="tomcatThreadPool"
maxThreads="1000"
minSpareThreads="100"
acceptCount="1000"
maxConnections="1000"
connectionTimeout="20000"
compression="on"
compressionMinSize="2048"
disableUploadTimeout="true"
redirectPort="8443"
URIEncoding="UTF‐8" />

Engine 标签

Engine 作为Servlet引擎的顶级元素,内部可以嵌入: ClusterListenerRealmValveHost

1
2
3
<Engine name="Catalina" defaultHost="localhost">
...
</Engine>
  • name: 用于指定Engine的名称, 默认为Catalina 。该名称会影响一部分Tomcat的存储路径(如临时文件)。
  • defaultHost : 默认使用的虚拟主机名称, 当客户端请求指向的主机无效时,将交由默认的虚拟主机处理,默认为localhost。

Host 标签

Host 元素用于配置一个虚拟主机, 它支持以下嵌入元素:AliasClusterListenerValveRealmContext。如果在Engine下配置Realm, 那么此配置将在当前Engine下的所有Host中共享。 同样,如果在Host中配置Realm, 则在当前Host下的所有Context中共享。

Context中的Realm优先级 > Host 的Realm优先级 > Engine中的Realm优先级。说的简单点就是Realm类似于Unix里面的group。在Unix中,一个group对应着系统的一组资源,某个group不能访问不属于它的资源。Tomcat用Realm来将不同的应用(类似系统资源)赋给不同的用户(类似group),没有权限的用户则不能访问相关的应用。用于配置安全管理角色,通常读取tomcat-uesrs.xml进行验证。

1
2
3
4
5
6
7
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
...
</Host>

<Host name="www.web1.com" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Alias>www.web2.com</Alias>
</Host>
  • name:当前Host通用的网络名称,必须与DNS服务器上的注册信息一致。Engine中包含的Host必须存在一个名称与Engine的defaultHost设置一致。
  • appBase:当前Host的应用基础目录,当前Host上部署的Web应用均在该目录下(可以是绝对目录,相对路径)。默认为webapps。
  • unpackWARs:设置为true,Host在启动时会将appBase目录下war包解压为目录。设置为false, Host将直接从war文件启动。
  • autoDeploy:控制tomcat是否在运行时定期检测并自动部署新增或变更的web应用。

通过给Host添加别名,我们可以实现同一个Host拥有多个网络名称,然后就可以通过两个域名访问当前Host下的应用(需要确保DNS或hosts中添加了域名的映射配置)。

Context

Context元素用于配置一个Web应用,默认的配置如下:

1
2
3
<Context docBase="myApp" path="/myApp">
....
</Context>
  • docBase:Web应用目录或者War包的部署路径。可以是绝对路径,也可以是相对于 Host appBase的相对路径。
  • path:Web应用的Context 路径。如果我们Host名为localhost, 则该web应用访问的根路径为: http://localhost:8080/myApp。

它支持的内嵌元素为:CookieProcessorLoaderManagerRealmResourcesWatchedResourceJarScannerValve

tomcat-user.xml

该配置文件中,主要配置的是Tomcat的用户,角色等信息,用来控制Tomcat中manager, host-manager的访问权限,还可以配合上面讲的Realm控制用户组的访问权限。Realm的相关内容可以参考这篇文章,写的已经很详细了。

web.xml

web.xml 是web应用的描述文件,它支持的元素及属性来自于Servlet规范定义 。在Tomcat中, Web 应用的描述信息包括 tomcat/conf/web.xml 中默认配置以及Web应用 WEB-INF/web.xml 下的定制配置。在Tomcat中的web.xml进行的配置是对webappp下所有的应用都有影响(全局配置)。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<context‐param>
<param‐name>contextConfigLocation</param‐name>
<param‐value>classpath:applicationContext‐*.xml</param‐value>
<description>Spring Config File Location</description>
</context‐param>

<session‐config>
<session‐timeout>30</session‐timeout> 会话超时时间,单位 分钟
<cookie‐config> 用于配置会话追踪Cookie
<name>JESSIONID</name> Cookie的名称
<domain>www.itcast.cn</domain> Cookie的域名
<path>/</path> Cookie的路径
<comment>Session Cookie</comment> 注释
<http‐only>true</http‐only> cookie只能通过HTTP方式进行访问,JS无法读取或修改,此项可以增加网站访问的安全性。
<secure>false</secure> 此cookie只能通过HTTPS连接传递到服务器,而HTTP 连接则不会传递该信息。注意是从浏览器传递到服务器,服务器端的Cookie对象不受此项影响。
<max‐age>3600</max‐age> 以秒为单位表示cookie的生存期,默认为‐1表示是会话Cookie,浏览器关闭时就会消失。
</cookie‐config>
<tracking‐mode>COOKIE</tracking‐mode> 用于配置会话追踪模式,Servlet3.0版本中支持的追踪模式:COOKIE、URL、SSL
</session‐config>

<servlet>
<servlet‐name>myServlet</servlet‐name> 指定servlet的名称, 该属性在web.xml中唯一。
<servlet‐class>cn.itcast.web.MyServlet</servlet‐class> 用于指定servlet类名
<init‐param> 用于指定servlet的初始化参数, 在应用中可以通HttpServlet.getInitParameter 获取。
<param‐name>fileName</param‐name>
<param‐value>init.conf</param‐value>
</init‐param>
<load‐on‐startup>1</load‐on‐startup> 用于控制在Web应用启动时,Servlet的加载顺序。 值小于0,web应用启动时,不加载该servlet, 第一次访问时加载。
<enabled>true</enabled> 若为false ,表示Servlet不处理任何请求。
</servlet>

<servlet‐mapping>
<servlet‐name>myServlet</servlet‐name>
<url‐pattern>*.do</url‐pattern>
<url‐pattern>/myservet/*</url‐pattern> 用于指定URL表达式,一个 servlet‐mapping可以同时配置多个 url‐pattern
</servlet‐mapping>

<servlet>
<servlet‐name>uploadServlet</servlet‐name>
<servlet‐class>cn.itcast.web.UploadServlet</servlet‐class>
<multipart‐config>
<location>C://path</location> 存放生成的文件地址。
<max‐file‐size>10485760</max‐file‐size> 允许上传的文件最大值。 默认值为‐1, 表示没有限制。
<max‐request‐size>10485760</max‐request‐size>针对该 multi/form‐data 请求的最大数量,默认值为‐1, 表示无限制。
<file‐size‐threshold>0</file‐size‐threshold> 当数量量大于该值时, 内容会被写入文件。
</multipart‐config>
</servlet>

<listener>
<listener‐class>org.springframework.web.context.ContextLoaderListener</listener‐class>
</listener>

<filter>
<filter‐name>myFilter</filter‐name> 用于指定过滤器名称,在web.xml中,过滤器名称必须唯一。
<filter‐class>cn.itcast.web.MyFilter</filter‐class> 过滤器的全限定类名, 该类必须实现Filter接口。
<async‐supported>true</async‐supported> 该过滤器是否支持异步
<init‐param> 用于配置Filter的初始化参数, 可以配置多个, 可以通过FilterConfig.getInitParameter获取
<param‐name>language</param‐name>
<param‐value>CN</param‐value>
</init‐param>
</filter>

<filter‐mapping>
<filter‐name>myFilter</filter‐name>
<url‐pattern>/*</url‐pattern> 指定该过滤器需要拦截的URL。
</filter‐mapping>

<welcome‐file‐list>
<welcome‐file>index.html</welcome‐file>
<welcome‐file>index.htm</welcome‐file>
<welcome‐file>index.jsp</welcome‐file>
</welcome‐file‐list>

<error‐page>
<error‐code>404</error‐code>
<location>/404.html</location>
</error‐page>
<error‐page>
<error‐code>500</error‐code>
<location>/500.html</location>
</error‐page>
<error‐page>
<exception‐type>java.lang.Exception</exception‐type>
<location>/error.jsp</location>
</error‐page>

更多详细配置见博客 https://blog.csdn.net/qq_15758463/article/details/120287536?spm=1001.2014.3001.5501与文档 https://blog.yuyunzhao.cn/document/Tomcat.pdf

WebSocket

WebSocket 是一种全双工应用层协议,用于建立客户端与服务器端的双向连接,使得服务端可以主动向客户端发出请求。常用于聊天软件等需要双方保持互相能够通讯且保持长链接的场景:

image-20220130231727802