阀门(Valve)常打开,快发请求过来 | Tomcat的AccessLogValve介绍

说起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那些事儿。用力点,不费电!!! :))


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值