Tomcat源码解析--请求处理

376 阅读3分钟

当一次浏览器的请求发送过来,宏观上Tomcat要做的事情:1.接收请求数据;2.调用相应的Servlet处理;3.返回数据给浏览器。那么在Tomcat内部是怎么完成这一系列操作的呢?

Tomcat处理HTTP请求

在之前的体系结构里,我们提到过Connector与Container的解耦设计。在此处就更加清晰了:

1.用户点击网页内容,请求被发送到本机端口8080,被在那里监听的Coyote HTTP/1.1 Connector获得;

2.Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应;

3.Engine获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host;

4.Engine匹配到名为localhost的Host标签,该Host获得请求/test/index.jsp,匹配它所拥有的所有的Context;

5.path=“/test”的Context获得请求/index.jsp,在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类;

6.构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet()或doPost().执行业务逻辑、数据存储等程序;

7.Context把执行完之后的HttpServletResponse对象返回给Host;

8.Host把HttpServletResponse对象返回给Engine;

9.Engine把HttpServletResponse对象返回Connector;

10.Connector把HttpServletResponse对象返回给客户Browser;

最经典的职责链模式

让我们从第二步开始,跟一下Tomcat的源码,看看它是怎么在代码中完成这一系列请求的。

断点打在org.apache.catalina.connector.CoyoteAdapter.java类中的Service(),该类就是负责从Connector调用Container的入口。从图中可以看到,执行的是StandardEngineValve.invoke()。

可是StandardEngine中哪来的getPipeline()呢?我们只好找它的父类ContainerBase.getPipeline(),可以看到返回的是一个StandardPipeline对象:

直接找到StandardPipeline的顶层接口,看一下它的方法:

再看一眼StandardEngineValve.invoke():

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // 拿到Engine中包含的host
        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());
        }

        // 当前节点执行完毕,触发下一个节点的invoke().
        // 这是把所有pipeline串联起来的关键
        host.getPipeline().getFirst().invoke(request, response);

    }

到这里我们就明白了,这是一个职责链模式。从CoyoteAdapter.Service()触发之后,代码会顺着职责链StandardHostValve、StandardContextValve、StandardWrapperValve依次执行下去。

那么问题来了,在此处连接的时候是直接invoke()调用的,是什么时候来为StandardPipeline设置阀门的呢?

我们找到它的set方法,看看在哪里被调用过:

public StandardEngine() {
    super();
    pipeline.setBasic(new StandardEngineValve());
    /* Set the jmvRoute using the system property jvmRoute */
    try {
        setJvmRoute(System.getProperty("jvmRoute"));
    } catch(Exception ex) {
        log.warn(sm.getString("standardEngine.jvmRouteFail"));
    }
    // By default, the engine will hold the reloading thread
    backgroundProcessorDelay = 10;

}

原来,在初始化容器组件时,就已经将需要的valve设置了进去。当请求来临时直接调用即可。

本文提到了职责链模式在tomcat中的应用,如果不明白职责链模式的调用方式可以参见作者的另一篇博客:设计模式--职责链模式

可以参见下图:

自定义一个Valve

职责链的即用即插机制给我们带来了更好的拓展性,例如,你要添加一个额外的逻辑处理阀门是很容易的。

1.自定义个阀门PrintIPValve,只要继承ValveBase并重写invoke方法即可。

public class PrintIPValve extends ValveBase{

  @Override
  public void invoke(Request request, Response response) throws IOException, 	   ServletException {

    System.out.println("------自定义阀门PrintIPValve:"+request.getRemoteAddr());
    getNext().invoke(request,response);
  }
}
  1. 配置Tomcat的核心配置文件server.xml,这里把阀门配置到Engine容器下,作用范围就是整个引擎,也可以根据作用范围配置在Host或者是Context下
<Valve className="org.apache.catalina.valves.PrintIPValve" />