设为首页 收藏本站
查看: 264|回复: 0

[经验分享] [Tomcat源码系列]结构解析 3)请求处理控制结构

[复制链接]
发表于 2017-2-5 07:34:44 | 显示全部楼层 |阅读模式
  一、请求处理控制结构基础

     与生命期结构类似,请求处理也是一个两层的结构

1.Valve:Valve是最小的处理单元,我们看看Valve的定义

A Valve is a request processing component associated with a particular Container.  A series of Valves are generally associated with each other into a Pipeline.
  下面是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,必须仔细阅读)

Perform request processing as required by this Valve.An individual Valve <b>MAY</b> perform the following actions, in the specified order:

  • Examine and/or modify the properties of the specified Request and Response.
  • Examine the properties of the specified Request, completely generate the corresponding Response, and return control to the caller.
  • Examine the properties of the specified Request and Response, wrap either or both of these objects to supplement their functionality, and pass them on.
  • If the corresponding Response was not generated (and control was not returned, call the next Valve in the pipeline (if there is one) by executing context.invokeNext(). Examine, but not modify, the properties of the resulting Response (which was created by a subsequently invoked Valve or Container).

A Valve MUST NOT do any of the following things:

  • 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接口的说明

写道

Interface describing a collection of Valves that should be executed in sequence when the invoke() method is invoked.  It is required that a Valve somewhere in the pipeline (usually the last one)  must process the request and create the corresponding response, rather than trying to pass the request on.
  与其说是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开始

1.    CoyoteAdapter.service

   --组装好请求处理链

   --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就定义了如下的处理单元

写道

<!-- The request dumper valve dumps useful debugging information about

the request and response data received and sent by Tomcat.

Documentation at: /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就定义了如下的处理单元

<Valve className="org.apache.catalina.authenticator.SingleSignOn" />

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"

prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/>
  StandardHostValve如何处理请求跟StandardEngineValve类似,接下来请求进入到StandardContextValve.invoke

4) 同样的,StandardContext这个Pipeline会有StandardContextValve这个处理单元。我们可以配置其他的处理单元到处理链中,譬如Tomcat就定义了如下的处理单元

写道

<Valve className="org.apache.catalina.valves.AccessLogValve"

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, 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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-337559-1-1.html 上篇帖子: 终于把Tomcat的虚拟目录配置成功了 下篇帖子: Tomcat设置404错误页无法正确定向
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表