设为首页 收藏本站
查看: 593|回复: 0

[经验分享] tomcat架构分析(valve源码导读)

[复制链接]

尚未签到

发表于 2017-1-31 06:37:49 | 显示全部楼层 |阅读模式
出处:http://gearever.iteye.com
源码面前,了无秘密
                             ----侯捷
在tomcat架构分析(valve机制)(http://gearever.iteye.com/blog/1536022)里已经对valve的机制做了分析。现在通过源码来加深下理解。侯捷说过,源码面前,了无秘密。通过这些代码,可以看到在tomcat中我们经常碰到的一些现象或配置是怎么实现的。
StandardEngineValve
看一下StandardEngineValve的调用逻辑;

public final void invoke(Request request, Response response)
throws IOException, ServletException {
// 定位host
Host host = request.getHost();
if (host == null) {
......
return;
}
// 调用host的第一个valve
host.getPipeline().getFirst().invoke(request, response);
}

可以清晰的看到,根据request定位到可以处理的host对象,同时,开始从头调用host里的pipeline上的valve。
StandardHostValve
看一下StandardHostValve的调用逻辑;

public final void invoke(Request request, Response response)
throws IOException, ServletException {
// 定位context
Context context = request.getContext();
if (context == null) {
......
return;
}
......
// 调用context的第一个valve
context.getPipeline().getFirst().invoke(request, response);
// 更新session
if (Globals.STRICT_SERVLET_COMPLIANCE) {
request.getSession(false);
}
// Error page processing
response.setSuspended(false);
//如果有抛异常或某个HTTP错误,导向响应的配置页面
Throwable t = (Throwable) request.getAttribute(Globals.EXCEPTION_ATTR);
if (t != null) {
throwable(request, response, t);
} else {
status(request, response);
}
// Restore the context classloader
Thread.currentThread().setContextClassLoader
(StandardHostValve.class.getClassLoader());
}

可以清晰的看到,注释部分里根据request定位到可以处理的context对象,同时,开始从头调用context里的pipeline上的valve。在调用完context的所有的valve之后(当然也是context调用完其对应的wrapper上的所有valve之后),蓝色部分显示了拿到response对象时可以做的处理。
熟悉tomcat的可能有配置错误信息的经验,例如;

<error-page>
<error-code>404</error-code>
<location>/error.jsp</location>
</error-page>

它就是为了在用户访问资源出现HTTP 404错误时,将访问重定向到一个统一的错误页面。这样做一是为了美观,另一个主要作用是不会将一些具体的错误信息例如java抛异常时的栈信息暴露给用户,主要还是出于安全的考虑。 上述代码中的注释部分就是实现这个重定向功能。
StandardContextValve
看一下StandardContextValve的调用逻辑;其代码比较多,只贴一些比较核心的吧。

public final void invoke(Request request, Response response)
throws IOException, ServletException {
......
// 定位wrapper
Wrapper wrapper = request.getWrapper();
if (wrapper == null) {
notFound(response);
return;
} else if (wrapper.isUnavailable()) {
......
}
// Normal request processing
//web.xml中配置web-app/listener/listener-class
Object instances[] = context.getApplicationEventListeners();
ServletRequestEvent event = null;
//响应request初始化事件,具体的响应listener是可配置的
......
//调用wrapper的第一个valve
wrapper.getPipeline().getFirst().invoke(request, response);
//响应request撤销事件,具体的响应listener是可配置的
......            
}

可以清晰的看到,注释部分里根据request定位到可以处理的wrapper对象,同时,开始从头调用wrapper里的pipeline上的valve。 需要注意的是,这里在调用wrapper的valve前后,分别有响应request初始化及撤销事件的逻辑,tomcat有一整套事件触发体系,这里限于篇幅就不阐述了。有时间专门说。
StandardWrapperValve
看一下StandardWrapperValve的调用逻辑;其代码比较多,只贴一些比较核心的吧;

public final void invoke(Request request, Response response)
throws IOException, ServletException {
......
requestCount++;
//定位wrapper
StandardWrapper wrapper = (StandardWrapper) getContainer();
Servlet servlet = null;
Context context = (Context) wrapper.getParent();
......
// Allocate a servlet instance to process this request
try {
if (!unavailable) {
//加载servlet
servlet = wrapper.allocate();               
}
} catch (UnavailableException e) {
......
}
......
// 根据配置建立一个filter-servlet的处理链表,servlet在链表的尾端
ApplicationFilterFactory factory =
ApplicationFilterFactory.getInstance();
ApplicationFilterChain filterChain =
factory.createFilterChain(request, wrapper, servlet);
// Reset comet flag value after creating the filter chain
request.setComet(false);
// Call the filter chain for this request
// NOTE: This also calls the servlet's service() method
try {
String jspFile = wrapper.getJspFile();
if (jspFile != null)
request.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
else
request.removeAttribute(Globals.JSP_FILE_ATTR);
if ((servlet != null) && (filterChain != null)) {
// Swallow output if needed
if (context.getSwallowOutput()) {
try {
SystemLogHandler.startCapture();
if (comet) {
filterChain.doFilterEvent(request.getEvent());
request.setComet(true);
} else {
//调用filter-servlet链表
filterChain.doFilter(request.getRequest(),
response.getResponse());
}
} finally {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
context.getLogger().info(log);
}
}
} else {
if (comet) {
request.setComet(true);
filterChain.doFilterEvent(request.getEvent());
} else {
//调用filter-servlet链表
filterChain.doFilter
(request.getRequest(), response.getResponse());
}
}
}
request.removeAttribute(Globals.JSP_FILE_ATTR);
} catch (ClientAbortException e) {
request.removeAttribute(Globals.JSP_FILE_ATTR);
throwable = e;
exception(request, response, e);
}
......
}

可以清晰的看到,注释部分里,先是能拿到相应的wrapper对象;然后完成加载wrapper对象中的servlet,例如如果是jsp,将完成jsp编译,然后加载servlet等;再然后,根据配置生成一个filter栈,通过执行栈,调用完所有的filter之后,就调用servlet,如果没有配置filter,就直接调用servlet,生成filter栈是通过request的URL模式匹配及servlet名称来实现的,具体涉及的东西在tomcat的servlet规范实现中再阐述吧。
以上,完成了一整套servlet调用的过程。通过上面的阐述,可以看见valve是个很灵活的机制,通过它可以实现很大的扩展。
Valve的应用及定制化
Tomcat除了提供上面提到的几个标准的valve实现外,也提供了一些用于调试程序的valve的实现。实现valve需要继承org.apache.catalina.valves.ValveBase基类。 以RequestDumperValve为例,
引用
org.apache.catalina.valves.RequestDumperValve

RequestDumperValve是打印出request及response信息的valve。其实现方法为:

public void invoke(Request request, Response response)
throws IOException, ServletException {
Log log = container.getLogger();
// Log pre-service information
log.info("REQUEST URI =" + request.getRequestURI());
......
log.info(" queryString=" + request.getQueryString());
......
log.info("-------------------------------------------------------");
// 调用下一个valve
getNext().invoke(request, response);
// Log post-service information
log.info("-------------------------------------------------------");
......
log.info(" contentType=" + response.getContentType());
Cookie rcookies[] = response.getCookies();
for (int i = 0; i < rcookies.length; i++) {
log.info(" cookie=" + rcookies.getName() + "=" +
rcookies.getValue() + "; domain=" +
rcookies.getDomain() + "; path=" + rcookies.getPath());
}
String rhnames[] = response.getHeaderNames();
for (int i = 0; i < rhnames.length; i++) {
String rhvalues[] = response.getHeaderValues(rhnames);
for (int j = 0; j < rhvalues.length; j++)
log.info(" header=" + rhnames + "=" + rhvalues[j]);
}
log.info(" message=" + response.getMessage());
log.info("========================================================");
}

可以很清晰的看出,它打印出了request及response的信息,其中红色部分显示它调用valve链表中的下一个valve。我们可以这样配置它;

<Host name="localhost"  appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
<Valve className="org.apache.catalina.valves.RequestDumperValve"/>
<Context path="/my" docBase=" /usr/local/tomcat/backup/my" >         
</Context>
<Context path="/my2" docBase=" /usr/local/tomcat/backup/my" >         
</Context>
</Host>

这样,只要访问此host下的所有context,都会打印出调试信息。 Valve的应用有很多,例如cluster,SSO等,会有专门一章来讲讲。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-335473-1-1.html 上篇帖子: How Tomcat Works 读书笔记(第四章) 下篇帖子: tomcat Access Log 统计相应时间
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表