原文地址:https://www.iteye.com/blog/gearever-1540028
源码面前,了无秘密
----侯捷
在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为例,
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[i].getName() + "=" +
- rcookies[i].getValue() + "; domain=" +
- rcookies[i].getDomain() + "; path=" + rcookies[i].getPath());
- }
- String rhnames[] = response.getHeaderNames();
- for (int i = 0; i < rhnames.length; i++) {
- String rhvalues[] = response.getHeaderValues(rhnames[i]);
- for (int j = 0; j < rhvalues.length; j++)
- log.info(" header=" + rhnames[i] + "=" + 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等,会有专门一章来讲讲。
微信公众号: 架构师日常笔记 欢迎关注!