tomcat学习

tomcat 分析

参考: https://u19900101.github.io/2021-09-07-tomcat/

整体架构

Tomcat 的两个核心功能

1) 处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。

2) 加载和管理 Servlet,以及具体处理 Request 请求。

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

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

连接器和容器

连接器

EndPoint

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

2) Tomcat 并没有 EndPoint 接口,而是提供了一个抽象类 AbstractEndpoint ,里面定义了两个内部类:Acceptor 和 SocketProcessor。Acceptor 用于监听 Socket 连接请求。 SocketProcessor 用于处理接收到的 Socket 请求,它实现 Runnable 接口,在 Run 方法里调用协议处理组件 Processor 进行处理。为了提高处理能力,SocketProcessor 被提交到线程池来执行。而这个线程池叫作执行器(Executor),我在后面的专栏会详细介绍 Tomcat 如何扩展原生的 Java 线程池。

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

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

Adapter:由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat 定义了自己的 Request 类来“存放”这些请求信息。ProtocolHandler接口负责解析请求并生成 Tomcat Request 类。但是这个 Request 对象不是标准的 ServletRequest,也就意味着,不能用 Tomcat Request 作为参数来调用容器。Tomcat 设计者的解决方案是引入CoyoteAdapter,这是适配器模式的经典运用,连接器调用 CoyoteAdapter 的 Sevice 方法,传入的是 Tomcat Request 对象,CoyoteAdapter 负责将 Tomcat Request 转成 ServletRequest,再调用容器的 Service 方法。

容器

Engine:表示整个 Catalina 的 Servlet 引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine,但是一个引擎可包含多个 Host

Host:代表一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可包含多个 Context

Context:表示一个 Web 应用程序,一个 Web 应用可包含多个 Wrapper

Wrapper:表示一个 Servlet,Wrapper 作为容器中的最底层,不能包含子容器

tomcat 的一些问题

关于Tomcat中的三个Context的理解

参考: https://yzddmr6.com/posts/tomcat-context/

idea 部署并运行 tomcat 源码

参考: https://www.cnblogs.com/grasp/p/10061577.html ,照这个弄有问题。

使用 maven(3.3.9) 部署,tomcat(8.5.88),java(11.0.1)

下载源码后,解压,在源码目录文件夹下创建 pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
  
  <modelVersion>4.0.0</modelVersion>  
  <groupId>org.apache.tomcat</groupId>  
  <artifactId>Tomcat8.5</artifactId>  
  <name>Tomcat8.5</name>  
  <version>8.5</version>  
  
  <build>  
    <finalName>Tomcat8.5</finalName>  
    <sourceDirectory>java</sourceDirectory>  
    <testSourceDirectory>test</testSourceDirectory>  
    <resources>  
      <resource>  
        <directory>java</directory>  
      </resource>  
    </resources>  
    <testResources>  
      <testResource>  
        <directory>test</directory>  
      </testResource>  
    </testResources>  
    <plugins>  
      <plugin>  
        <groupId>org.apache.maven.plugins</groupId>  
        <artifactId>maven-compiler-plugin</artifactId>  
        <version>2.3</version>  
        <configuration>  
          <encoding>UTF-8</encoding>  
          <source>1.8</source>  
          <target>1.8</target>  
        </configuration>  
      </plugin>  
    </plugins>  
  </build>  
  
  <dependencies>  
    <dependency>  
      <groupId>junit</groupId>  
      <artifactId>junit</artifactId>  
      <version>4.12</version>  
      <scope>test</scope>  
    </dependency>  
    <dependency>  
      <groupId>org.easymock</groupId>  
      <artifactId>easymock</artifactId>  
      <version>4.3</version>  
    </dependency>  
    <dependency>  
      <groupId>ant</groupId>  
      <artifactId>ant</artifactId>  
      <version>1.7.0</version>  
    </dependency>  
    <dependency>  
      <groupId>wsdl4j</groupId>  
      <artifactId>wsdl4j</artifactId>  
      <version>1.6.2</version>  
    </dependency>  
    <dependency>  
      <groupId>javax.xml</groupId>  
      <artifactId>jaxrpc</artifactId>  
      <version>1.1</version>  
    </dependency>  
    <dependency>  
      <groupId>org.eclipse.jdt.core.compiler</groupId>  
      <artifactId>ecj</artifactId>  
      <version>4.5.1</version>  
    </dependency>  
    <!-- https://mvnrepository.com/artifact/com.unboundid/unboundid-ldapsdk -->  
    <dependency>  
      <groupId>com.unboundid</groupId>  
      <artifactId>unboundid-ldapsdk</artifactId>  
      <version>4.0.9</version>  
      <scope>test</scope>  
    </dependency>  
  
  
  
  </dependencies>  
</project>

idea 导入这个项目(maven),找到 java/org/apache/catalina/startup/Bootstrap.java ,启动。有报错,一个一个解决。

在 tomcat 的源码 org.apache.catalina.startup.ContextConfig 中的 configureStart 函数中手动将 JSP 解析器初始化:在 webConfig(); 后面添加下面这句

context.addServletContainerInitializer(new JasperInitializer(), null);

报错解决

问题:

找不到:符号: 变量 CookieFilter 位置: 类 util. TestCookieFilter

解决:在tomcat/test/utiltest/util包中新建CookieFilter

package util;

import java.util.Locale;
import java.util.StringTokenizer;

/**
 * Cookie过滤
 *
 * @author Chova
 * @date 2020-09-04
 */
public class CookieFilter {
    private static final String OBFUSCATED = "[obfuscated]";

    private CookieFilter() {
        // Hide default constructor
    }

    public static String filter(String cookieHeader, String sessionId) {

        StringBuilder sb = new StringBuilder(cookieHeader.length());

        // Cookie name value pairs are ';' separated.
        // Session IDs don't use ; in the value so don't worry about quoted
        // values that contain ;
        StringTokenizer st = new StringTokenizer(cookieHeader, ";");

        boolean first = true;
        while (st.hasMoreTokens()) {
            if (first) {
                first = false;
            } else {
                sb.append(';');
            }
            sb.append(filterNameValuePair(st.nextToken(), sessionId));
        }


        return sb.toString();
    }

    private static String filterNameValuePair(String input, String sessionId) {
        int i = input.indexOf('=');
        if (i == -1) {
            return input;
        }
        String name = input.substring(0, i);
        String value = input.substring(i + 1, input.length());

        return name + "=" + filter(name, value, sessionId);
    }

    public static String filter(String cookieName, String cookieValue, String sessionId) {
        if (cookieName.toLowerCase(Locale.ENGLISH).contains("jsessionid") &&
                (sessionId == null || !cookieValue.contains(sessionId))) {
            cookieValue = OBFUSCATED;
        }

        return cookieValue;
    }
}

参考: https://blog.csdn.net/JewaveOxford/article/details/108431280