58-Tomcat 源码分析
只列了关键代码。写得很乱,意识流操作...
1.Connector
1.1 Endpoint
public abstract class AbstractEndpoint<S> {
// [线程池] 调用Processor完成协议解析,解析完后将请求交给Adapter
private Executor executor = null;
public abstract static class Acceptor implements Runnable {}
// ConnectionHandler
}
public class NioEndpoint extends AbstractJsseEndpoint <NioChannel> {
private volatile ServerSocketChannel serverSock = null;
// Acceptor
// Poller
// PollerEvent
// SocketProcessor
}
a. Acceptor
protected class Acceptor extends AbstractEndpoint.Acceptor {
@Override
public void run() {
while (running) {
// Accept the next incoming connection from the server
SocketChannel socket = serverSock.accept();
}
// Configure the Socket 配置新接收的客户端连接Socket
if (running && !paused) {
// setSocketOptions() will hand the socket off to an appropriate processor if successful
if (!setSocketOptions(socket)) {
closeSocket(socket);
}
} else {
closeSocket(socket);
}
}
}
protected boolean setSocketOptions(SocketChannel socket) {
socket.configureBlocking(false);
Socket sock = socket.socket();
// 读写ByteBuffer的处理器,读写buffer的大小=8192byte=8KB
SocketBufferHandler bufhandler = new SocketBufferHandler(
socketProperties.getAppReadBufSize(),
socketProperties.getAppWriteBufSize(),
socketProperties.getDirectBuffer());
NioChannel channel = new NioChannel(socket, bufhandler);
/**
* 将 channel 注册到 Poller (Selector): 将 NioChannel 包装到封装在一个 PollerEvent 对象中,
* 并将 PollerEvent 对象压入 Poller 的 Queue 里。
* 这是个典型的生产者 - 消费者模式,Acceptor 与 Poller 线程之间通过 Queue 通信; 当前还在 Acceptor线程上
*/
getPoller0().register(channel);
}
b. Poller
/**
* Poller 本质是一个 Selector,它内部维护一个 Queue,每个 Poller 在单独的线程中运行,即 IO 线程。
*/
public class Poller implements Runnable {
private Selector selector;
private final SynchronizedQueue<PollerEvent> events = new SynchronizedQueue<>();
public void register(final NioChannel socket) {
socket.setPoller(this);
NioSocketWrapper ka = new NioSocketWrapper(socket, NioEndpoint.this);
socket.setSocketWrapper(ka);
ka.setPoller(this);
PollerEvent r = eventCache.pop();
ka.interestOps(SelectionKey.OP_READ); // This is what OP_REGISTER turns into.
if (r == null) {
r = new PollerEvent(socket, ka, OP_REGISTER);
} else {
r.reset(socket, ka, OP_REGISTER);
}
// 将 PollerEvent 添加到 Poller 的 SynchronizedQueue<PollerEvent> events 队列
addEvent(r);
}
@Override
public void run() {
// Loop until destroy() is called
while (true) {
boolean hasEvents = false;
if (!close) {
hasEvents = events();
if (wakeupCounter.getAndSet(-1) > 0) {
// If we are here, means we have other stuff to do
// Do a non blocking select
keyCount = selector.selectNow();
} else {
keyCount = selector.select(selectorTimeout);
}
wakeupCounter.set(0);
}
// 拿到 SelectionKey
Iterator <SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
// Walk through the collection of ready keys and dispatch any active event.
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
iterator.remove();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
// Attachment may be null if another thread has called cancelledKey()
if (socketWrapper != null) {
// 处理 IO 事件 !!!
processKey(sk, socketWrapper);
}
}
}
}
/**
* Processes events in the event queue of the Poller.
*/
public boolean events() {
boolean result = false;
PollerEvent pe = null;
for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++) {
result = true;
pe.run(); // PollerEvent#run() => 注册 SelectionKey.OP_READ
pe.reset();
if (running && !paused) {
eventCache.push(pe); // PollerEvent Cache
}
}
return result;
}
protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
// 判断 isReadable 或 isWritable
if (sk.isReadable() || sk.isWritable()) {
// 先处理读
if (sk.isReadable()) {
// processSocket() 定义在 AbstractEndpoint 类中
if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
closeSocket = true; // 处理失败 => closeSocket
}
}
// 再处理写
if (!closeSocket && sk.isWritable()) {
if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {
closeSocket = true;
}
}
if (closeSocket) {
cancelledKey(sk);
}
}
}
}
AbstractEndPoint:
public boolean processSocket(SocketWrapperBase <S> socketWrapper, SocketEvent event, boolean dispatch) {
// 这也是一个线程 => 1.4
SocketProcessorBase <S> sc = processorCache.pop(); // sc=NioEndpoint$SocketProcessor
// 拿到 Executor 派发到单独的线程中执行
Executor executor = getExecutor();
executor.execute(sc); // 下一个入口: org.apache.tomcat.util.net.NioEndpoint.SocketProcessor
}
c. PollerEvent
public static class PollerEvent implements Runnable {
private NioChannel socket;
private int interestOps;
private NioSocketWrapper socketWrapper;
@Override
public void run() {
if (interestOps == OP_REGISTER) {
// register OP_READ attachment=socketWrapper
socket.getIOChannel().register(socket.getPoller().getSelector(),
SelectionKey.OP_READ, socketWrapper);
}
}
}
1.2 Processor
public abstract class SocketProcessorBase<S> implements Runnable {
protected SocketWrapperBase<S> socketWrapper;
protected SocketEvent event;
/**
* SocketProcessor 处理 SocketEvent
*/
@Override
public final void run() {
doRun();
}
protected abstract void doRun();
}
这里走的实现是 NioEndPoint.SocketProcessor 类
/**
* This class is the equivalent of the Worker, but will simply use in an
* external Executor thread pool.
*/
protected class SocketProcessor extends SocketProcessorBase<NioChannel> {
@Override
protected void doRun() {
// 这里的 handler 是 AbstractHttp11Protocol
SocketState state = getHandler().process(socketWrapper, event);
}
}
AbstractProtocol.ConnectionHandler
protected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S> {
@Override
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
// 拿到 Processor
Processor processor = connections.get(socket);
// 底层就是调用 Http11Processor#service()
state = processor.process(wrapper, status);
}
}
Http11Processor
// ----------------------------------------------------------------- Fields On AbstractProcessor
private volatile long asyncTimeoutGeneration = 0;
protected final AbstractEndpoint<?> endpoint;
protected final Request request;
protected final Response response;
protected volatile SocketWrapperBase<?> socketWrapper = null;
protected volatile SSLSupport sslSupport;
// ----------------------------------------------------------------- Fields On AbstractProcessor
@Override
public SocketState service(SocketWrapperBase<?> socketWrapper) {
RequestInfo rp = request.getRequestProcessor(); // 封装请求信息
prepareRequest();
// 调用Adapter的service方法
// 将tomcat的org.apache.coyote.Request/Response适配为Servlet规范的ServletRequest/ServletResponse
getAdapter().service(request, response);
}
1.3 CoyoteAdapter
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {
// 将 org.apache.coyote.Request 转换成 org.apache.catalina.connector.Request -> 标准的 ServletRequest
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
// Calling the container 进入容器执行的环节,走每个容器 Pipeline 中的 valve
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
}
2. Container
// TODO
3. 启动流程
使用过 Tomcat 的都知道,我们可以通过 Tomcat 的 /bin 目录下的脚本 startup.sh 来启动 Tomcat,那你是否知道我们执行了这个脚本后发生了什么呢?你可以通过下面这张流程图来了解一下。
- Tomcat 本质上是一个 Java 程序,因此 startup.sh 脚本会启动一个 JVM 来运行 Tomcat 的启动类 Bootstrap。
- Bootstrap 的主要任务是初始化 Tomcat 的类加载器,并且创建 Catalina。
- Catalina 是一个启动类,它通过解析 server.xml、创建相应的组件,并调用 Server 的 start 方法。
- Server 组件的职责就是管理 Service 组件,它会负责调用 Service 的 start 方法。
- Service 组件的职责就是管理连接器和顶层容器 Engine,因此它会调用连接器和 Engine 的 start 方法。
- 你可以把 Bootstrap 看作是上帝,它初始化了类加载器,也就是创造万物的工具。
- 如果我们把 Tomcat 比作是一家公司,那么 Catalina 应该是公司创始人,因为 Catalina 负责组建团队,也就是创建 Server 以及它的子组件。
- Server 是公司的 CEO,负责管理多个事业群,每个事业群就是一个 Service。
- Service 是事业群总经理,它管理两个职能部门:
- 一个是对外的市场部,也就是连接器组件;
- 另一个是对内的研发部,也就是容器组件。
- Engine 则是研发部经理,因为 Engine 是最顶层的容器组件。
你可以看到这些启动类或者组件不处理具体请求,它们的任务主要是“管理”,管理下层组件的生命周期,并且给下层组件分配任务,也就是把请求路由到负责“干活儿”的组件。因此我把它们比作 Tomcat 的“高层”。
3.1 Catalina
Catalina 的主要任务就是创建 Server,它不是直接 new 一个 Server 实例就完事了,而是需要解析 server.xml,把在 server.xml 里配置的各种组件一一创建出来,接着调用 Server 组件的 init 方法和 start 方法,这样整个 Tomcat 就启动起来了。作为“管理者”,Catalina 还需要处理各种“异常”情况,比如当我们通过“Ctrl + C”关闭 Tomcat 时,Tomcat 将如何优雅的停止并且清理资源呢?因此 Catalina 在 JVM 中注册一个“关闭钩子”。
public void start() {
// 1. 如果持有的 Server 实例为空,就解析 server.xml 创建出来
if (getServer() == null) {
load();
}
// 2. 如果创建失败,报错退出
if (getServer() == null) {
log.fatal(sm.getString("catalina.noServer"));
return;
}
// 3. 启动 Server
try {
getServer().start();
} catch (LifecycleException e) {
return;
}
// 创建并注册关闭钩子
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
// 用 await 方法监听停止请求
if (await) {
await();
stop();
}
}
那什么是“关闭钩子”,它又是做什么的呢?如果我们需要在 JVM 关闭时做一些清理工作,比如将缓存数据刷到磁盘上,或者清理一些临时文件,可以向 JVM 注册一个“关闭钩子”。“关闭钩子”其实就是一个线程,JVM 在停止之前会尝试执行这个线程的 run 方法。下面我们来看看 Tomcat 的“关闭钩子”CatalinaShutdownHook 做了些什么。
protected class CatalinaShutdownHook extends Thread {
@Override
public void run() {
try {
if (getServer() != null) {
Catalina.this.stop();
}
} catch (Throwable ex) {
...
}
}
}
从这段代码中你可以看到,Tomcat 的“关闭钩子”实际上就执行了 Server 的 stop 方法,Server 的 stop 方法会释放和清理所有的资源。
3.2 Server 组件
Server 组件的具体实现类是 StandardServer,我们来看下 StandardServer 具体实现了哪些功能。Server 继承了 LifeCycleBase,它的生命周期被统一管理,并且它的子组件是 Service,因此它还需要管理 Service 的生命周期,也就是说在启动时调用 Service 组件的启动方法,在停止时调用它们的停止方法。Server 在内部维护了若干 Service 组件,它是以数组来保存的,那 Server 是如何添加一个 Service 到数组中的呢?
@Override
public void addService(Service service) {
service.setServer(this);
synchronized (servicesLock) {
// 创建一个长度 +1 的新数组
Service results[] = new Service[services.length + 1];
// 将老的数据复制过去
System.arraycopy(services, 0, results, 0, services.length);
results[services.length] = service;
services = results;
// 启动 Service 组件
if (getState().isAvailable()) {
try {
service.start();
} catch (LifecycleException e) {
// Ignore
}
}
// 触发监听事件
support.firePropertyChange("service", null, service);
}
}
从上面的代码你能看到,它并没有一开始就分配一个很长的数组,而是在添加的过程中动态地扩展数组长度,当添加一个新的 Service 实例时,会创建一个新数组并把原来数组内容复制到新数组,这样做的目的其实是为了节省内存空间。
除此之外,Server 组件还有一个重要的任务是启动一个 Socket 来监听停止端口,这就是为什么你能通过 shutdown 命令来关闭 Tomcat。不知道你留意到没有,上面 Caralina 的启动方法的最后一行代码就是调用了 Server 的 await 方法。
在 await 方法里会创建一个 Socket 监听 8005 端口,并在一个死循环里接收 Socket 上的连接请求,如果有新的连接到来就建立连接,然后从 Socket 中读取数据;如果读到的数据是停止命令“SHUTDOWN”,就退出循环,进入 stop 流程。
3.3 Service 组件
Service 组件的具体实现类是 StandardService,我们先来看看它的定义以及关键的成员变量。
public class StandardService extends LifecycleBase implements Service {
// 名字
private String name = null;
//Server 实例
private Server server = null;
// 连接器数组
protected Connector connectors[] = new Connector[0];
private final Object connectorsLock = new Object();
// 对应的 Engine 容器
private Engine engine = null;
// 映射器及其监听器
protected final Mapper mapper = new Mapper();
protected final MapperListener mapperListener = new MapperListener(this);
StandardService 继承了 LifecycleBase 抽象类,此外 StandardService 中还有一些我们熟悉的组件,比如 Server、Connector、Engine 和 Mapper。
那为什么还有一个 MapperListener?这是因为 Tomcat 支持热部署,当 Web 应用的部署发生变化时,Mapper 中的映射信息也要跟着变化,MapperListener 就是一个监听器,它监听容器的变化,并把信息更新到 Mapper 中,这是典型的观察者模式。
作为“管理”角色的组件,最重要的是维护其他组件的生命周期。此外在启动各种组件时,要注意它们的依赖关系,也就是说,要注意启动的顺序。我们来看看 Service 启动方法:
protected void startInternal() throws LifecycleException {
// 1. 触发启动监听器
setState(LifecycleState.STARTING);
// 2. 先启动 Engine,Engine 会启动它子容器
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
// 3. 再启动 Mapper 监听器
mapperListener.start();
// 4. 最后启动连接器,连接器会启动它子组件,比如 Endpoint
synchronized (connectorsLock) {
for (Connector connector: connectors) {
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
}
从启动方法可以看到,Service 先启动了 Engine 组件,再启动 Mapper 监听器,最后才是启动连接器。这很好理解,因为内层组件启动好了才能对外提供服务,才能启动外层的连接器组件。而 Mapper 也依赖容器组件,容器组件启动好了才能监听它们的变化,因此 Mapper 和 MapperListener 在容器组件之后启动。组件停止的顺序跟启动顺序正好相反的,也是基于它们的依赖关系。
3.4 Engine 组件
最后我们再来看看顶层的容器组件 Engine 具体是如何实现的。Engine 本质是一个容器,因此它继承了 ContainerBase 基类,并且实现了 Engine 接口。
public class StandardEngine extends ContainerBase implements Engine {...}
我们知道,Engine 的子容器是 Host,所以它持有了一个 Host 容器的数组,这些功能都被抽象到了 ContainerBase 中,ContainerBase 中有这样一个数据结构:
protected final HashMap<String, Container> children = new HashMap<>();
ContainerBase 用 HashMap 保存了它的子容器,并且 ContainerBase 还实现了子容器的“增删改查”,甚至连子组件的启动和停止都提供了默认实现,比如 ContainerBase 会用专门的线程池来启动子容器。
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
所以 Engine 在启动 Host 子容器时就直接重用了这个方法。
那 Engine 自己做了什么呢?我们知道容器组件最重要的功能是处理请求,而 Engine 容器对请求的“处理”,其实就是把请求转发给某一个 Host 子容器来处理,具体是通过 Valve 来实现的。
通过前面的学习,我们知道每一个容器组件都有一个 Pipeline,而 Pipeline 中有一个基础阀(Basic Valve),而 Engine 容器的基础阀定义如下:
final class StandardEngineValve extends ValveBase {
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// 拿到请求中的 Host 容器
Host host = request.getHost();
if (host == null) {
return;
}
// 调用 Host 容器中的 Pipeline 中的第一个 Valve
host.getPipeline().getFirst().invoke(request, response);
}
}
这个基础阀实现非常简单,就是把请求转发到 Host 容器。你可能好奇,从代码中可以看到,处理请求的 Host 容器对象是从请求中拿到的,请求对象中怎么会有 Host 容器呢?这是因为请求到达 Engine 容器中之前,Mapper 组件已经对请求进行了路由处理,Mapper 组件通过请求的 URL 定位了相应的容器,并且把容器对象保存到了请求对象中。
3.5 小结
上面我们提到的 Server、Service、Engine、Host、Context 都是接口, 下图中罗列了这些接口的默认实现类。当前对于 Endpoint 组件来说,在 Tomcat 中没有对应的 Endpoint 接口, 但是有一个抽象类 AbstractEndpoint ,其下有三个实现类: NioEndpoint、Nio2Endpoint、AprEndpoint , 这三个实现类分别对应于前面讲解链接器 Coyote 时, 提到的链接器支持的 3 种 IO 模型:NIO、NIO2、APR,Tomcat8.5 版本中,默认采用的是 NioEndpoint。
EndPoint 内部 ProtocolHandler 定义了自己一套生命周期方法,不走 LifeCycle 这套。protocolHandler.start()
是在 Connector.startInternal()
中被调用的。
4. 请求处理流程
4.1 Mapper
设计了这么多层次的容器,Tomcat 是怎么确定请求是由哪个 Wrapper 容器里的 Servlet 来处理的呢?
答案是,Tomcat 是用 Mapper 组件来完成这个任务的。
Mapper 组件的功能就是将用户请求的 URL 定位到一个 Servlet,它的工作原理是:Mapper 组件里保存了 Web 应用的配置信息,其实就是容器组件与访问路径的映射关系,比如 Host 容器里配置的域名、Context 容器里的 Web 应用路径,以及 Wrapper 容器里 Servlet 映射的路径,你可以想象这些配置信息就是一个多层次的 Map。
Mapper 组件体系结构(完成 url 和 Host、Context、Wrapper 等容器的映射):
当一个请求到来时,Mapper 组件通过解析请求 URL 里的域名和路径,再到自己保存的 Map 里去查找,就能定位到一个 Servlet。请注意,一个请求 URL 最后只会定位到一个 Wrapper 容器,也就是一个 Servlet。
假如有用户访问一个 URL,比如 http://user.shopping.com:8080/order/buy,Tomcat 如何将这个 URL 定位到一个 Servlet 呢?
首先,根据协议和端口号选定 Service 和 Engine。
我们知道 Tomcat 的每个连接器都监听不同的端口,比如 Tomcat 默认的 HTTP 连接器监听 8080 端口、默认的 AJP 连接器监听 8009 端口。上面例子中的 URL 访问的是 8080 端口,因此这个请求会被 HTTP 连接器接收,而一个连接器是属于一个 Service 组件的,这样 Service 组件就确定了。我们还知道一个 Service 组件里除了有多个连接器,还有一个容器组件,具体来说就是一个 Engine 容器,因此 Service 确定了也就意味着 Engine 也确定了。
然后,根据域名选定 Host。
Service 和 Engine 确定后,Mapper 组件通过 URL 中的域名去查找相应的 Host 容器,比如例子中的 URL 访问的域名是 user.shopping.com,因此 Mapper 会找到 Host2 这个容器。
之后,根据 URL 路径找到 Context 组件。
Host 确定以后,Mapper 根据 URL 的路径来匹配相应的 Web 应用的路径,比如例子中访问的是 /order,因此找到了 Context4 这个 Context 容器。
最后,根据 URL 路径找到 Wrapper(Servlet)。
Context 确定后,Mapper 再根据 web.xml 中配置的 Servlet 映射路径来找到具体的 Wrapper 和 Servlet。
4.2 Pipeline-Valve
我们知道容器组件最重要的功能是处理请求,最先拿到请求的是 Engine 容器,Engine 容器对请求做一些处理后,会把请求传给自己子容器 Host 继续处理,依次类推,最后这个请求会传给 Wrapper 容器,Wrapper 会调用最终的 Servlet 来处理。那么这个调用过程具体是怎么实现的呢?
答案是使用 Pipeline-Valve 管道。在 Tomcat 中,每个 Container 组件采用「责任链模式」来完成具体的请求处理。
在 Tomcat 中定义了 Pipeline 和 Valve 两个接口,Pipeline 用于构建责任链, 后者代表责任链上的每个处理器。Pipeline 中维护了一个基础的 Valve,它始终位于 Pipeline 的末端(最后执行),封装了具体的请求处理和输出响应的过程。当然,我们也可以调用 addValve() 方法,为 Pipeline 添加其他的 Valve, 后添加的 Valve 位于基础的 Valve 之前,并按照添加顺序执行。Pipiline 通过获得首个 Valve 来启动整合链条的执行。
public interface Valve {
public Valve getNext();
public void setNext(Valve valve);
public void invoke(Request request, Response response);
}
由于 Valve 是一个处理点,因此 invoke 方法就是来处理请求的。注意到 Valve 中有 getNext 和 setNext 方法,因此我们大概可以猜到有一个链表将 Valve 链起来了。继续看 Pipeline 接口:
public interface Pipeline extends Contained {
public void addValve(Valve valve);
public Valve getBasic();
public void setBasic(Valve valve);
public Valve getFirst();
}
没错,Pipeline 中有 addValve 方法。Pipeline 中维护了 Valve 链表,Valve 可以插入到 Pipeline 中,对请求做某些处理。我们还发现 Pipeline 中没有 invoke 方法,因为整个调用链的触发是 Valve 来完成的,Valve 完成自己的处理后,调用 getNext.invoke 来触发下一个 Valve 调用。
每一个容器都有一个 Pipeline 对象,只要触发这个 Pipeline 的第一个 Valve,这个容器里 Pipeline 中的 Valve 就都会被调用到。但是,不同容器的 Pipeline 是怎么链式触发的呢,比如 Engine 中 Pipeline 需要调用下层容器 Host 中的 Pipeline。
这是因为 Pipeline 中还有个 getBasic 方法。这个 BasicValve 处于 Valve 链表的末端,它是 Pipeline 中必不可少的一个 Valve,负责调用下层容器的 Pipeline 里的第一个 Valve。还是通过一张图来解释。
每一个容器组件都有一个 Pipeline,而 Pipeline 中有一个基础阀(Basic Valve),而 Engine 容器的基础阀定义如下:
final class StandardEngineValve extends ValveBase {
public final void invoke(Request request, Response response) throws IOException, ServletException {
// 拿到请求中的Host容器
Host host = request.getHost();
// 调用Host容器中的Pipeline中的第一个Valve
host.getPipeline().getFirst().invoke(request, response);
}
}
这个基础阀实现非常简单,就是把请求转发到 Host 容器。问题是处理请求的 Host 容器对象是从请求中拿到的,请求对象中怎么会有 Host 容器呢?
这是因为请求到达 Engine 容器中之前,Mapper 组件已经对请求进行了路由处理,Mapper 组件通过请求的 URL 定位了相应的容器,并且把容器对象保存到了请求对象中。
下面从 Tomcat 的设计架构层面来分析 Tomcat 的请求处理。
- Connector 组件 Endpoint 中的 Acceptor 监听客户端套接字连接并接收 Socket;
- 将连接交给线程池 Executor 处理,开始执行请求响应任务;
- Processor 组件读取消息报文,解析请求行、请求体、请求头,封装成 Request 对象;
- Mapper 组件根据请求行的 URL 值和请求头的 Host 值匹配由哪个 Host 容器、Context 容器、Wrapper 容器处理请求;
- CoyoteAdaptor 组件负责将 Connector 组件和 Engine 容器关联起来,把生成的 Request 对象和响应对象 Response 传递到 Engine 容器中,调用 Pipeline;
- Engine 容器的管道开始处理,管道中包含若干个 Valve、每个 Valve 负责部分处理逻辑。执行完 Valve 后会执行基础的 Valve —— StandardEngineValve,负责调用 Host 容器的 Pipeline;
- Host 容器的管道开始处理,流程类似,最后执行 Context 容器的 Pipeline;
- Context 容器的管道开始处理,流程类似,最后执行 Wrapper 容器的 Pipeline;
- Wrapper 容器的管道开始处理,流程类似,最后执行 Wrapper 容器对应的 Servlet 对象的处理方法,但在执行 Servlet 之前,会经过 FilterChain(如下图)。