写这篇博客的灵感来自于最近做的一个网关系统,需要把响应流量按时间序列记录到数据库中。当我准备开始写这篇博客的时候,就在想如何以简洁的话来描述Valve以及它有什么使用场景呢?
它的作用个人总结为:org.apache.catalina.Valve是Tomcat中各个连接某些org.apache.catalina.Contained实例的责任链抽象接口
它的使用场景:能在更高层次(Filter甚至Host之前)处理Request和Response对象
1,Tomcat架构简介
我们先来看看Tomcat的架构图和server.xml文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <Server port="8005" shutdown="SHUTDOWN"> 3 <Listener className="org.apache.catalina.startup.VersionLoggerListener" /> 4 <!-- Security listener. Documentation at /docs/config/listeners.html 5 <Listener className="org.apache.catalina.security.SecurityListener" /> 6 --> 7 <!--APR library loader. Documentation at /docs/apr.html --> 8 <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> 9 <!-- Prevent memory leaks due to use of particular java/javax APIs--> 10 <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /> 11 <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> 12 <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /> 13 14 <!-- Global JNDI resources 15 Documentation at /docs/jndi-resources-howto.html 16 --> 17 <GlobalNamingResources> 18 <!-- Editable user database that can also be used by 19 UserDatabaseRealm to authenticate users 20 --> 21 <Resource name="UserDatabase" auth="Container" 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 </GlobalNamingResources> 27 28 <!-- A "Service" is a collection of one or more "Connectors" that share 29 a single "Container" Note: A "Service" is not itself a "Container", 30 so you may not define subcomponents such as "Valves" at this level. 31 Documentation at /docs/config/service.html 32 --> 33 <Service name="Catalina"> 34 35 <!--The connectors can use a shared executor, you can define one or more named thread pools--> 36 <!-- 37 <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" 38 maxThreads="150" minSpareThreads="4"/> 39 --> 40 41 42 <!-- A "Connector" represents an endpoint by which requests are received 43 and responses are returned. Documentation at : 44 Java HTTP Connector: /docs/config/http.html 45 Java AJP Connector: /docs/config/ajp.html 46 APR (HTTP/AJP) Connector: /docs/apr.html 47 Define a non-SSL/TLS HTTP/1.1 Connector on port 8080 48 --> 49 <Connector port="8080" protocol="HTTP/1.1" 50 connectionTimeout="20000" 51 redirectPort="8443" /> 52 <!-- A "Connector" using the shared thread pool--> 53 <!-- 54 <Connector executor="tomcatThreadPool" 55 port="8080" protocol="HTTP/1.1" 56 connectionTimeout="20000" 57 redirectPort="8443" /> 58 --> 59 <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 60 This connector uses the NIO implementation. The default 61 SSLImplementation will depend on the presence of the APR/native 62 library and the useOpenSSL attribute of the 63 AprLifecycleListener. 64 Either JSSE or OpenSSL style configuration may be used regardless of 65 the SSLImplementation selected. JSSE style configuration is used below. 66 --> 67 <!-- 68 <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" 69 maxThreads="150" SSLEnabled="true"> 70 <SSLHostConfig> 71 <Certificate certificateKeystoreFile="conf/localhost-rsa.jks" 72 type="RSA" /> 73 </SSLHostConfig> 74 </Connector> 75 --> 76 <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2 77 This connector uses the APR/native implementation which always uses 78 OpenSSL for TLS. 79 Either JSSE or OpenSSL style configuration may be used. OpenSSL style 80 configuration is used below. 81 --> 82 <!-- 83 <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol" 84 maxThreads="150" SSLEnabled="true" > 85 <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" /> 86 <SSLHostConfig> 87 <Certificate certificateKeyFile="conf/localhost-rsa-key.pem" 88 certificateFile="conf/localhost-rsa-cert.pem" 89 certificateChainFile="conf/localhost-rsa-chain.pem" 90 type="RSA" /> 91 </SSLHostConfig> 92 </Connector> 93 --> 94 95 <!-- Define an AJP 1.3 Connector on port 8009 --> 96 <!-- 97 <Connector protocol="AJP/1.3" 98 address="::1" 99 port="8009" 100 redirectPort="8443" /> 101 --> 102 103 <!-- An Engine represents the entry point (within Catalina) that processes 104 every request. The Engine implementation for Tomcat stand alone 105 analyzes the HTTP headers included with the request, and passes them 106 on to the appropriate Host (virtual host). 107 Documentation at /docs/config/engine.html --> 108 109 <!-- You should set jvmRoute to support load-balancing via AJP ie : 110 <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1"> 111 --> 112 <Engine name="Catalina" defaultHost="localhost"> 113 114 <!--For clustering, please take a look at documentation at: 115 /docs/cluster-howto.html (simple how to) 116 /docs/config/cluster.html (reference documentation) --> 117 <!-- 118 <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/> 119 --> 120 121 <!-- Use the LockOutRealm to prevent attempts to guess user passwords 122 via a brute-force attack --> 123 <Realm className="org.apache.catalina.realm.LockOutRealm"> 124 <!-- This Realm uses the UserDatabase configured in the global JNDI 125 resources under the key "UserDatabase". Any edits 126 that are performed against this UserDatabase are immediately 127 available for use by the Realm. --> 128 <Realm className="org.apache.catalina.realm.UserDatabaseRealm" 129 resourceName="UserDatabase"/> 130 </Realm> 131 132 <Host name="localhost" appBase="webapps" 133 unpackWARs="true" autoDeploy="true"> 134 135 <!-- SingleSignOn valve, share authentication between web applications 136 Documentation at: /docs/config/valve.html --> 137 <!-- 138 <Valve className="org.apache.catalina.authenticator.SingleSignOn" /> 139 --> 140 141 <!-- Access log processes all example. 142 Documentation at: /docs/config/valve.html 143 Note: The pattern used is equivalent to using pattern="common" --> 144 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" 145 prefix="localhost_access_log" suffix=".txt" 146 pattern="%h %l %u %t "%r" %s %b" /> 147 148 </Host> 149 </Engine> 150 </Service> 151 </Server>
这里我们看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; /** * <p>A <b>Valve</b> 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 <code>invoke()</code> method below.</p> * * <b>HISTORICAL NOTE</b>: 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 <code>null</code> if none */ 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(); /** * <p>Perform request processing as required by this Valve.</p> * * <p>An individual Valve <b>MAY</b> perform the following actions, in * the specified order:</p> * <ul> * <li>Examine and/or modify the properties of the specified Request and * Response. * <li>Examine the properties of the specified Request, completely generate * the corresponding Response, and return control to the caller. * <li>Examine the properties of the specified Request and Response, wrap * either or both of these objects to supplement their functionality, * and pass them on. * <li>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 <code>getNext().invoke()</code>. * <li>Examine, but not modify, the properties of the resulting Response * (which was created by a subsequently invoked Valve or Container). * </ul> * * <p>A Valve <b>MUST NOT</b> do any of the following things:</p> * <ul> * <li>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). * <li>Create a completed Response <strong>AND</strong> pass this * Request and Response on to the next Valve in the pipeline. * <li>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. * <li>Modify the HTTP headers included with the Response after the * <code>getNext().invoke()</code> method has returned. * <li>Perform any actions on the output stream associated with the * specified Response after the <code>getNext().invoke()</code> method has * returned. * </ul> * * @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的任何一个页面。可以在控制台找那个看到日志