当一次浏览器的请求发送过来,宏观上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);
}
}
- 配置Tomcat的核心配置文件server.xml,这里把阀门配置到Engine容器下,作用范围就是整个引擎,也可以根据作用范围配置在Host或者是Context下
<Valve className="org.apache.catalina.valves.PrintIPValve" />