2.3 Tomcat 请求处理流程 2.3 Tomcat 请求处理流程 2.3.1 请求的接收与连接器 (Connector) Tomcat 作为 Web 服务器,首先需要监听并接收来自客户端的 HTTP 请求。这个任务由 连接器 (Connector) 组件负责。连接器是 Tomcat 中负责接收外部请求并将其传递给 Tomcat 容器的核心组件。 1. 连接器的作用: 监听端口: 连接器监听指定的端口 (例如 HTTP 的 8080 端口,HTTPS 的 443 端口),等待客户端的连接请求。 协议处理: 连接器负责处理网络协议,例如 HTTP/1.1、HTTP/2、AJP 等。它将接收到的字节流按照协议规范解析成 Tomcat 内部可以理解的请求对象。
Tomcat 作为 Web 服务器,首先需要监听并接收来自客户端的 HTTP 请求。这个任务由 连接器 (Connector) 组件负责。连接器是 Tomcat 中负责接收外部请求并将其传递给 Tomcat 容器的核心组件。
1. 连接器的作用:
监听端口: 连接器监听指定的端口 (例如 HTTP 的 8080 端口,HTTPS 的 443 端口),等待客户端的连接请求。
协议处理: 连接器负责处理网络协议,例如 HTTP/1.1、HTTP/2、AJP 等。它将接收到的字节流按照协议规范解析成 Tomcat 内部可以理解的请求对象。
请求封装: 连接器将解析后的请求信息封装成 Tomcat 内部的 Request 对象,该对象包含了请求头、请求体、请求参数等信息。
响应处理: 连接器还负责将 Tomcat 容器处理后的 Response 对象转换成符合 HTTP 协议的响应报文,并发送回客户端。
线程管理: 连接器管理处理请求的线程,例如使用线程池来提高并发处理能力。
2. 连接器的类型:
Tomcat 支持多种连接器,常见的有:
HTTP Connector (HTTP/1.1): 用于处理 HTTP/1.1 协议的连接,是最常用的连接器。例如 org.apache.catalina.connector.Http11NioConnector (NIO)、org.apache.catalina.connector.Http11AprConnector (APR)、org.apache.catalina.connector.Http11BioConnector (BIO)。
AJP Connector (Apache JServ Protocol): 用于与前端 Web 服务器 (如 Apache HTTP Server) 集成,进行反向代理和负载均衡。例如 org.apache.catalina.connector.AjpNioConnector。
3. 连接器的配置 (server.xml):
连接器在 Tomcat 的 server.xml 配置文件中进行配置。以下是一个 HTTP/1.1 NIO 连接器的配置示例:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" acceptCount="100" maxThreads="200" minSpareThreads="10" maxSpareThreads="75" enableLookups="false" disableUploadTimeout="true" URIEncoding="UTF-8"/>
配置项解释:
port: 连接器监听的端口号,这里是 8080 端口。
protocol: 使用的协议,HTTP/1.1 表示 HTTP/1.1 协议。
connectionTimeout: 连接超时时间,单位毫秒。
redirectPort: 如果需要重定向到 HTTPS,指定 HTTPS 连接器的端口号。
acceptCount: 等待队列的最大长度,当所有处理线程都在忙时,新来的连接将被放入等待队列。
maxThreads: 连接器创建的最大线程数,用于处理客户端请求。
minSpareThreads: 连接器保持的最小空闲线程数。
maxSpareThreads: 连接器保持的最大空闲线程数。
enableLookups: 是否允许 DNS 反向查找,建议设置为 false 以提高性能。
disableUploadTimeout: 是否禁用上传超时,建议设置为 true。
URIEncoding: URI 编码,建议设置为 UTF-8。
4. 代码实践:
在 Tomcat 启动时,连接器组件会被初始化并启动,开始监听指定的端口。当客户端发送请求到该端口时,连接器会接收请求并进行初步处理。
Mermaid Graph TD 图:
图示解释:
客户端发送 HTTP 请求到 Tomcat 服务器。
连接器接收到 HTTP 请求。
连接器解析 HTTP 协议,将字节流转换为 Tomcat 可以理解的数据结构。
连接器将解析后的请求信息封装成 Request 对象。
Request 对象被传递到 Tomcat 容器进行进一步处理。
连接器接收并初步处理请求后,会将请求传递给 容器 (Container) 组件进行更深层次的处理。Tomcat 的容器采用了 层次结构 设计,这种结构使得 Tomcat 能够灵活地管理和组织 Web 应用。
1. 容器层次结构:
Tomcat 的容器层次结构从上到下依次为:
Engine (引擎): 最顶层的容器,一个 Tomcat 服务器实例只能有一个 Engine。Engine 负责管理多个虚拟主机 (Host)。
Host (虚拟主机): 代表一个虚拟主机,负责管理多个 Web 应用 (Context)。一个 Engine 可以包含多个 Host。
Context (Web 应用): 代表一个 Web 应用,负责管理该 Web 应用下的所有 Servlet。一个 Host 可以包含多个 Context。
Wrapper (Servlet 包装器): 最底层的容器,负责管理一个 Servlet 的生命周期和请求处理。一个 Context 可以包含多个 Wrapper。
容器层次结构关系: Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。
2. 请求路由过程:
当连接器将请求传递给容器时,请求会按照容器层次结构进行路由:
Engine 路由: Engine 接收到请求后,会根据请求的 主机名 (Host Name) 将请求路由到对应的 Host 容器。主机名通常从 HTTP 请求头中的 Host 字段获取。
Host 路由: Host 容器接收到请求后,会根据请求的 上下文路径 (Context Path) 将请求路由到对应的 Context 容器。上下文路径通常从请求 URI 中获取。
Context 路由: Context 容器接收到请求后,会根据请求的 Servlet 路径 (Servlet Path) 将请求路由到对应的 Wrapper 容器,即具体的 Servlet。Servlet 路径也从请求 URI 中获取,并根据 web.xml 或注解中的 Servlet 映射配置进行匹配。
Wrapper 处理: Wrapper 容器负责调用 Servlet 的 service() 方法来处理请求。
3. 容器配置 (server.xml, context.xml, web.xml):
Engine 配置 (server.xml): 在 server.xml 中配置 Engine,通常不需要修改默认配置。
Host 配置 (server.xml): 在 server.xml 中配置 Host,定义虚拟主机的主机名、应用部署目录等。
<Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> </Host> </Engine>
name: Engine 的名称,通常为 "Catalina"。
defaultHost: 默认主机名,当请求的主机名无法匹配任何 Host 时,将使用默认主机。
name: Host 的主机名,例如 "localhost" 或域名。
appBase: Web 应用部署目录,Tomcat 会在该目录下查找 Web 应用。
unpackWARs: 是否自动解压 WAR 文件。
autoDeploy: 是否自动部署新的 Web 应用。
Context 配置 (context.xml 或 server.xml): 在 context.xml 或 server.xml 中配置 Context,定义 Web 应用的上下文路径、资源配置等。
<Context path="/myapp" docBase="myapp" reloadable="true"> </Context>
path: Web 应用的上下文路径,例如 "/myapp"。
docBase: Web 应用的文档根目录,可以是 WAR 文件路径或解压后的目录路径。
reloadable: 是否允许 Tomcat 在 Web 应用发生更改时自动重新加载。
Servlet 映射配置 (web.xml 或注解): 在 web.xml 或 Servlet 类中使用注解配置 Servlet 的映射路径。
web.xml (示例):
<servlet> <servlet-name>MyServlet</servlet-name> <servlet-class>com.example.MyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/myservlet</url-pattern> </servlet-mapping>
Servlet 注解 (示例):
@WebServlet("/myservlet") public class MyServlet extends HttpServlet { // ... Servlet 代码 ... }
4. 代码实践:
Tomcat 启动时,会解析 server.xml、context.xml 和 web.xml 等配置文件,构建容器层次结构和 Servlet 映射关系。当请求到达时,Tomcat 会根据这些配置进行路由,找到对应的 Servlet 进行处理。
Mermaid Graph TD 图:
图示解释:
连接器将请求传递给 Engine 引擎。
Engine 根据 Host Name 匹配 Host 虚拟主机。
Host 根据 Context Path 匹配 Context Web 应用。
Context 根据 Servlet Path 匹配 Wrapper Servlet 包装器。
最终请求被路由到对应的 Servlet 进行处理。
在请求被路由到 Servlet 之前,Tomcat 引入了 阀门 (Valve) 管线 机制。阀门类似于 Servlet 过滤器 (Filter),但它是 Tomcat 容器级别的组件,可以拦截和处理所有经过该容器的请求。
1. 阀门的作用:
请求预处理: 在请求到达 Servlet 之前,Valve 可以对请求进行预处理,例如日志记录、安全检查、权限验证、请求参数转换等。
响应后处理: 在 Servlet 处理完请求并生成响应后,Valve 也可以对响应进行后处理,例如添加响应头、压缩响应内容、记录响应日志等。
请求拦截: Valve 可以根据特定条件拦截请求,例如阻止非法请求、限制访问频率等。
2. 阀门管线 (Pipeline):
每个容器 (Engine, Host, Context) 都有一个关联的 Valve 管线。Valve 管线由一系列 Valve 组成,请求会依次经过管线中的每个 Valve 进行处理。Valve 的执行顺序由其在管线中的配置顺序决定。
3. 标准阀门:
Tomcat 提供了许多标准 Valve,可以满足常见的请求处理需求:
AccessLogValve: 记录访问日志,可以配置日志格式、日志文件路径等。
RequestDumperValve: 用于调试,可以打印请求和响应的详细信息。
RemoteAddrValve/RemoteHostValve: 基于客户端 IP 地址或主机名进行访问控制。
BasicAuthenticator/DigestAuthenticator/FormAuthenticator: 实现基本、摘要或表单认证。
ErrorReportValve: 处理错误页面显示。
4. 自定义阀门:
除了标准 Valve,开发者还可以自定义 Valve 来实现特定的请求处理逻辑。自定义 Valve 需要实现 org.apache.catalina.Valve 接口,并将其配置到容器的 Valve 管线中。
5. 阀门配置 (server.xml, context.xml):
Valve 可以配置在 Engine, Host, Context 容器中。配置方式通常是在容器的配置元素中添加 <Valve> 子元素。
Host 级别 Valve 配置 (server.xml 示例):
<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" resolveHosts="false"/> </Host>
Context 级别 Valve 配置 (context.xml 示例):
<Context path="/myapp" docBase="myapp" reloadable="true"> <Valve className="com.example.MyCustomValve"/> </Context>
自定义 Valve 代码示例 (MyCustomValve.java):
package com.example; import org.apache.catalina.Valve; import org.apache.catalina.ValveContext; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import javax.servlet.ServletException; import java.io.IOException; public class MyCustomValve implements Valve { @Override public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException { // 请求预处理逻辑 System.out.println("MyCustomValve: 请求预处理 - 请求 URI: " + request.getRequestURI()); // 调用下一个 Valve 或 Servlet context.invokeNext(request, response); // 响应后处理逻辑 System.out.println("MyCustomValve: 响应后处理 - 响应状态码: " + response.getStatus()); } @Override public ValveContext getContext() { return null; // Not used in simple Valve } @Override public void setContext(ValveContext context) { // Not used in simple Valve } @Override public Valve getNext() { return null; // Not used in simple Valve } @Override public void setNext(Valve valve) { // Not used in simple Valve } @Override public boolean isAsyncSupported() { return false; // 默认不支持异步 } }
6. 代码实践:
Tomcat 在请求处理过程中,会依次执行配置在容器 Valve 管线中的 Valve。开发者可以通过配置标准 Valve 或自定义 Valve 来扩展 Tomcat 的请求处理能力。
Mermaid Graph TD 图:
图示解释:
请求到达 Context Web 应用的 Valve 管线。
请求依次经过 Valve 1, Valve 2, Valve 3 进行预处理。
请求最终被传递到 Wrapper Servlet 包装器进行 Servlet 处理。
Servlet 处理完成后,响应会再次经过 Valve 管线 (反向顺序) 进行后处理。
最终响应返回给客户端。
经过容器路由和 Valve 管线处理后,请求最终会被路由到 Wrapper 容器,并由 Wrapper 容器负责调用 Servlet 的 service() 方法来处理请求。Servlet 是 Java Web 开发的核心组件,负责处理具体的业务逻辑。
1. Servlet 的生命周期:
Servlet 的生命周期由 Servlet 容器 (Tomcat) 管理,主要包括以下阶段:
加载和实例化 (Load & Instantiate): Tomcat 启动时或第一次接收到对该 Servlet 的请求时,Tomcat 会加载 Servlet 类,并创建 Servlet 实例。Servlet 实例只会被创建一次,是单例的。
初始化 (Initialization): Servlet 实例创建后,Tomcat 会调用 Servlet 的 init() 方法进行初始化。init() 方法只会被调用一次,用于执行 Servlet 初始化操作,例如加载配置文件、建立数据库连接等。
请求处理 (Service): 当客户端发送请求到该 Servlet 时,Tomcat 会调用 Servlet 的 service() 方法来处理请求。service() 方法会被多次调用,每次处理一个请求。service() 方法会根据 HTTP 请求方法 (GET, POST, etc.) 调用对应的 doGet(), doPost() 等方法。
销毁 (Destroy): 当 Tomcat 关闭或 Web 应用被卸载时,Tomcat 会调用 Servlet 的 destroy() 方法进行销毁。destroy() 方法只会被调用一次,用于释放 Servlet 占用的资源,例如关闭数据库连接、释放文件句柄等。
2. service() 方法和 doGet(), doPost() 方法:
service(ServletRequest req, ServletResponse res): Servlet 接口中定义的核心方法,用于处理所有类型的 HTTP 请求。HttpServlet 抽象类继承了 GenericServlet 并实现了 service() 方法,根据请求方法调用 doGet(), doPost(), doPut(), doDelete() 等方法。
doGet(HttpServletRequest req, HttpServletResponse resp): 处理 HTTP GET 请求。
doPost(HttpServletRequest req, HttpServletResponse resp): 处理 HTTP POST 请求。
doPut(HttpServletRequest req, HttpServletResponse resp): 处理 HTTP PUT 请求。
doDelete(HttpServletRequest req, HttpServletResponse resp): 处理 HTTP DELETE 请求。
开发者通常需要继承 HttpServlet 抽象类,并重写 doGet(), doPost() 等方法来实现具体的业务逻辑。
3. Request 和 Response 对象:
Servlet 的 service() 方法接收两个重要的参数:ServletRequest 和 ServletResponse (在 HttpServlet 中类型为 HttpServletRequest 和 HttpServletResponse)。
HttpServletRequest: 封装了客户端的 HTTP 请求信息,例如请求头、请求参数、请求体、Cookie、Session 等。Servlet 可以通过 HttpServletRequest 对象获取客户端的请求数据。
HttpServletResponse: 用于构建 HTTP 响应报文,Servlet 可以通过 HttpServletResponse 对象设置响应头、响应状态码、响应体等,并将响应发送回客户端。
4. Servlet 代码示例 (MyServlet.java):
package com.example; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/hello") public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=UTF-8"); PrintWriter out = resp.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html><head><title>Hello Servlet</title></head><body>"); out.println("<h1>Hello, Servlet!</h1>"); out.println("<p>Current Time: " + new java.util.Date() + "</p>"); out.println("</body></html>"); } }
5. 代码实践:
开发者需要编写 Servlet 代码来实现具体的业务逻辑,并通过 HttpServletRequest 获取请求数据,通过 HttpServletResponse 构建响应数据。
Mermaid Graph TD 图:
图示解释:
Wrapper Servlet 包装器获取 Servlet 实例。
Wrapper 调用 Servlet 实例的 service() 方法。
service() 方法根据请求方法调用 doGet() 或 doPost() 等方法。
Servlet 在 doGet() 或 doPost() 方法中处理业务逻辑。
Servlet 生成 Response 数据 (例如 HTML 内容)。
Servlet 将 Response 对象返回给 Wrapper。
Servlet 处理完请求并生成响应数据后,Tomcat 需要将响应数据构建成符合 HTTP 协议的响应报文,并发送回客户端。这个过程涉及到 HttpServletResponse 对象的使用以及响应数据的写出。
1. HttpServletResponse 对象的使用:
Servlet 通过 HttpServletResponse 对象来设置响应报文的各个部分:
设置响应状态码 (Status Code): 使用 resp.setStatus(int sc) 方法设置 HTTP 响应状态码,例如 200 OK, 404 Not Found, 500 Internal Server Error 等。
设置响应头 (Headers): 使用 resp.setHeader(String name, String value) 方法设置响应头,例如 Content-Type, Content-Length, Cache-Control 等。
获取输出流 (Output Stream/Writer): 使用 resp.getOutputStream() 获取字节输出流或 resp.getWriter() 获取字符输出流,用于将响应体数据写入到输出流中。
2. 响应数据的写出:
Servlet 通过输出流将响应体数据写入到 HttpServletResponse 对象中。Tomcat 会将写入到输出流的数据转换成 HTTP 响应报文的响应体部分。
字节输出流 (getOutputStream()): 适用于输出二进制数据,例如图片、文件等。
字符输出流 (getWriter()): 适用于输出文本数据,例如 HTML, JSON, XML 等。
3. 响应的返回流程:
Servlet 将响应数据写入 HttpServletResponse 对象后,Tomcat 会按照以下流程将响应返回给客户端:
Wrapper 容器: Wrapper 容器接收到 Servlet 返回的 HttpServletResponse 对象。
Valve 管线 (反向): 响应会再次经过 Context, Host, Engine 的 Valve 管线 (按照配置顺序的逆序执行),进行响应后处理。
连接器 (Connector): Valve 管线处理完成后,响应被传递回连接器。
响应封装: 连接器将 HttpServletResponse 对象转换成符合 HTTP 协议的响应报文 (包括响应头、响应状态行、响应体)。
响应发送: 连接器将 HTTP 响应报文通过网络连接发送回客户端。
4. 代码实践 (Servlet 响应示例):
@WebServlet("/response") public class ResponseServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置响应状态码 resp.setStatus(HttpServletResponse.SC_OK); // 200 OK // 设置响应头 resp.setContentType("text/plain;charset=UTF-8"); resp.setHeader("X-Custom-Header", "Custom Value"); // 获取字符输出流并写入响应体 PrintWriter out = resp.getWriter(); out.println("This is a plain text response."); } }
5. 代码实践:
Servlet 通过 HttpServletResponse 对象构建响应,并使用输出流将响应数据写入,最终由 Tomcat 将响应发送回客户端。
Mermaid Graph TD 图:
图示解释:
Servlet 处理完成后,生成 HttpServletResponse 对象。
Servlet 设置响应状态码和响应头。
Servlet 获取输出流 (Writer 或 OutputStream)。
Servlet 将响应体数据写入输出流。
HttpServletResponse 对象被传递回 Wrapper 容器。
响应经过 Valve 管线进行后处理。
响应被传递到连接器。
连接器将 HttpServletResponse 对象封装成 HTTP 响应报文。
连接器将 HTTP 响应报文发送回客户端。