说起Tomcat的组件,Server、Host、Context这些兄弟个个都名头响的很,但其实这些组件后面,还有一些不起眼,但却至关重要的组件,今天要介绍的这个就是其中的一个。
这个组件是阀,英文名Valve,用于特定容器(Container)中的请求流的处理,多个Valve互相关联组成一个Pipeline。它是现实世界的管道中阀的抽象,和管道中的阀门作用一致,都用于按照设定控制管道内流的流向。
Tomcat中的Valve,最主要作用自然是按照Valve中的业务逻辑处理Request和Response。
此处,还会做以下一系列的操作:
验证和修改特定的请求和响应中的properties
包装已经包含的各类请求参数
控制响应信息
根据请求和响应中的数据,确认是否进入下一个Valve的流程
当然,和现实世界中的阀的区别是Tomcat中的Valve不可以做以下操作:
在自己内部生成完整的Request和Response传入到其它的Valve中,
不可以在生成Response前消费掉Request,
不可以在其它Valve返回后再修改Http header。
...
这群Valve中,最为大家熟知的当数AccessLogValve,由于是默认配置到localhost这个虚拟主机里的,这家伙每天都在辛勤的生成请求日志。
我们在Tomcat的logs目录下看到的这些访问日志文件,都源于它的努力。
其中红框内的文件前缀是当前虚拟主机的名称,由于默认使用localhost,所以配置中命名时就成了localhost_access_log,附加文件生成日期。
我们看这个Valve是怎么样被调用的,看下面的调用栈:
是在CoyoteAdapter的service方法中被调用的,触发时这个样子:
request.getMappingData().context.logAccess(request, response,System.currentTimeMillis() - req.getStartTime(),false);
此时,会调用到ContainerBase的logAccess方法,内容见下面:
/**
* Check this container for an access log and if none is found, look to the
* parent. If there is no parent and still none is found, use the NoOp
* access log.
*/
public void logAccess(Request request, Response response, long time,
boolean useDefault) {
boolean logged = false;
if (getAccessLog() != null) {
getAccessLog().log(request, response, time); //具体调用
logged = true;
}
if (getParent() != null) {
// No need to use default logger once request/response has been logged
getParent().logAccess(request, response, time, (useDefault && !logged)); //注意这里的getParent
}
}
在getAccessLog的时候,会将当前容器内的Pipeline中所有Valve取出
Valve valves[] = getPipeline().getValves();
for (Valve valve : valves) {
if (valve instanceof AccessLog) {
if (adapter == null) {
adapter = new AccessLogAdapter((AccessLog) valve);
} else {
adapter.add((AccessLog) valve);
}
}
}
而如果当前Container中没有AccessLog,就继续调用其父容器的logAccess,此处ContainerBase是所有容器的父类。
在AccessLog不为null的时候,则生成AccessLogAdapter最终记录日志。
而整个记日志的过程,是按照定义的格式将数据记录下来,其中的AccessLogValve中配置的pattern,最终体现到AccessLogValve中是一个个具体的
AccessLogElement,例如客户端请求IP,则是通过下面的Element体现的
/**
* write remote IP address - %a //pattern中具体的符号代表
*/
protected class RemoteAddrElement implements AccessLogElement {
@Override
public void addElement(CharArrayWriter buf, Date date, Request request,
Response response, long time) {
if (requestAttributesEnabled) {
Object addr = request.getAttribute(REMOTE_ADDR_ATTRIBUTE);
if (addr == null) {
buf.append(request.getRemoteAddr());
} else {
buf.append(addr.toString());
}
} else {
buf.append(request.getRemoteAddr());
}
}
}
这里的各个element生成的代码如下,可以参考其具体含义配置,生成符合自己需求的log格式:
/**
* parse pattern string and create the array of AccessLogElement
*/
protected AccessLogElement[] createLogElements() {
List<AccessLogElement> list = new ArrayList<>();
boolean replace = false;
StringBuilder buf = new StringBuilder();
for (int i = 0; i < pattern.length(); i++) {
char ch = pattern.charAt(i);
... //这里是遍历pattern,并根据具体的项,生成对应的Element
}
/**
* create an AccessLogElement implementation which needs header string
*/
protected AccessLogElement createAccessLogElement(String header, char pattern) {
switch (pattern) {
case 'i':
return new HeaderElement(header);
case 'c':
return new CookieElement(header);
case 's':
return new SessionAttributeElement(header);
case 't':
return new DateAndTimeElement(header);
}
具体的log文件生成之后,如果轮换等,这里不再上源码,各位根据上面的,各取所需。
长按下方二维码,关注Tomcat那些事儿。用力点,不费电!!! :))