Tomcat的实现原理

Tomcat 是一个Web容器。作为 Web 应用的容器承载着 Web 请求处理和响应的工作
Tomcat的实现原理_第1张图片
最开始用户通过浏览器查看诸如新闻之类的静态资源,此时就需要通过 HTTP 服务器向浏览器返回静态 HTML 资源,浏览器将解析的 HTML 呈现给使用者。这里的 Web 容器就是用来存放 HTTP 服务器,能够处理网络请求并且进行响应。
Tomcat的实现原理_第2张图片
随着互联网的发展,用户需求从静态资源转向了动态资源的获取,同时浏览器在资源获取的同时还会与服务端进行一些交互。

由此 Web 容器的功能开始有了扩展,除了能够处理 HTTP 请求,还需要 HTTP 服务器调用服务端程序也就是常说的 Web 应用。
Tomcat的实现原理_第3张图片
针对这种需求,Sun 公司推出了 Servlet 技术, Servlet 是运行在服务端的 Java 小程序。

由于 Servlet 不能独立运行,因此需要由一个 Servlet 容器来承载它,并且对其进行初始化启动等管理操作。

为了承载 Servlet,也加入了 Servlet 容器,不过每个 Servlet 都代表一个业务类,包含了一些业务应用如果都接入到 Web 容器中会用户提供统一的服务响应就需要遵循统一的接口。

说白了就是要遵守一定规则才能放到 Servlet 容器中,方便进行管理,那么这个规则就是 Servlet 接口。从图中可以看出 Servlet 接口会对单个的 Servlet 进行标准定义。

对于Tomcat而言,它并不知道我们会有什么样的方法,这些都只是在项目被部署进webapp下后才确定的,由此分析,必然用到了Java的反射来实现类的动态加载、实例化、获取方法、调用方法。但是我们部署到Tomcat的中的Web项目必须是按照规定好的Servlet 接口来进行编写,以便进行调用

public interface Servlet {

    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig();

    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;

    public String getServletInfo();

    public void destroy();
}

对于 Servlet 接口而言定义了 init 方法用做 Servlet 资源的初始化,同时也定义 destroy 方法用做 Servlet 资源的释放。

其中 Service 方法用来实现具体的业务需求,可以看到该方法传入 ServletRequest 和 ServletResponse 两个参数,分别表示封装了用户的请求信息和 Servlet 的响应信息。

接口中的 getServletConfig 方法会返回 ServletConfig,ServletConfig 是用来封装 Servlet 的初始化参数的,可以在 web.xml 配置 Servlet 参数,然后通过 getServletConfig 方法获取参数。

上面介绍了 Servlet 接口一下,再通过图 5 对 Servlet 接口调用的周边类进行深入了解。

Tomcat的实现原理_第4张图片

如图 5 所示,Servlet 接口依赖 ServletConfig 接口,该接口正好是用来处理 Servlet 配置参数的,ServletConfig 接口同时也会关联 ServletContext 获取 Servlet 上下文的信息。

Servlet 接口中的 service 方法依赖两个参数分别是 ServletRequest 和 ServletResponse。

同时有两个接口 HttpServletRequest 和 HttpServletResponse 会分别继承 ServletRequest 和 ServletResponse。

一般而言 Servlet 作为接口需要具体的实现类去实现这个接口,因此 Servlet 规范提供了一个抽象类名叫 GenericServlet,它实现了 Servlet。

接着有一个 HttpServlet 的类继承 GenericServlet,为了处理 HTTP 请求这类也会依赖 HttpServletRequest 和 HttpServletResponse。

Servlet 接口定义是 Servlet 容器的重要组成部分,Servlet 容器通过接口去管理接入的 Servlet 实体。

在了解 Servlet 接口规范和 Servlet 容器以后,我们知道如果需要加载不同的动态资源(Web 应用)需要利用 Servlet 容器去加载对应的 Servlet,那么这个加载过程是如何进行的?

我们接下来看看 Servlet 的请求和响应流程。

Tomcat的实现原理_第5张图片
如图 6 所示,这里通过 8 个步骤展示了 HTTP 请求和响应的流程:

1.用户通过浏览器发起 HTTP 请求。
2.Servlet 容器在接受到请求以后会对 HTTP 请求进行解析。
3.通过解析的结果以及配置信息创建 Servlet 实例。
4.实例创建以后调用 Servlet 实例的 init 方法完成实例初始化工作。
5.接下来就是调用 Servlet 中的 Service 方法完成具体业务。
6.Service 方法完成以后会将响应信息返回给 Servlet 容器。
7.Servlet 容器将 Servlet 返回的信息创建成 HTTP 响应返回给浏览器端。
8.最后容器关闭的时候,Servlet 容器调用 destroy 方法卸载掉 Servlet,并且释放对应的资源。

Tomcat的结构比较复杂,但是又比较模块化,所以只要我们找到了最核心的模块,对于tomcat的整体架构和工作原理就很好理解了。
Tomcat的实现原理_第6张图片Tomcat的实现原理_第7张图片

Connector(连接器)组件负责生成请求对象和响应对象的,Tomcat默认为HttpConnector,负责根据收到的Http请求报文生成Request对象和Response对象,并把这两个对象传递给Container,然后根据Response中的内容生成相应的HTTP报文。

Container是容器的父接口,所有子容器都必须实现这个接口,简单来说就是服务器部署的项目是运行在Container中的。Container里面的项目获取到Connector传递过来对应的的Request对象和Response对象进行相应的操作。
Tomcat的实现原理_第8张图片

Connector可以根据不同的的设计和应用场景进行替换,而一个Container可以对应多个Connector。多个Connector和一个Container就形成了一个Service,而Service可以对外提供服务。

而service由server提供生存环境并控制其生命周期,

假设有一个请求http://localhost:8080/test/index.jsp 我们梳理一下流程
Tomcat的实现原理_第9张图片
1.请求先发送到本机端口8080,然后被在那里侦听的Coyote HTTP/1.1 Connector获得;
2. Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应;
3.Engine获得请求localhost:8080/test/index.jsp,匹配它所有虚拟主机Host;
4.Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机);
5.localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context;
6.Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为"“的Context去处理);
7.path=”/test"的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet;
8.Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类;
9.构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法;
10.Context把执行完了之后的HttpServletResponse对象返回给Host;
11.Host把HttpServletResponse对象返回给Engine;
12.Engine把HttpServletResponse对象返回给Connector;
13.Connector把HttpServletResponse对象返回给客户browser;

上面我们知道了流程,但是connector是如何接受请求?又如何封装成Request和Response对象的呢?

Connector是使用ProtocolHandler来处理请求的,不同的ProtocolHandler代表不同的连接类型,比如我们之前说到的Http11Protocol使用的是普通Socket来连接的,Http11NioProtocol使用的是NioSocket来连接的

可以看到ProtocolHandler由包含了三个部件:Endpoint、Processor、Adapter
Tomcat的实现原理_第10张图片

Endpoint用来处理底层Socket的网络连接,由于是处理底层的Socket网络连接,因此Endpoint是用来实现TCP/IP协议的,

Processor用于将Endpoint接收到的Socket封装成Request,用来实现HTTP协议的

Adapter用于将Request交给Container进行具体的处理,用来将请求适配到Servlet容器进行具体的处理

Endpoint的抽象实现类AbstractEndpoint里面定义的Acceptor和AsyncTimeout两个内部类和一个Handler接口。Acceptor用于监听请求,AsyncTimeout用于检查异步Request的超时,Handler用于处理接收到的Socket,在内部调用Processor进行处理。

到了这里,我们可以回答上面的问题了,但是Container是如何进行处理的以及处理完之后是如何将处理完的结果返回给Connector我们还不清楚,
Tomcat的实现原理_第11张图片

Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine;

Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点;

Context:代表一个应用程序,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件;

Wrapper:每一Wrapper封装着一个Servlet的一个或多个实例;
Tomcat的实现原理_第12张图片

Context和Host的区别是Context表示一个应用,Tomcat中默认配置下webapps下的每一个文件夹目录都是一个Context而整个webapps就是一个Host站点

我们访问应用Context的时候,

如果是Host(webapps)下的其他应用,则可以使用http://www.test.com/docs进行访问,

默认指定的根应用(ROOT)是可以进行改变的

Container如何处理请求
Tomcat的实现原理_第13张图片

Container处理请求是使用Pipeline-Valve管道来处理的

Pipeline-Valve是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将处理后的请求返回,再让下一个处理着继续处理。

每个Pipeline都有特定的Valve,而且是在管道的最后一个执行,这个Valve叫做BaseValve,BaseValve是不可删除的;

在上层容器的管道的BaseValve中会调用下层容器的管道。

当执行到StandardWrapperValve的时候,会在StandardWrapperValve中创建FilterChain,并调用其doFilter方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法,这样请求就得到了处理!

当所有的Pipeline-Valve都执行完之后,并且处理完了具体的请求,这个时候就可以将返回的结果交给Connector了,Connector在通过Socket的方式将结果返回给客户端。

如果执行过程中间出现问题就抛异常。

你可能感兴趣的:(java,架构,架构,java,容器)