前言
在上半部分我们分析了Tomcat请求响应的生成过程,以及对应请求容器的映射过程,就像客人去朋友家小聚,首先肯定要知道朋友的地址和门牌号码,知道之后当然就要敲门进去,一阵吃喝吹吹牛逼,再从朋友家出来返回。地址和门牌号码对应请求映射关系,吃吃喝喝对应Servlet
处理业务逻辑,回家对应返回response
。本文就对下半部分:根据门牌号码找到朋友家再返回的过程进行解析
在Tomcat请求响应处理(一)的最后,我们看到一个很长的链式调用connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)
,我们一点点来分析一下。connector.getService()
得到Connector
的父容器StandardService
,由于StandardService
是连接Tomcat两大组件的桥梁,自然getContainer()
又可以得到Container
顶层容器StandardEngine
,在Tomcat架构中各个组件及组件间关系(二)中说过每个Container
都存在一个StandardPipeline
管道,每个管道中存在一个或者多个Valve
阀门。当请求来时会按照容器的父子关系依次流入一个个管道,遇到管道中一个个阀门,既然管道有顺序,里面的阀门也有顺序,getPipeline().getFirst()
就对应着第一个阀门StandardEngineValve
,进而调用其invoke(Request, Response)
由于参数
request
中已经保存了正确的“门牌号码”,自然能得到请求对应的虚拟主机
StandardHost
,如果此时该对象为空自然有问题,将错误码塞入
response
中返回,最后责任链模式再次出现,调用
StandardHost
中管道的第一个阀门,默认情况下在
server.xml
中存在一个
Valve
,对应的实体为
AccessLogValve
,主要用来记录该虚拟主机的访问情况
其中并没有做其他的操作,仅仅调用了管道中下一个阀门,下一个阀门依然不是基础阀门,在
StandardHost
启动时Tomcat又为其添加了另一个“错误上报阀门”
getErrorReportValveClass()
返回该阀门对应全路径字符串
org.apache.catalina.valves.ErrorReportValve
,当管道中不存在对应名称的阀门就将该阀门加入管道中
该阀门的第一句直接调用了下一个阀门,我们可以把该阀门的功能理解为spring中的后置增强,即在响应之后再进行某些操作,因为该阀门是用来记录处理请求中产生错误的,而上面说过,当流程中发生错误会存在一个对应的错误码,而该错误码又封装在
response
中,那这里就不难理解为什么要在调用链返回过程中再做处理。下一个阀门就是
StandardHost
的基础阀门
StandardHostValve
,
代码清单1
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Context to be used for this Request
Context context = request.getContext();
if (context == null) {
response.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
sm.getString("standardHost.noContext"));
return;
}
// Bind the context CL to the current thread
if( context.getLoader() != null ) {
// Not started - it should check for availability first
// This should eventually move to Engine, it's generic.
if (Globals.IS_SECURITY_ENABLED) {
PrivilegedAction pa = new PrivilegedSetTccl(
context.getLoader().getClassLoader());
AccessController.doPrivileged(pa);
} else {
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
}
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(context.getPipeline().isAsyncSupported());
}
// (1)
boolean asyncAtStart = request.isAsync();
boolean asyncDispatching = request.isAsyncDispatching();
if (asyncAtStart || context.fireRequestInitEvent(request)) {
// Ask this Context to process this request. Requests that are in
// async mode and are not being dispatched to this resource must be
// in error and have been routed here to check for application
// defined error pages.
try {
if (!asyncAtStart || asyncDispatching) {
// (2)
context.getPipeline().getFirst().invoke(request, response);
} else {
// Make sure this request/response is here because an error
// report is required.
if (!response.isErrorReportRequired()) {
throw new IllegalStateException(sm.getString("standardHost.asyncStateError"));
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
container.getLogger().error("Exception Processing " + request.getRequestURI(), t);
// If a new error occurred while trying to report a previous
// error allow the original error to be reported.
if (!response.isErrorReportRequired()) {
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
throwable(request, response, t);
}
}
// Now that the request/response pair is back under container
// control lift the suspension so that the error handling can
// complete and/or the container can flush any remaining data
response.setSuspended(false);
Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// Protect against NPEs if the context was destroyed during a
// long running request.
if (!context.getState().isAvailable()) {
return;
}
// Look for (and render if found) an application level error page
if (response.isErrorReportRequired()) {
if (t != null) {
throwable(request, response, t);
} else {
status(request, response);
}
}
if (!request.isAsync() && (!asyncAtStart || !response.isErrorReportRequired())) {
context.fireRequestDestroyEvent(request);
}
}
// Access a session (if present) to update last accessed time, based on a
// strict interpretation of the specification
if (ACCESS_SESSION) {
request.getSession(false);
}
// Restore the context classloader
if (Globals.IS_SECURITY_ENABLED) {
PrivilegedAction pa = new PrivilegedSetTccl(
StandardHostValve.class.getClassLoader());
AccessController.doPrivileged(pa);
} else {
Thread.currentThread().setContextClassLoader
(StandardHostValve.class.getClassLoader());
}
}
标注(1)下的两行代码判断该请求是否异步,默认为false,Context.fireRequestInitEvent(Request)
,该方法内封装了ServletRequestEvent
事件,并由一系列的应用事件监听器applicationEventListenersObjects
负责处理,同样默认不存在具体的监听器,返回true,导致代码走到标注(2)处,再一次责任链调用StandardContext
内的第一个阀门
看到第一个if判断对
/META-INF/
和
/WEB-INF
目录下的资源进行了过滤,记得我刚学web编程时老师让我们把自定义的
Servlet
放在
/WEB-INF
下,说是不让直接访问只能内部跳转从而保证安全,那不能访问的秘密就是这段代码了。之后调用请求映射的
Wrapper
,进而
invoke
管道中的第一个阀门,对应的类为
StandardWrapperValve
图中我删除了很多非重点代码,并将主要流程分成两部分,第一红框域具体的
Servlet
有关,第二个与该
Servlet
相关的过滤器有关。我们从第一个开始分析,
wrapper.allocate()
最终会调用
StandardWrapper.loadServlet()
,
代码清单2
public synchronized Servlet loadServlet() throws ServletException {
if (unloading) {
throw new ServletException(
sm.getString("standardWrapper.unloading", getName()));
}
// (1)
// Nothing to do if we already have an instance or an instance pool
if (!singleThreadModel && (instance != null))
return instance;
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
if (servletClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
// (2)
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.notServlet", servletClass), e);
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
unavailable(null);
// Added extra log statement for Bugzilla 36630:
// http://bz.apache.org/bugzilla/show_bug.cgi?id=36630
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// (3)
if (multipartConfigElement == null) {
MultipartConfig annotation =
servlet.getClass().getAnnotation(MultipartConfig.class);
if (annotation != null) {
multipartConfigElement =
new MultipartConfigElement(annotation);
}
}
processServletSecurityAnnotation(servlet.getClass());
// Special handling for ContainerServlet instances
if ((servlet instanceof ContainerServlet) &&
(isContainerProvidedServlet(servletClass) ||
((Context) getParent()).getPrivileged() )) {
((ContainerServlet) servlet).setWrapper(this);
}
classLoadTime=(int) (System.currentTimeMillis() -t1);
if (servlet instanceof SingleThreadModel) {
if (instancePool == null) {
instancePool = new Stack();
}
singleThreadModel = true;
}
// (4)
initServlet(servlet);
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
return servlet;
}
标注(1)判断该Servlet
是否为单例,默认Servlet
是多例的,如果实现一个过时的SingleThreadModel
标记接口,Tomcat就会将标识singleThreadModel
置为true,而这里就会直接返回;否则进入标注(2)根据解析的servletClass
反射创建出用户自己配置的Servlet
。标注(3)是在Servlet3.0
中引入的注解形式的文件上传方式校验,标注(4)最终会调用Servlet
的init(ServletConfig)
,该方法GenericServlet.init(ServletConfig)
,内部又调用了一个无参的init()
,当我们创建Servlet
时可以覆写该方法,从而在第一次调用Servlet
时进行一些初始化的操作
我们回到图6中的第二个红框,代码使用工厂创建了一个过滤器链filterChain
,具体创建代码如 代码清单3
public ApplicationFilterChain createFilterChain
(ServletRequest request, Wrapper wrapper, Servlet servlet) {
// get the dispatcher type
DispatcherType dispatcher = null;
if (request.getAttribute(Globals.DISPATCHER_TYPE_ATTR) != null) {
dispatcher = (DispatcherType) request.getAttribute(
Globals.DISPATCHER_TYPE_ATTR);
}
String requestPath = null;
Object attribute = request.getAttribute(
Globals.DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null){
requestPath = attribute.toString();
}
// If there is no servlet to execute, return null
if (servlet == null)
return (null);
boolean comet = false;
// Create and initialize a filter chain object
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
comet = req.isComet();
if (Globals.IS_SECURITY_ENABLED) {
// Security: Do not recycle
filterChain = new ApplicationFilterChain();
if (comet) {
req.setFilterChain(filterChain);
}
} else {
// (1)
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
// (2)
filterChain.setServlet(servlet);
filterChain.setSupport
(((StandardWrapper)wrapper).getInstanceSupport());
// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0))
return (filterChain);
// Acquire the information we will need to match filter mappings
String servletName = wrapper.getName();
// Add the relevant path-mapped filters to this filter chain
// (3)
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
continue;
}
boolean isCometFilter = false;
if (comet) {
try {
isCometFilter = filterConfig.getFilter() instanceof CometFilter;
} catch (Exception e) {
// Note: The try catch is there because getFilter has a lot of
// declared exceptions. However, the filter is allocated much
// earlier
Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(t);
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} else {
filterChain.addFilter(filterConfig);
}
}
// Add filters that match on servlet name second
// (4)
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
continue;
}
boolean isCometFilter = false;
if (comet) {
try {
isCometFilter = filterConfig.getFilter() instanceof CometFilter;
} catch (Exception e) {
// Note: The try catch is there because getFilter has a lot of
// declared exceptions. However, the filter is allocated much
// earlier
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} else {
filterChain.addFilter(filterConfig);
}
}
// Return the completed filter chain
return (filterChain);
}
标注(1)和(2)创建出过滤器链并将请求与之对应的Servlet
与之关联,通过StandardContext
得到web.xml
中配置的所有
对应的实体FilterMap
数组,该数组中的数据来源逆序调用为StandardContext -> addFilterMap(FilterMap) -> WebXml.configureContext(Context) -> ContextConfig.webConfig() -> ContextConfig.configureStart() -> ContextConfig检测到Lifecycle.CONFIGURE_START_EVENT事件
,具体的流程分析可以参考Tomcat架构中各个组件及组件间关系(二)。还有一点需要注意的是,每一个过滤器都存在一个类型为DispatcherType
的调度类型dispatcher
,表示该过滤器对哪一种请求类型进行拦截,共有FORWORD
、INCLUDE
、REQUEST
、ASYNC
、ERROR
五种类型,默认为REQUEST
标注(3)遍历所有的过滤器数组,进行两轮匹配判断,第一轮matchDispatcher(FilterMap, DispatcherType)
判断是否filter
配置的调度类型在上述五种类型之中;第二轮matchFiltersURL(FilterMap, String)
判断请求URL
是否命中一个filter
,如若存在一个匹配过滤器,那么根据对应filter
名称从StandardContext
中的成员变量HashMap
中得到对应的实例ApplicationFilterConfig
。之前的文章曾经分析过在解析web.xml
时,过滤器对应的对象为FilterDef
,但在StandardContext
启动时中间有一步是启动所有的过滤器,此时会将所有的FilterDef
转成ApplicationFilterConfig
放入该Map
中
要理解标注(4)必须先回忆一下
配置的方式,要拦截请求其实有两种方式:1.配置
过滤请求路径;2.配置
过滤特定Servlet
,那代码中的两个for
循环就对应两种方式了。最后将请求路径或者请求对应Servlet
的过滤器通过addFilter(filterConfig)
加入到ApplicationFilterChain
中成员变量filters
数组中
回到图6第二个红框中最后一句,终于见到了我们熟悉的doFilter(Request, Response)
,内部最终走到ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse)
,见代码清单4
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
filter = filterConfig.getFilter();
support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
filter, request, response);
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
if (Globals.IS_SECURITY_ENABLED) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[] {req, res, this};
SecurityUtil.doAsPrivilege
("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response);
} catch (IOException e) {
if (filter != null) {
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response, e);
}
throw e;
} catch (ServletException e) {
if (filter != null) {
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response, e);
}
throw e;
} catch (RuntimeException e) {
if (filter != null) {
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response, e);
}
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
if (filter != null) {
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response, e);
}
throw new ServletException
(sm.getString("filterChain.filter"), e);
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
servlet, request, response);
if (request.isAsyncSupported()
&& !support.getWrapper().isAsyncSupported()) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse)) {
if (Globals.IS_SECURITY_ENABLED) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[] {req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
servlet.service(request, response);
}
} else {
servlet.service(request, response);
}
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response);
} catch (IOException e) {
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response, e);
throw e;
} catch (ServletException e) {
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response, e);
throw e;
} catch (RuntimeException e) {
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response, e);
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response, e);
throw new ServletException
(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
虽然代码比较长但是结构还是很清晰的,主要分为上下两部分。第一部分由整个if
块包裹,pos
是当前遍历ApplicationFilter
元素对应的数组下标,n
为整个数组长度,总的意思就是遍历过滤器数组中每一个filter
,并依次调用它的doFilter(ServletRequest, ServletResponse)
,该方法就由我们自己实现了。当所有的filter
处理完毕走到第二部分,就调用serlvet.service(request, response)
。至此Tomcat整个请求响应处理的过程分析完毕
后记
对于Tomcat源码的解析暂时告一段落了,我从整个过程中学到了很多。好的代码就像好的作文一样,对程序员的影响是潜移默化的,很难想象一个从来不看范文的人能写出多好的文章