5.1 Tomcat Valve (阀门)


文档摘要

5.1 Tomcat Valve (阀门) 5.1 Tomcat Valve (阀门) 5.1.1 Valve 的概念与作用 在Tomcat的请求处理流程中,当一个HTTP请求到达Tomcat服务器后,它会经过一系列组件的处理才能最终到达Servlet容器,并被相应的Servlet处理。Valve就位于这个处理管道之中,它像一个“阀门”一样,拦截并处理流经它的请求和响应。 核心概念: 请求处理管道: Tomcat使用管道-阀门(Pipeline-Valve)设计模式来处理HTTP请求。Pipeline代表请求处理管道,而Valve则是管道中的一个个处理环节。 拦截器: Valve本质上是一种拦截器,它可以拦截到达容器的请求,并在请求被Servlet处理之前或之后执行特定的逻辑。

5.1 Tomcat Valve (阀门)

5.1 Tomcat Valve (阀门)

5.1.1 Valve 的概念与作用

在Tomcat的请求处理流程中,当一个HTTP请求到达Tomcat服务器后,它会经过一系列组件的处理才能最终到达Servlet容器,并被相应的Servlet处理。Valve就位于这个处理管道之中,它像一个“阀门”一样,拦截并处理流经它的请求和响应。

核心概念:

  • 请求处理管道: Tomcat使用管道-阀门(Pipeline-Valve)设计模式来处理HTTP请求。Pipeline代表请求处理管道,而Valve则是管道中的一个个处理环节。

  • 拦截器: Valve本质上是一种拦截器,它可以拦截到达容器的请求,并在请求被Servlet处理之前或之后执行特定的逻辑。

  • 可插拔: Valve是可插拔的组件,可以根据需求灵活地添加到Tomcat的各个容器(Engine, Host, Context, Wrapper)中,实现定制化的请求处理流程。

  • 职责链模式: Pipeline实际上是职责链模式的一种实现,请求会依次经过Pipeline中配置的Valve,每个Valve都有机会处理请求,并将请求传递给下一个Valve。

Valve 的主要作用:

  • 请求预处理: 在请求到达Servlet之前,Valve可以进行请求的预处理,例如字符编码转换、请求参数校验、身份认证、访问控制等。

  • 响应后处理: 在Servlet处理完请求并生成响应后,Valve可以进行响应的后处理,例如添加响应头、压缩响应内容、记录访问日志等。

  • 请求转发/重定向: Valve可以根据特定条件将请求转发到其他的Servlet或资源,或者进行重定向。

  • 错误处理: Valve可以捕获请求处理过程中发生的异常,并进行统一的错误处理,例如跳转到错误页面、记录错误日志等。

  • 性能监控: Valve可以记录请求处理的耗时、资源使用情况等信息,用于性能监控和分析。

  • 安全增强: Valve可以实现诸如IP地址过滤、请求频率限制、安全审计等安全增强功能。

5.1.2 Valve 的架构与工作原理

Tomcat的Valve架构基于 org.apache.catalina.Valve 接口和 org.apache.catalina.Pipeline 接口。

Valve 接口:

org.apache.catalina.Valve 接口是所有Valve组件必须实现的接口。它定义了Valve的基本行为,最核心的方法是 invoke(Request request, Response response)

public interface Valve { // ... 其他方法 ... /** * Execute a periodic operation, such as scanning for invalid sessions, * expiring timers, etc. */ public void backgroundProcess(); /** * Return descriptive information about this Valve implementation. */ public String getInfo(); /** * Return the next Valve in the pipeline associated with this Valve, * if any. */ public Valve getNext(); /** * Set the next Valve in the pipeline associated with this Valve. * * @param valve The new next valve */ public void setNext(Valve valve); /** * Execute a periodic operation, such as scanning for invalid sessions, * expiring timers, etc. * <p> * <b>IMPLEMENTATION NOTE</b>: It is strongly recommended that Valve * implementations write log messages using the facilities of their * associated Manager or Container, rather than to System.out or * System.err. This allows a Catalina container to redirect log output * appropriately. * * @param request The servlet request being processed * @param response The servlet response being created * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet error occurs */ public void invoke(Request request, Response response) throws IOException, ServletException; }
  • invoke(Request request, Response response): 这是Valve的核心方法。当请求到达Valve时,Tomcat容器会调用这个方法。Valve的实现需要在该方法中完成自身的处理逻辑。

    • 请求和响应对象: 方法参数 requestresponse 分别是Tomcat封装的请求和响应对象,提供了访问HTTP请求和响应的各种信息和操作的方法。

    • 调用链:invoke() 方法中,Valve可以选择是否将请求传递给Pipeline中的下一个Valve。通常,Valve会调用 getNext().invoke(request, response) 将请求传递给下一个Valve,形成Valve的调用链。如果不调用 getNext().invoke(),则请求处理流程将被Valve中断。

Pipeline 接口:

org.apache.catalina.Pipeline 接口定义了Valve的管道,负责管理和执行Valve链。每个Tomcat容器(Engine, Host, Context, Wrapper)都关联一个Pipeline。

public interface Pipeline { // ... 其他方法 ... /** * Add a Valve to the end of the pipeline. * * @param valve Valve to be added * * @exception IllegalArgumentException if this Valve is not of a * type that is allowed to be added to this Pipeline. * @exception IllegalArgumentException if the specified Valve is null */ public void addValve(Valve valve) throws IllegalArgumentException; /** * Get the Valves associated with this Pipeline. * * @return the valves associated with this Pipeline */ public Valve[] getValves(); /** * Return the basic Valve (if any) associated with this Pipeline. */ public Valve getBasic(); /** * Set the basic Valve (usually the last one in the pipeline). * * @param valve The new basic valve */ public void setBasic(Valve valve); /** * Return the Valve instance that has been distinguished as the basic Valve * (if any). * * @return the basic Valve */ public Valve getFirst(); /** * Remove a Valve from the pipeline. * * @param valve Valve to be removed * * @exception IllegalArgumentException if this Valve is not of a * type that is allowed to be removed from this Pipeline. * @exception IllegalArgumentException if the specified Valve is null */ public void removeValve(Valve valve) throws IllegalArgumentException; /** * Invoke the first Valve in this pipeline's processing chain, passing * the specified request and response. * * @param request the request to be processed * @param response the response to be created * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet error occurs * @exception Throwable if a processing error occurs */ public void invoke(Request request, Response response) throws IOException, ServletException, Throwable; }
  • addValve(Valve valve): 向Pipeline中添加Valve。Valve会按照添加的顺序形成调用链。

  • getValves(): 获取Pipeline中所有的Valve。

  • invoke(Request request, Response response): 启动Pipeline的请求处理流程。当Tomcat容器接收到请求后,会调用Pipeline的 invoke() 方法,该方法会依次调用Pipeline中的每个Valve的 invoke() 方法。

Valve 工作流程:

  1. 请求接收: Tomcat容器(Engine, Host, Context, Wrapper)接收到HTTP请求。

  2. Pipeline 启动: 容器调用与其关联的Pipeline的 invoke() 方法,启动请求处理流程。

  3. Valve 链式调用: Pipeline的 invoke() 方法会依次调用Pipeline中配置的每个Valve的 invoke() 方法。

  4. Valve 处理: 每个Valve的 invoke() 方法执行自身的处理逻辑,例如日志记录、权限校验等。

  5. 传递到下一个 Valve: 大部分Valve在完成自身处理后,会调用 getNext().invoke(request, response) 将请求传递给Pipeline中的下一个Valve。

  6. Basic Valve (Servlet 容器): Pipeline的最后一个Valve通常是Basic Valve,它负责将请求传递给Servlet容器进行Servlet处理。

  7. Servlet 处理: 请求到达Servlet容器,被相应的Servlet处理,生成响应。

  8. 响应返回 Valve 链: 响应生成后,会沿着Valve链逆向返回,每个Valve的 invoke() 方法可以进行响应的后处理。

  9. 响应发送: 最终,响应被发送回客户端。

Valve 的分类:

Tomcat Valve 可以根据不同的维度进行分类:

  • 按功能分类:

    • 日志 Valve: AccessLogValve, RequestDumperValve 等,用于记录请求访问日志、调试信息。

    • 安全 Valve: RemoteAddrValve, RemoteHostValve, JSecurityValve 等,用于实现IP地址过滤、主机名过滤、安全认证等。

    • 会话管理 Valve: PersistentValve, StandardSessionValve 等,用于会话持久化、会话管理。

    • 错误处理 Valve: ErrorReportValve,用于处理请求处理过程中发生的错误。

    • 性能监控 Valve: 自定义 Valve 可以实现性能监控功能。

  • 按实现方式分类:

    • 标准 Valve (Standard Valve): Tomcat内置的 Valve,例如 AccessLogValve, RemoteAddrValve 等。

    • 自定义 Valve (Custom Valve): 开发者根据自身需求实现的 Valve。

  • 按容器级别分类:

    • Engine Valve: 配置在 <Engine> 容器下的 Valve,作用于整个Tomcat实例的所有Host和Context。

    • Host Valve: 配置在 <Host> 容器下的 Valve,作用于该Host下的所有Context。

    • Context Valve: 配置在 <Context> 容器下的 Valve,作用于该Context下的所有Servlet。

    • Wrapper Valve: 配置在 <Wrapper> 容器下的 Valve,作用于单个Servlet。Wrapper Valve 相对较少使用,通常在Context或更高级别配置 Valve 即可满足需求。

5.1.3 Valve 的配置与使用

Tomcat Valve 的配置通常在 server.xml 文件中进行。可以根据 Valve 的作用范围,将其配置在不同的容器标签下:<Engine>, <Host>, <Context>.

配置语法:

<Engine ...> <Valve className="your.custom.ValveClassName" attribute1="value1" attribute2="value2" ... /> ... </Engine> <Host ...> <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" resolveHosts="false"/> ... </Host> <Context path="/your-app" ...> <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.0\.0\.1|192\.168\.\d+\.\d+"/> ... </Context>
  • <Valve> 标签: 用于声明一个 Valve。

  • className 属性: 指定 Valve 的完整类名,必须是实现了 org.apache.catalina.Valve 接口的类。

  • 其他属性: Valve 可以根据自身的需求定义其他的配置属性,在 <Valve> 标签中使用属性名-值对进行配置。这些属性的值会在 Valve 初始化时被设置到 Valve 实例中。

配置位置:

  • Engine Valve: 配置在 <Engine> 标签内部,对整个Tomcat实例生效。通常用于全局性的功能,例如全局访问日志、全局安全策略等。

  • Host Valve: 配置在 <Host> 标签内部,对该Host下的所有Web应用生效。通常用于Host级别的功能,例如Host级别的访问日志、Host级别的安全策略等。

  • Context Valve: 配置在 <Context> 标签内部,只对该Web应用生效。通常用于Web应用级别的功能,例如应用级别的访问日志、应用级别的安全策略、自定义的请求处理逻辑等。

标准 Valve 示例 - AccessLogValve:

AccessLogValve 是 Tomcat 内置的用于记录请求访问日志的 Valve。以下是一个配置 AccessLogValve 的示例:

<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 &quot;%r&quot; %s %b" resolveHosts="false"/> <Valve className="org.apache.catalina.valves.ErrorReportValve" /> <Context path="/my-app" docBase="my-app" reloadable="true"> </Context> </Host>
  • className="org.apache.catalina.valves.AccessLogValve": 指定使用 AccessLogValve

  • directory="logs": 指定日志文件存放目录为 Tomcat 的 logs 目录。

  • prefix="localhost_access_log.": 指定日志文件名前缀。

  • suffix=".txt": 指定日志文件后缀。

  • pattern="%h %l %u %t &quot;%r&quot; %s %b": 指定日志格式,%h 表示客户端主机名,%l 表示远程登录名,%u 表示远程用户,%t 表示日期和时间,%r 表示请求行,%s 表示状态码,%b 表示响应内容长度。

  • resolveHosts="false": 是否解析主机名,设置为 false 可以提高性能。

标准 Valve 示例 - RemoteAddrValve:

RemoteAddrValve 用于根据客户端IP地址进行访问控制。以下是一个配置 RemoteAddrValve 的示例,只允许来自 127.0.0.1192.168.x.x 网段的IP地址访问:

<Context path="/my-app" docBase="my-app" reloadable="true"> <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.0\.0\.1|192\.168\.\d+\.\d+"/> </Context>
  • className="org.apache.catalina.valves.RemoteAddrValve": 指定使用 RemoteAddrValve

  • allow="127\.0\.0\.1|192\.168\.\d+\.\d+": 使用正则表达式指定允许访问的IP地址,| 表示或,\. 需要转义,\d+ 表示一个或多个数字。

5.1.4 自定义 Valve 的代码实践

为了更好地理解 Valve 的工作原理和应用,我们来创建一个简单的自定义 Valve,实现记录请求处理时间的日志功能。

1. 创建自定义 Valve 类:

创建一个 Java 类 RequestTimeValve,实现 org.apache.catalina.Valve 接口。

package your.custom.valve; import org.apache.catalina.Valve; import org.apache.catalina.ValveContext; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import javax.servlet.ServletException; import java.io.IOException; public class RequestTimeValve implements Valve { private static final Log log = LogFactory.getLog(RequestTimeValve.class); protected Valve next = null; @Override public Valve getNext() { return next; } @Override public void setNext(Valve valve) { this.next = valve; } @Override public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException { long startTime = System.currentTimeMillis(); try { // 调用下一个 Valve,继续请求处理流程 context.invokeNext(request, response); } finally { long endTime = System.currentTimeMillis(); long elapsedTime = endTime - startTime; String requestURI = request.getRequestURI(); String remoteAddr = request.getRemoteAddr(); log.info("Request URI: " + requestURI + ", Remote Address: " + remoteAddr + ", Processing Time: " + elapsedTime + "ms"); } } @Override public void backgroundProcess() { // 不需要后台处理,可以留空 } @Override public String getInfo() { return "RequestTimeValve/1.0"; } @Override public boolean isAsyncSupported() { return false; // 简单示例,不支持异步 } }

代码详解:

  • package your.custom.valve;: 指定 Valve 类的包名。

  • import ...;: 导入需要的类。

  • public class RequestTimeValve implements Valve: 定义 RequestTimeValve 类,实现 Valve 接口。

  • private static final Log log = LogFactory.getLog(RequestTimeValve.class);: 使用 Tomcat 的日志框架 JULI 获取 Logger 实例。

  • protected Valve next = null;: 保存 Pipeline 中的下一个 Valve。

  • getNext(), setNext(): 实现 Valve 接口的 getNext()setNext() 方法,用于获取和设置下一个 Valve。

  • invoke(Request request, Response response, ValveContext context): Valve 的核心方法。

    • long startTime = System.currentTimeMillis();: 记录请求开始时间。

    • context.invokeNext(request, response);: 调用 ValveContext.invokeNext() 方法将请求传递给 Pipeline 中的下一个 Valve。注意这里使用的是 ValveContext 而不是 getNext().invoke()ValveContext 是 Tomcat 内部使用的接口,用于 Valve 调用链的管理。

    • finally { ... }: 使用 finally 块确保无论请求处理是否发生异常,都会执行后处理逻辑。

    • long endTime = System.currentTimeMillis();: 记录请求结束时间。

    • long elapsedTime = endTime - startTime;: 计算请求处理耗时。

    • String requestURI = request.getRequestURI();, String remoteAddr = request.getRemoteAddr();: 获取请求 URI 和客户端 IP 地址。

    • log.info(...): 使用 Logger 记录请求处理信息,包括请求 URI、客户端 IP 地址和处理时间。

  • backgroundProcess(), getInfo(), isAsyncSupported(): 实现 Valve 接口的其他方法,在本示例中,backgroundProcess()isAsyncSupported() 可以留空或返回默认值,getInfo() 返回 Valve 的描述信息。

2. 打包自定义 Valve:

RequestTimeValve.java 编译成 .class 文件,并将其打包成 JAR 文件,例如 request-time-valve.jar

3. 部署自定义 Valve:

request-time-valve.jar 文件复制到 Tomcat 的 lib 目录下。

4. 配置 Tomcat 使用自定义 Valve:

修改 server.xml 文件,在需要应用 RequestTimeValve 的容器中添加 <Valve> 配置。例如,在 <Context> 容器中添加:

<Context path="/my-app" docBase="my-app" reloadable="true"> <Valve className="your.custom.valve.RequestTimeValve"/> </Context>
  • className="your.custom.valve.RequestTimeValve": 指定自定义 Valve 的完整类名。

5. 测试自定义 Valve:

启动 Tomcat 服务器,访问 http://localhost:8080/my-app/ (假设 my-app 是你部署的应用),查看 Tomcat 日志文件 (logs 目录下),应该能看到类似以下的日志输出:

... your.custom.valve.RequestTimeValve - Request URI: /my-app/, Remote Address: 127.0.0.1, Processing Time: 123ms

这表明自定义 Valve RequestTimeValve 已经成功部署并开始工作,记录了请求的处理时间。

5.1.5 Valve 的高级应用与注意事项

Valve 的高级应用:

  • 细粒度的访问控制: 可以根据请求的 URI、HTTP 方法、请求头、请求参数等信息,实现更细粒度的访问控制策略。

  • 请求改写与增强: 可以修改请求的 URI、请求头、请求参数等信息,或者向请求中添加额外的属性,实现请求的改写和增强。

  • 响应内容过滤与修改: 可以过滤或修改响应的内容,例如敏感信息脱敏、内容压缩、内容转换等。

  • 自定义错误页面: 可以根据不同的错误类型,返回自定义的错误页面,提升用户体验。

  • 集成第三方服务: 可以在 Valve 中集成第三方服务,例如调用外部 API 进行身份认证、日志收集、监控告警等。

使用 Valve 的注意事项:

  • 性能影响: Valve 会增加请求处理的开销,特别是复杂的 Valve 逻辑可能会显著降低性能。需要仔细评估 Valve 的性能影响,并进行必要的优化。

  • Valve 的顺序: Pipeline 中 Valve 的顺序非常重要,不同的顺序可能会导致不同的处理结果。需要仔细考虑 Valve 的执行顺序,确保 Valve 链的正确性。

  • 异常处理: 在 Valve 的 invoke() 方法中,需要妥善处理可能发生的异常,避免异常导致请求处理流程中断或Tomcat服务器崩溃。可以使用 try-catch 块捕获异常,并进行日志记录或错误处理。

  • 线程安全: Valve 实例是单例的,在多线程环境下会被并发访问。需要确保 Valve 的实现是线程安全的,避免出现线程安全问题。

  • 避免过度使用: Valve 虽然强大,但不宜过度使用。过多的 Valve 会增加请求处理的复杂性和开销,降低性能。应该根据实际需求,合理选择和配置 Valve。

5.1.6 总结

Tomcat Valve 是一个强大的组件,它允许开发者在 Tomcat 的请求处理管道中插入自定义的处理逻辑,实现各种高级特性。通过深入理解 Valve 的概念、架构、工作原理和配置使用方法,开发者可以灵活地扩展和定制 Tomcat 的功能,满足各种应用场景的需求。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U