[Tomcat源码系列]结构解析 3)请求处理控制结构
一、请求处理控制结构基础
与生命期结构类似,请求处理也是一个两层的结构
1.Valve:Valve是最小的处理单元,我们看看Valve的定义下面是Valve的接口定义
public interface Valve { public String getInfo(); public Valve getNext(); public void setNext(Valve valve); public void backgroundProcess(); public void invoke(Request request, Response response) throws IOException, ServletException; public void event(Request request, Response response, CometEvent event) throws IOException, ServletException;
其中我们最需要关注的是invoke方法,请求处理处理方法,我们看看tomcat对这个方法的注释(如果扩展Valve,必须仔细阅读)
- Examineand/ormodifythepropertiesofthespecifiedRequestandResponse.
- ExaminethepropertiesofthespecifiedRequest,completelygeneratethecorrespondingResponse,andreturncontroltothecaller.
- ExaminethepropertiesofthespecifiedRequestandResponse,wrapeitherorbothoftheseobjectstosupplementtheirfunctionality,andpassthemon.
- IfthecorrespondingResponsewasnotgenerated(andcontrolwasnotreturned,callthenextValveinthepipeline(ifthereisone)byexecutingcontext.invokeNext().Examine,butnotmodify,thepropertiesoftheresultingResponse(whichwascreatedbyasubsequentlyinvokedValveorContainer).
- Change request properties that have already been used to direct the flow of processing control for this request (for instance,trying to change the virtual host to which a Request should be sent from a pipeline attached to a Host or Context in the standard implementation).
- Create a completed Response AND pass this Request and Response on to the next Valve in the pipeline.
- Consume bytes from the input stream associated with the Request,unless it is completely generating the response, or wrapping the request before passing it on.
- Modify the HTTP headers included with the Response after the invokeNext() method has returned.
- Perform any actions on the output stream associated with the specified Response after the invokeNext()method has returned.
2.Pipeline:如上所说,“A series of Valves are generally associated with each other into a Pipeline”,我们Tomcat对Pipeline接口的说明
与其说是Pipeline(管道),其实此处似乎称为处理链更合适一点,中间每个处理节点(Valve)做一部分处理,然后由下一个处理节点继续处理,而通常最后一个处理节点必须处理请求并创建响应。如下是Pipeline接口的定义
public interface Pipeline { public Valve getBasic(); public void setBasic(Valve valve); public void addValve(Valve valve); public Valve[] getValves(); public void removeValve(Valve valve); public Valve getFirst(); }
二、请求处理过程解析
1.处理过程预览
Tomcat的主要处理组件Engine、Host、Context和Wrapper的实现都会实现Pipeline接口(实际是ContainerBase实现了该接口)。在第一篇中,我们知道,实际对请求的处理是一个Adpater,Tomcat中Adapter的实现是CoyoteAdapter,因此请求处理的入口是CoyoteAdapter的service方法,我们的请求处理过程就从CoyoteAdapter开始--组装好请求处理链
--StandardEngine.getPipeline().getFirst().invoke(request,response);
--XxxValve.invoke
--StandardEngineValve.invoke
2.StandardEngineValve.invoke
--Host.getPipeline().getFirst().invoke(request,response);
--YyyValve.invoke
--StandardHostValve.invoke
3.StandardHostValve.invoke
--Context.getPipeline().getFirst().invoke(request,response);
--ZzzValve.invoke
--StandardContextValve.invoke
4.StandardContextValve.invoke
--ServletRequestListener.requestInitialized
--Wrapper.getPipeline().getFirst().invoke(request,response);
--StandardWrapperValve.invoke
--ServletRequestListener.requestDestroyed
5.StandardWrapperValve.invoke
--组装Filter+Servlet
--处理请求2. 处理解析
1)我们从CoyoteAdapter.service为入口,看看Tomcat的整个请求处理过程
CoyoteAdapter.service
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception { Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); if (request == null) { //创建request、response对象 ...略 } try { // Parse and set Catalina and configuration specific // request parameters req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName()); //组装请求处理链在此部分进行,后面详细解析 if (postParseRequest(req, request, res, response)) { // Calling the container connector.getContainer().getPipeline().getFirst().invoke(request, response); //此处的Container是StandardEngine对象 ...略 } catch (IOException e) { ; } catch (Throwable t) { log.error(sm.getString("coyoteAdapter.service"), t); } finally { ...略 } }
2)默认的StandardEngine这个Pipeline会有StandardEngineValve这个处理单元。我们可以配置其他的处理单元到处理链中,譬如Tomcat就定义了如下的处理单元
therequestandresponsedatareceivedandsentbyTomcat.
Documentationat:/docs/config/valve.html-->
<Valve classname="org.apache.catalina.valves.RequestDumperValve"/>我们看看StandardEngineValve.invoke
public final void invoke(Request request, Response response) throws IOException, ServletException { // Select the Host to be used for this Request //请求是属于哪个Host的在CoyoteAdapter.postParseRequest已经准备好,后面重点会讲解这个过程 Host host = request.getHost(); if (host == null) { response.sendError (HttpServletResponse.SC_BAD_REQUEST, sm.getString("standardEngine.noHost", request.getServerName())); return; } // Ask this Host to process this request host.getPipeline().getFirst().invoke(request, response); }
3)同样的,StandardHost这个Pipeline会有StandardHostValve这个处理单元。我们可以配置其他的处理单元到处理链中,譬如Tomcat就定义了如下的处理单元
<Valveclassname="org.apache.catalina.valves.AccessLogValve"directory="logs"
prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/>StandardHostValve如何处理请求跟StandardEngineValve类似,接下来请求进入到StandardContextValve.invoke4) 同样的,StandardContext这个Pipeline会有StandardContextValve这个处理单元。我们可以配置其他的处理单元到处理链中,譬如Tomcat就定义了如下的处理单元
prefix="localhost_access_log."suffix=".txt"
pattern="common"/>我们看看StandardContextValve是如何处理请求的
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"))) { String requestURI = request.getDecodedRequestURI(); notFound(requestURI, response); return; } // Wait if we are reloading while (context.getPaused()) { try { Thread.sleep(1000); } catch (InterruptedException e) { ; } } // Select the Wrapper to be used for this Request Wrapper wrapper = request.getWrapper(); if (wrapper == null) { String requestURI = request.getDecodedRequestURI(); notFound(requestURI, response); return; } //ServletRequestListener. requestInitialized ...略 wrapper.getPipeline().getFirst().invoke(request, response); //ServletRequestListener.requestDestroyed ...略 }
5) 同样的,StandardWrapper这个Pipeline会有StandardWrapperValve这个处理单元,详细处理过程可以参阅一下org.apache.catalina.core.StandardWrapperValve.invoke代码
3.请求处理链组装
在如上代码中可以看到,实际请求进入到StandardEngineValve的时候,由哪个Host、哪个Context、哪个Wrapper参与处理过程实际已经确定下来了,我们看看这个过程是如何处理的。Tomcat使用org.apache.tomcat.util.http.mapper.Mapper来管理这种请求如何映射到具体Host、Context、Wrapper。这个过程分为两个阶段
1)初始化阶段
在Engine、Host、Context的初始化阶段,会将子组件通过addChild方法加入到父组件中,我们以StandardContext.addChild为例看看如何处理
public void addChild(Container child) { // Global JspServlet Wrapper oldJspServlet = null; if (!(child instanceof Wrapper)) { throw new IllegalArgumentException (sm.getString("standardContext.notWrapper")); } Wrapper wrapper = (Wrapper) child; boolean isJspServlet = "jsp".equals(child.getName()); // Allow webapp to override JspServlet inherited from global web.xml. if (isJspServlet) { oldJspServlet = (Wrapper) findChild("jsp"); if (oldJspServlet != null) { removeChild(oldJspServlet); } } String jspFile = wrapper.getJspFile(); if ((jspFile != null) && !jspFile.startsWith("/")) { if (isServlet22()) { if(log.isDebugEnabled()) log.debug(sm.getString("standardContext.wrapper.warning", jspFile)); wrapper.setJspFile("/" + jspFile); } else { throw new IllegalArgumentException (sm.getString("standardContext.wrapper.error", jspFile)); } } super.addChild(child); if (isJspServlet && oldJspServlet != null) { /* * The webapp-specific JspServlet inherits all the mappings * specified in the global web.xml, and may add additional ones. */ String[] jspMappings = oldJspServlet.findMappings(); for (int i=0; jspMappings!=null && i<jspMappings.length; i++) { addServletMapping(jspMappings[i], child.getName()); } } }
在如上处理过程当中,我们关注的重点是addServletMapping,进一步进入addServletMapping,我们可以看到最终会将Wrapper对象告诉给Mapper对象,代码如下
public void addServletMapping(String pattern, String name, boolean jspWildCard) { // Validate the proposed mapping if (findChild(name) == null) throw new IllegalArgumentException (sm.getString("standardContext.servletMap.name", name)); pattern = adjustURLPattern(RequestUtil.URLDecode(pattern)); if (!validateURLPattern(pattern)) throw new IllegalArgumentException (sm.getString("standardContext.servletMap.pattern", pattern)); // Add this mapping to our registered set synchronized (servletMappings) { String name2 = (String) servletMappings.get(pattern); if (name2 != null) { // Don't allow more than one servlet on the same pattern Wrapper wrapper = (Wrapper) findChild(name2); wrapper.removeMapping(pattern); mapper.removeWrapper(pattern); } servletMappings.put(pattern, name); } Wrapper wrapper = (Wrapper) findChild(name); wrapper.addMapping(pattern); // Update context mapper mapper.addWrapper(pattern, wrapper, jspWildCard); fireContainerEvent("addServletMapping", pattern); }
StandardEngine和StandardHost也会有类似的处理,这里不再重复,可以直接看这两个类的addChild实现
2)请求处理链组装阶段
在如上分析CoyoteAdpater.service的过程当中,我们知道,在进入StandardEngineValve.invoke之前,会先把请求处理链先准备好,实际上有了Mapper这个对象及如上的基础,这个处理过程不会太过复杂。代码处理过程是CoyoteAdpater.service-->CoyoteAdapter.postParseRequest-->Mapper.mapper,有兴趣可以直接看org.apache.tomcat.util.http.mapper.Mapper.mapper方法
三、通过如上层层处理,最终请求到达我们实际的处理Servlet