写这篇博客的灵感来自于最近做的一个网关系统,需要把响应流量按时间序列记录到数据库中。当我准备开始写这篇博客的时候,就在想如何以简洁的话来描述Valve以及它有什么使用场景呢?
它的作用个人总结为:org.apache.catalina.Valve是Tomcat中各个连接某些org.apache.catalina.Contained实例的责任链抽象接口
它的使用场景:能在更高层次(Filter甚至Host之前)处理Request和Response对象
1,Tomcat架构简介
我们先来看看Tomcat的架构图和server.xml文件
1
2
3
4
7
8
9
10
11
12
13
14
17
18
21 22 type="org.apache.catalina.UserDatabase" 23 description="User database that can be updated and saved" 24 factory="org.apache.catalina.users.MemoryUserDatabaseFactory" 25 pathname="conf/tomcat-users.xml" /> 26 27 28 33 34 35 36 40 41 42 49 50 connectionTimeout="20000" 51 redirectPort="8443" /> 52 53 59 67 76 82 94 95 96 102 103 108 109 112 113 114 117 120 121 123 124 128 129 resourceName="UserDatabase"/> 130 131 132 133 unpackWARs="true" autoDeploy="true"> 134 135 137 140 141 144 145 prefix="localhost_access_log" suffix=".txt" 146 pattern="%h %l %u %t "%r" %s %b" /> 147 148 149 150 151 这里我们看xml文件,这里我们看到Server节点,Service节点,Engine节点和Host节点。四个节点是依次是包含的关系,也对应了架构图中的Server,Service,Engine和Host四个板块的包含关系。从架构图我们能看到Engine,Host和Context之间有个Valve。 2,Valve接口 我们来看下org.apache.catalina.Valve接口的源码 package org.apache.catalina; import java.io.IOException; import javax.servlet.ServletException; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; /** * A Valve is a request processing component associated with a * particular Container. A series of Valves are generally associated with * each other into a Pipeline. The detailed contract for a Valve is included * in the description of the * * HISTORICAL NOTE: The "Valve" name was assigned to this concept * because a valve is what you use in a real world pipeline to control and/or * modify flows through it. * * @author Craig R. McClanahan * @author Gunnar Rjnning * @author Peter Donald */ public interface Valve { //-------------------------------------------------------------- Properties /** * @return the next Valve in the pipeline containing this Valve, if any. */ public Valve getNext(); /** * Set the next Valve in the pipeline containing this Valve. * * @param valve The new next valve, or */ public void setNext(Valve valve); //---------------------------------------------------------- Public Methods /** * Execute a periodic task, such as reloading, etc. This method will be * invoked inside the classloading context of this container. Unexpected * throwables will be caught and logged. */ public void backgroundProcess(); /** * Perform request processing as required by this Valve.invoke()
method below.null
if none
*
*
An individual Valve MAY perform the following actions, in
* the specified order:
*
- Examine and/or modify the properties of the specified Request and
* Response.
*
- Examine the properties of the specified Request, completely generate
* the corresponding Response, and return control to the caller.
*
- Examine the properties of the specified Request and Response, wrap
* either or both of these objects to supplement their functionality,
* and pass them on.
*
- If the corresponding Response was not generated (and control was not
* returned, call the next Valve in the pipeline (if there is one) by
* executing
getNext().invoke()
.*
- Examine, but not modify, the properties of the resulting Response
* (which was created by a subsequently invoked Valve or Container).
*
*
*
*
A Valve MUST NOT do any of the following things:
*
- Change request properties that have already been used to direct
* the flow of processing control for this request (for instance,
* trying to change the virtual host to which a Request should be
* sent from a pipeline attached to a Host or Context in the
* standard implementation).
*
- Create a completed Response AND pass this
* Request and Response on to the next Valve in the pipeline.
*
- Consume bytes from the input stream associated with the Request,
* unless it is completely generating the response, or wrapping the
* request before passing it on.
*
- Modify the HTTP headers included with the Response after the
*
getNext().invoke()
method has returned.*
- Perform any actions on the output stream associated with the
* specified Response after the
getNext().invoke()
method has* returned.
*
*
*
* @param request The servlet request to be processed
* @param response The servlet response to be created
*
* @exception IOException if an input/output error occurs, or is thrown
* by a subsequently invoked Valve, Filter, or Servlet
* @exception ServletException if a servlet error occurs, or is thrown
* by a subsequently invoked Valve, Filter, or Servlet
*/
public void invoke(Request request, Response response)
throws IOException, ServletException;
public boolean isAsyncSupported();
}
getNext和setNext方法主要是获取/设置当前这个责任链节点的上一个节点;处理逻辑的是方法invoke;isAsyncSupported方法是表示是否支持异步。例如org.apache.catalina.valves.AccessLogValve日志记录组件就是异步的,该组件是用来记录访问日志。
3,自定义Valve
这里我们使用两种方式来自定义Valve
1)原生Tomcat使用自定义Valve
2)SpringBoot使用自定义Valve
自定义Valve代码如下:
package net.sunmonkey.valves;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import javax.servlet.ServletException;
import java.io.IOException;
/**
* @author Wenqin Cheng
* @date 2020/12/30
*/
public class MyValve implements Valve {
private Valve next;
@Override
public Valve getNext() {
return next;
}
@Override
public void setNext(Valve valve) {
next = valve;
}
@Override
public void backgroundProcess() {
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
System.out.println("=====================start===================");
System.out.println("#getNext().getClass().getName(): "+getNext().getClass().getName());
System.out.println(this.getClass().getName()+"#invoke");
System.out.println("request: "+request);
System.out.println("response: "+response);
System.out.println("request.getServletPath():"+request.getServletPath());
System.out.println("request.getQueryString():"+request.getQueryString());
//例如这里可以获取请求体长度,用来记录请求流量
System.out.println("request.getContentLength(): "+request.getContentLength());
//例如获取响应的流量
System.out.println("response.getBytesWritten(false): "+response.getBytesWritten(false));
System.out.println("==================end======================");
getNext().invoke(request, response);
}
@Override
public boolean isAsyncSupported() {
return true;
}
}
3.1,原生Tomcat使用自定义Valve
我们只需要在server.xml配置文件中配置就可以。如下
3.2,SpringBoot使用自定义Valve
后续更新...
至此,我们访问Tomcat的任何一个页面。可以在控制台找那个看到日志