一 概述
前一章节已经讲解到了tomcat把一次请求封装成了HttpServletRequest对象,接下来request对象会在关联的管道中流转,这里我们就看看容器组件与管道,阀门的关联关系,管道如何从独立的形态相互串联起来的。
二 阀与管道关系
这里我们接上一章节中的org.apache.catalina.connector.CoyoteAdapter类的service()方法
/**
* Service method.
*/
public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res)
throws Exception {
Request request = (Request) req.getNote(ADAPTER_NOTES);//判断是否绑定了HttpServletRequest
Response response = (Response) res.getNote(ADAPTER_NOTES);//判断是否绑定了HttpServletResponse
if (request == null) {
// Create objects
request = connector.createRequest();//连接器创建一个http请求对象request
request.setCoyoteRequest(req);//设置该http请求对象的真正req对象
response = connector.createResponse();
response.setCoyoteResponse(res);
// Link objects
request.setResponse(response);//将请求对象与响应对象绑定相关联
response.setRequest(request);
// Set as notes
req.setNote(ADAPTER_NOTES, request);
res.setNote(ADAPTER_NOTES, response);
// Set query string encoding
req.getParameters().setQueryStringEncoding(connector.getURIEncoding());
}
if (connector.getXpoweredBy()) {
response.addHeader("X-Powered-By", POWERED_BY);
}
boolean comet = false;
boolean async = false;
try {
// Parse and set Catalina and configuration specific
// request parameters
req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
if (postParseRequest(req, request, res, response)) {//将当前http请求对象request与相关的host,Context、Wrapper对象引用相绑定。
//check valves if we support async
request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);//将真正的请求处理交给Engine的Pipeline去处理
if (request.isComet()) {
if (!response.isClosed() && !response.isError()) {
if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
// Invoke a read event right away if there are available bytes
if (event(req, res, SocketStatus.OPEN)) {
comet = true;
res.action(ActionCode.ACTION_COMET_BEGIN, null);
}
} else {
comet = true;
res.action(ActionCode.ACTION_COMET_BEGIN, null);
}
} else {
// Clear the filter chain, as otherwise it will not be reset elsewhere
// since this is a Comet request
request.setFilterChain(null);
}
}
}
AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
if (asyncConImpl!=null && asyncConImpl.getState()==AsyncContextImpl.AsyncState.STARTED) {
res.action(ActionCode.ACTION_ASYNC_START, request.getAsyncContext());
async = true;
} else if (request.isAsyncDispatching()) {
asyncDispatch(req, res, SocketStatus.OPEN);
if (request.isAsyncStarted()) {
async = true;
res.action(ActionCode.ACTION_ASYNC_START, request.getAsyncContext());
}
} else if (!comet) {
response.finishResponse();
req.action(ActionCode.ACTION_POST_REQUEST , null);
}
} catch (IOException e) {
// Ignore
} catch (Throwable t) {
log.error(sm.getString("coyoteAdapter.service"), t);
} finally {
req.getRequestProcessor().setWorkerThreadName(null);
// Recycle the wrapper request and response
if (!comet && !async) {
request.recycle();
response.recycle();
} else {
// Clear converters so that the minimum amount of memory
// is used by this processor
request.clearEncoders();
response.clearEncoders();
}
}
}
我们重点分析代码connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
connector.getService() :方法拿到的是connector相关联的service容器对象默认的都是StandardService
getContainer():方法拿到的是StandardEngine对象。这些对象是如何相互交织在一起在 《tomcat原理解析(三):资源初始化》 中已经详细介绍了。StandardEngine对象调用getPipeline()方法,因为StandardEngine类继承了ContainerBase类所以getPipeline()方法执行的是父类的方法。查看父类的代码org.apache.catalina.core.ContainerBase的getPipeline方法如下:
public Pipeline getPipeline() {
return (this.pipeline);
}
而返回的this.pipeline对象是这样初始化的protected Pipeline pipeline= CatalinaFactory.getFactory().createPipeline(this); 查看createPipeline方法的实现
public Pipeline createPipeline(Container container) {
Pipeline pipeline = new StandardPipeline();
pipeline.setContainer(container);
return pipeline;
}
也就是说getPipeline()返回的StandardPipeline对象。从这里可以看出StandardEngine,StandardHost,StandardContext,StandardWrapper标准容器类都继承了Container-base类,每个容器对象都有一个管道对象,每个管道对象必然会有一个或多个的阀门。也就是说容器包含管道,管道包含阀门。
管道实现了Pipeline接口

Pipeline接口如上图
getFirst:拿到第一个阀门
getValves:拿到一组阀门
addValve:添加一个阀门
getBasic:取一个默认的阀门
setBasic:设置一个默认的阀门
阀门实现了Valve接口。查看接口代码如下图:

setNext:设置下一个阀门getNext:获取下一个阀门invoke:执行阀门的内部自定义代码处理我们再次回到管道Pipeline接口的标准实现StandardPipeline类中,里面有3个属性protected Valve basic = null; 保存当前管道的基础阀门,也就是默认的阀门protected Container container = null; 保存当前管道关联的容器对象protected Valve first = null;//保存当前阀门的初始阀门引用对象再看看当前管道的getFirst()实现
public Valve getFirst() {
if (first != null) {
return first;
}
return basic;
}
当管道没有新加入的阀门时,直接返回默认的也就是基础的阀门。当新添加阀门时逻辑处理如下
public void addValve(Valve valve) {
// Validate that we can add this Valve
if (valve instanceof Contained)
((Contained) valve).setContainer(this.container);
// Start the new component if necessary
if (getState().isAvailable()) {
if (valve instanceof Lifecycle) {
try {
((Lifecycle) valve).start();
} catch (LifecycleException e) {
log.error("StandardPipeline.addValve: start: ", e);
}
}
}
// Add this Valve to the set associated with this Pipeline
if (first == null) {
first = valve;
valve.setNext(basic);
} else {
Valve current = first;
while (current != null) {
if (current.getNext() == basic) {
current.setNext(valve);
valve.setNext(basic);
break;
}
current = current.getNext();
}
}
container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
}
1.当管道中第一个阀门不存在,则将新加入的阀门设置为第一个,同时将基础阀门设置为最后的阀门
2.当管道存在第一个阀门时,则将新加入的阀门放在所有普通阀门的最后面(除去基础阀门),基础阀门永远存放在普通阀门最后面
三 管道相互串联
到这里我们已经概述了tomcat里面容器与管道和阀门(自定义的阀门和系统自带的基础阀门)的关系。 我们再次回到容器中管道调用的入口代码connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);这里首先拿到的是StandardEngine容器相关的管道,所有阀门中最后执行了基础的阀门org.apache.catalina.coreStandardEngineValve,调用invoke方法。代码如下:
invoke方法中最后一行的代码又执行了关联的host容器(默认是org.apache.catalina.core.StandardHost),我们看看StandardHost类的构造函数代码如下:public final void invoke(Request request, Response response) throws IOException, ServletException { // Select the Host to be used for this Request Host host = request.getHost(); if (host == null) { response.sendError (HttpServletResponse.SC_BAD_REQUEST, sm.getString("standardEngine.noHost", request.getServerName())); return; } if (request.isAsyncSupported()) { request.setAsyncSupported(host.getPipeline().isAsyncSupported()); } // Ask this Host to process this request host.getPipeline().getFirst().invoke(request, response); }
public StandardHost() { super(); pipeline.setBasic(new StandardHostValve()); }
设置了host容器中管道的默认阀门是org.apache.catalina.core.StandardHostValve。再次看看阀门中的invoke方法实现如下:
查看方法中的context.getPipeline().getFirst().invoke(request, response);代码。它又调用了关联的context容器(org.apache.catalina.core.StandardContext)中管道的阀门。我们看看StandardContext类的构造函数代码如下: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<Void> pa = new PrivilegedSetTccl( context.getLoader().getClassLoader()); AccessController.doPrivileged(pa); } else { Thread.currentThread().setContextClassLoader (context.getLoader().getClassLoader()); } } if (request.isAsyncSupported()) { request.setAsyncSupported(context.getPipeline().isAsyncSupported()); } // Ask this Context to process this request context.getPipeline().getFirst().invoke(request, response); // Access a session (if present) to update last accessed time, based on a // strict interpretation of the specification if (ACCESS_SESSION) { request.getSession(false); } // Error page processing response.setSuspended(false); Throwable t = (Throwable) request.getAttribute(Globals.EXCEPTION_ATTR); if (t != null) { throwable(request, response, t); } else { status(request, response); } // Restore the context classloader if (Globals.IS_SECURITY_ENABLED) { PrivilegedAction<Void> pa = new PrivilegedSetTccl( StandardHostValve.class.getClassLoader()); AccessController.doPrivileged(pa); } else { Thread.currentThread().setContextClassLoader (StandardHostValve.class.getClassLoader()); } }
它给context容器中的管道设置了默认的阀门org.apache.catalina.core.StandardContextValve。查看该阀门的invoke方法实现代码如下:public StandardContext() { super(); pipeline.setBasic(new StandardContextValve()); broadcaster = new NotificationBroadcasterSupport(); }
public final void invoke(Request request, Response response) throws IOException, ServletException { // Disallow any direct access to resources under WEB-INF or META-INF MessageBytes requestPathMB = request.getRequestPathMB(); if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0)) || (requestPathMB.equalsIgnoreCase("/META-INF")) || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0)) || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) { notFound(response); return; } // Wait if we are reloading boolean reloaded = false; while (context.getPaused()) { reloaded = true; try { Thread.sleep(1000); } catch (InterruptedException e) { // Ignore } } // Reloading will have stopped the old webappclassloader and // created a new one if (reloaded && context.getLoader() != null && context.getLoader().getClassLoader() != null) { Thread.currentThread().setContextClassLoader( context.getLoader().getClassLoader()); } // Select the Wrapper to be used for this Request Wrapper wrapper = request.getWrapper(); if (wrapper == null) { notFound(response); return; } else if (wrapper.isUnavailable()) { // May be as a result of a reload, try and find the new wrapper wrapper = (Wrapper) container.findChild(wrapper.getName()); if (wrapper == null) { notFound(response); return; } } // Normal request processing Object instances[] = context.getApplicationEventListeners(); ServletRequestEvent event = null; if ((instances != null) && (instances.length > 0)) { event = new ServletRequestEvent (((StandardContext) container).getServletContext(), request.getRequest()); // create pre-service event for (int i = 0; i < instances.length; i++) { if (instances[i] == null) continue; if (!(instances[i] instanceof ServletRequestListener)) continue; ServletRequestListener listener = (ServletRequestListener) instances[i]; try { if (!request.isAsyncDispatching()) { listener.requestInitialized(event); } } catch (Throwable t) { container.getLogger().error(sm.getString("standardContext.requestListener.requestInit", instances[i].getClass().getName()), t); ServletRequest sreq = request.getRequest(); sreq.setAttribute(Globals.EXCEPTION_ATTR,t); return; } } } if (request.isAsyncSupported()) { request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported()); } wrapper.getPipeline().getFirst().invoke(request, response); if ((instances !=null ) && (instances.length > 0)) { // create post-service event for (int i = 0; i < instances.length; i++) { int j = (instances.length -1) -i; if (instances[j] == null) continue; if (!(instances[j] instanceof ServletRequestListener)) continue; ServletRequestListener listener = (ServletRequestListener) instances[j]; try { if (!request.isAsyncDispatching()) { listener.requestDestroyed(event); } } catch (Throwable t) { container.getLogger().error(sm.getString("standardContext.requestListener.requestDestroy", instances[j].getClass().getName()), t); ServletRequest sreq = request.getRequest(); sreq.setAttribute(Globals.EXCEPTION_ATTR,t); } } } }
重点查看wrapper.getPipeline().getFirst().invoke(request, response)代码,在invoke实现中又调用了wrapper容器(org.apache.catalina.core.StandardWrapper)中管道的阀门。我们看看StandardWrapper的构造函数代码:
public StandardWrapper() { super(); swValve=new StandardWrapperValve(); pipeline.setBasic(swValve); broadcaster = new NotificationBroadcasterSupport(); }
构造函数中给该容器的管道设置了默认的org.apache.catalina.core.StandardWrapperValve阀门。阀门中的invoke代码实现如下:
public final void invoke(Request request, Response response) throws IOException, ServletException { // Initialize local variables we may need boolean unavailable = false; Throwable throwable = null; // This should be a Request attribute... long t1=System.currentTimeMillis(); requestCount++; StandardWrapper wrapper = (StandardWrapper) getContainer(); Servlet servlet = null; Context context = (Context) wrapper.getParent(); // Check for the application being marked unavailable if (!context.getAvailable()) { response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardContext.isUnavailable")); unavailable = true; } // Check for the servlet being marked unavailable if (!unavailable && wrapper.isUnavailable()) { container.getLogger().info(sm.getString("standardWrapper.isUnavailable", wrapper.getName())); long available = wrapper.getAvailable(); if ((available > 0L) && (available < Long.MAX_VALUE)) { response.setDateHeader("Retry-After", available); response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardWrapper.isUnavailable", wrapper.getName())); } else if (available == Long.MAX_VALUE) { response.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("standardWrapper.notFound", wrapper.getName())); } unavailable = true; } // Allocate a servlet instance to process this request try { if (!unavailable) { servlet = wrapper.allocate(); } } catch (UnavailableException e) { container.getLogger().error( sm.getString("standardWrapper.allocateException", wrapper.getName()), e); long available = wrapper.getAvailable(); if ((available > 0L) && (available < Long.MAX_VALUE)) { response.setDateHeader("Retry-After", available); response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardWrapper.isUnavailable", wrapper.getName())); } else if (available == Long.MAX_VALUE) { response.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("standardWrapper.notFound", wrapper.getName())); } } catch (ServletException e) { container.getLogger().error(sm.getString("standardWrapper.allocateException", wrapper.getName()), StandardWrapper.getRootCause(e)); throwable = e; exception(request, response, e); } catch (Throwable e) { container.getLogger().error(sm.getString("standardWrapper.allocateException", wrapper.getName()), e); throwable = e; exception(request, response, e); servlet = null; } // Identify if the request is Comet related now that the servlet has been allocated boolean comet = false; if (servlet instanceof CometProcessor && request.getAttribute("org.apache.tomcat.comet.support") == Boolean.TRUE) { comet = true; request.setComet(true); } // Acknowledge the request try { response.sendAcknowledgement(); } catch (IOException e) { request.removeAttribute(Globals.JSP_FILE_ATTR); container.getLogger().warn(sm.getString("standardWrapper.acknowledgeException", wrapper.getName()), e); throwable = e; exception(request, response, e); } catch (Throwable e) { container.getLogger().error(sm.getString("standardWrapper.acknowledgeException", wrapper.getName()), e); throwable = e; exception(request, response, e); servlet = null; } MessageBytes requestPathMB = request.getRequestPathMB(); DispatcherType dispatcherType = DispatcherType.REQUEST; if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC; request.setAttribute (ApplicationFilterFactory.DISPATCHER_TYPE_ATTR, dispatcherType); request.setAttribute (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR, requestPathMB); // Create the filter chain for this request 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 (request.isAsyncDispatching()) { //TODO SERVLET3 - async ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch(); } else if (comet) { filterChain.doFilterEvent(request.getEvent()); request.setComet(true); } else { filterChain.doFilter(request.getRequest(), response.getResponse()); } } finally { String log = SystemLogHandler.stopCapture(); if (log != null && log.length() > 0) { context.getLogger().info(log); } } } else { if (request.isAsyncDispatching()) { //TODO SERVLET3 - async ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch(); } else if (comet) { request.setComet(true); filterChain.doFilterEvent(request.getEvent()); } else { 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); } catch (IOException e) { request.removeAttribute(Globals.JSP_FILE_ATTR); container.getLogger().error(sm.getString("standardWrapper.serviceException", wrapper.getName()), e); throwable = e; exception(request, response, e); } catch (UnavailableException e) { request.removeAttribute(Globals.JSP_FILE_ATTR); container.getLogger().error(sm.getString("standardWrapper.serviceException", wrapper.getName()), e); // throwable = e; // exception(request, response, e); wrapper.unavailable(e); long available = wrapper.getAvailable(); if ((available > 0L) && (available < Long.MAX_VALUE)) { response.setDateHeader("Retry-After", available); response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardWrapper.isUnavailable", wrapper.getName())); } else if (available == Long.MAX_VALUE) { response.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("standardWrapper.notFound", wrapper.getName())); } // Do not save exception in 'throwable', because we // do not want to do exception(request, response, e) processing } catch (ServletException e) { request.removeAttribute(Globals.JSP_FILE_ATTR); Throwable rootCause = StandardWrapper.getRootCause(e); if (!(rootCause instanceof ClientAbortException)) { container.getLogger().error(sm.getString("standardWrapper.serviceException", wrapper.getName()), rootCause); } throwable = e; exception(request, response, e); } catch (Throwable e) { request.removeAttribute(Globals.JSP_FILE_ATTR); container.getLogger().error(sm.getString("standardWrapper.serviceException", wrapper.getName()), e); throwable = e; exception(request, response, e); } // Release the filter chain (if any) for this request if (filterChain != null) { if (request.isComet()) { // If this is a Comet request, then the same chain will be used for the // processing of all subsequent events. filterChain.reuse(); } else { filterChain.release(); } } // Deallocate the allocated servlet instance try { if (servlet != null) { wrapper.deallocate(servlet); } } catch (Throwable e) { container.getLogger().error(sm.getString("standardWrapper.deallocateException", wrapper.getName()), e); if (throwable == null) { throwable = e; exception(request, response, e); } } // If this servlet has been marked permanently unavailable, // unload it and release this instance try { if ((servlet != null) && (wrapper.getAvailable() == Long.MAX_VALUE)) { wrapper.unload(); } } catch (Throwable e) { container.getLogger().error(sm.getString("standardWrapper.unloadException", wrapper.getName()), e); if (throwable == null) { throwable = e; exception(request, response, e); } } long t2=System.currentTimeMillis(); long time=t2-t1; processingTime += time; if( time > maxTime) maxTime=time; if( time < minTime) minTime=time; }
该阀门最终会调用wapper封装的servlet对象。