[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
![[Tomcat源码系列]结构解析 3)请求处理控制结构 [Tomcat源码系列]结构解析 3)请求处理控制结构](https://cdn.ancii.com/article/image/v1/ID/iJ/R4/4RiDJI-SXw_VV6IJV1yf_v-ylvAP2A8b527dS4Oe6uM5_2OoSbZmBXDvJDxpUsG_.gif)