原文地址:https://www.iteye.com/blog/gearever-1540028

源码面前,了无秘密 
                             ----侯捷 
在tomcat架构分析(valve机制)(http://gearever.iteye.com/blog/1536022)里已经对valve的机制做了分析。现在通过源码来加深下理解。侯捷说过,源码面前,了无秘密。通过这些代码,可以看到在tomcat中我们经常碰到的一些现象或配置是怎么实现的。 
StandardEngineValve 
看一下StandardEngineValve的调用逻辑; 

Java代码  收藏代码
  1. public final void invoke(Request request, Response response)  
  2.     throws IOException, ServletException {  
  3.   
  4.     // 定位host  
  5.     Host host = request.getHost();  
  6.     if (host == null) {  
  7.         ......  
  8.         return;  
  9.     }  
  10.   
  11.     // 调用host的第一个valve  
  12.     host.getPipeline().getFirst().invoke(request, response);  
  13.   
  14. }  


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

Java代码  收藏代码
  1. public final void invoke(Request request, Response response)  
  2.     throws IOException, ServletException {  
  3.   
  4.     // 定位context  
  5.     Context context = request.getContext();  
  6.     if (context == null) {  
  7.         ......  
  8.         return;  
  9.     }  
  10.   
  11.     ......  
  12.   
  13.     // 调用context的第一个valve  
  14.     context.getPipeline().getFirst().invoke(request, response);  
  15.   
  16.     // 更新session  
  17.     if (Globals.STRICT_SERVLET_COMPLIANCE) {  
  18.         request.getSession(false);  
  19.     }  
  20.   
  21.     // Error page processing  
  22.     response.setSuspended(false);  
  23.   
  24.     //如果有抛异常或某个HTTP错误,导向响应的配置页面  
  25.     Throwable t = (Throwable) request.getAttribute(Globals.EXCEPTION_ATTR);  
  26.   
  27.     if (t != null) {  
  28.         throwable(request, response, t);  
  29.     } else {  
  30.         status(request, response);  
  31.     }  
  32.   
  33.     // Restore the context classloader  
  34.     Thread.currentThread().setContextClassLoader  
  35.         (StandardHostValve.class.getClassLoader());  
  36.   
  37. }  


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

Xml代码  收藏代码
  1. <error-page>  
  2.   <error-code>404</error-code>  
  3.   <location>/error.jsp</location>  
  4.  </error-page>  


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

StandardContextValve 
看一下StandardContextValve的调用逻辑;其代码比较多,只贴一些比较核心的吧。 

Java代码  收藏代码
  1. public final void invoke(Request request, Response response)  
  2.     throws IOException, ServletException {  
  3.   
  4.     ......  
  5.   
  6.     // 定位wrapper  
  7.     Wrapper wrapper = request.getWrapper();  
  8.     if (wrapper == null) {  
  9.         notFound(response);  
  10.         return;  
  11.     } else if (wrapper.isUnavailable()) {  
  12.         ......  
  13.     }  
  14.   
  15.     // Normal request processing  
  16.     //web.xml中配置web-app/listener/listener-class  
  17.     Object instances[] = context.getApplicationEventListeners();  
  18.   
  19.     ServletRequestEvent event = null;  
  20.   
  21.     //响应request初始化事件,具体的响应listener是可配置的   
  22.     ......  
  23.     //调用wrapper的第一个valve  
  24.     wrapper.getPipeline().getFirst().invoke(request, response);   
  25.   
  26.     //响应request撤销事件,具体的响应listener是可配置的   
  27.     ......               
  28. }  


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

Java代码  收藏代码
  1. public final void invoke(Request request, Response response)  
  2.     throws IOException, ServletException {  
  3.       
  4.     ......  
  5.     requestCount++;  
  6.     //定位wrapper  
  7.     StandardWrapper wrapper = (StandardWrapper) getContainer();  
  8.     Servlet servlet = null;  
  9.     Context context = (Context) wrapper.getParent();  
  10.       
  11.     ......  
  12.   
  13.     // Allocate a servlet instance to process this request  
  14.     try {  
  15.         if (!unavailable) {  
  16.             //加载servlet  
  17.             servlet = wrapper.allocate();                  
  18.         }  
  19.     } catch (UnavailableException e) {  
  20.         ......  
  21.     }   
  22.     ......  
  23.     // 根据配置建立一个filter-servlet的处理链表,servlet在链表的尾端  
  24.     ApplicationFilterFactory factory =  
  25.         ApplicationFilterFactory.getInstance();  
  26.     ApplicationFilterChain filterChain =  
  27.         factory.createFilterChain(request, wrapper, servlet);  
  28.     // Reset comet flag value after creating the filter chain  
  29.     request.setComet(false);  
  30.   
  31.     // Call the filter chain for this request  
  32.     // NOTE: This also calls the servlet's service() method  
  33.     try {  
  34.         String jspFile = wrapper.getJspFile();  
  35.         if (jspFile != null)  
  36.             request.setAttribute(Globals.JSP_FILE_ATTR, jspFile);  
  37.         else  
  38.             request.removeAttribute(Globals.JSP_FILE_ATTR);  
  39.         if ((servlet != null) && (filterChain != null)) {  
  40.             // Swallow output if needed  
  41.             if (context.getSwallowOutput()) {  
  42.                 try {  
  43.                     SystemLogHandler.startCapture();  
  44.                     if (comet) {  
  45.                         filterChain.doFilterEvent(request.getEvent());  
  46.                         request.setComet(true);  
  47.                     } else {  
  48.                         //调用filter-servlet链表  
  49.                         filterChain.doFilter(request.getRequest(),   
  50.                                 response.getResponse());  
  51.                     }  
  52.                 } finally {  
  53.                     String log = SystemLogHandler.stopCapture();  
  54.                     if (log != null && log.length() > 0) {  
  55.                         context.getLogger().info(log);  
  56.                     }  
  57.                 }  
  58.             } else {  
  59.                 if (comet) {  
  60.                     request.setComet(true);  
  61.                     filterChain.doFilterEvent(request.getEvent());  
  62.                 } else {  
  63.                     //调用filter-servlet链表  
  64.                     filterChain.doFilter  
  65.                         (request.getRequest(), response.getResponse());  
  66.                 }  
  67.             }  
  68.   
  69.         }  
  70.         request.removeAttribute(Globals.JSP_FILE_ATTR);  
  71.     } catch (ClientAbortException e) {  
  72.         request.removeAttribute(Globals.JSP_FILE_ATTR);  
  73.         throwable = e;  
  74.         exception(request, response, e);  
  75.     }   
  76.     ......  
  77. }  


可以清晰的看到,注释部分里,先是能拿到相应的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。其实现方法为: 

Java代码  收藏代码
  1. public void invoke(Request request, Response response)   
  2.                 throws IOException, ServletException {   
  3.   
  4.         Log log = container.getLogger();   
  5.   
  6.         // Log pre-service information   
  7.         log.info("REQUEST URI =" + request.getRequestURI());   
  8.         ......   
  9.         log.info(" queryString=" + request.getQueryString());   
  10.         ......   
  11.         log.info("-------------------------------------------------------");   
  12.           
  13.         // 调用下一个valve  
  14.         getNext().invoke(request, response);   
  15.           
  16.         // Log post-service information   
  17.         log.info("-------------------------------------------------------");   
  18.         ......   
  19.         log.info(" contentType=" + response.getContentType());   
  20.         Cookie rcookies[] = response.getCookies();   
  21.         for (int i = 0; i < rcookies.length; i++) {   
  22.             log.info(" cookie=" + rcookies[i].getName() + "=" +   
  23.                 rcookies[i].getValue() + "; domain=" +   
  24.                 rcookies[i].getDomain() + "; path=" + rcookies[i].getPath());   
  25.         }   
  26.         String rhnames[] = response.getHeaderNames();   
  27.         for (int i = 0; i < rhnames.length; i++) {   
  28.             String rhvalues[] = response.getHeaderValues(rhnames[i]);   
  29.             for (int j = 0; j < rhvalues.length; j++)   
  30.             log.info(" header=" + rhnames[i] + "=" + rhvalues[j]);            
  31.         }  
  32.         log.info(" message=" + response.getMessage());   
  33.         log.info("========================================================");   
  34.   
  35. }  


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

Xml代码  收藏代码
  1. <Host name="localhost"  appBase="webapps"  
  2.             unpackWARs="true" autoDeploy="true"  
  3.             xmlValidation="false" xmlNamespaceAware="false">  
  4.       <Valve className="org.apache.catalina.valves.RequestDumperValve"/>  
  5.       <Context path="/my" docBase=" /usr/local/tomcat/backup/my" >              
  6.       </Context>  
  7.       <Context path="/my2" docBase=" /usr/local/tomcat/backup/my" >             
  8.       </Context>  
  9. </Host>  


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

posted on 2019-09-03 14:20  一天不进步,就是退步  阅读(209)  评论(0编辑  收藏  举报