上一篇讲完了spring cloud,Spring boot,Tomcat容器之间关系以及执行顺序,这次再继续讲当一个请求到达服务时,执行的流程:
首先我们得把Tomcat讲清楚:
综上所述,一个tomcat只包含一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但有多个Connector,这样一个服务可以处理多个连接。
多个Connector和一个Container就形成了一个Service,有了Service就可以对外提供服务了,但是Service要提供服务又必须提供一个宿主环境,那就非Server莫属了,所以整个tomcat的声明周期都由Server控制。
Tomcat中的六个容器:
容器 | 作用 |
---|---|
Server容器 | 一个StandardServer类实例就表示一个Server容器,server是tomcat的顶级构成容器 |
Service容器 | 一个StandardService类实例就表示一个Service容器,Tomcat的次顶级容器,Service是这样一个集合:它由一个或者多个Connector组成,以及一个Engine,负责处理所有Connector所获得的客户请求。 |
Engine容器 | 一个StandardEngine类实例就表示一个Engine容器。Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名。当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理,Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理 |
Host容器 | 一个StandardHost类实例就表示一个Host容器,代表一个VirtualHost,虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配。每个虚拟主机下都可以部署(deploy)一个或者多个WebApp,每个Web App对应于一个Context,有一个Context path。当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。匹配的方法是“最长匹配”,所以一个path==”“的Context将成为该Host的默认Context。所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配 |
Context容器 | 一个StandardContext类实例就表示一个Context容器。一个Context对应于一个Web Application,一个WebApplication由一个或者多个Servlet组成。Context在创建的时候将根据配置文件CATALINA_HOME/conf/web.xml和WEBAPP_HOME/WEB-INF/web.xml载入Servlet类。当Context获得请求时,将在自己的映射表(mappingtable)中寻找相匹配的Servlet类。如果找到,则执行该类,获得请求的回应,并返回 |
Wrapper容器 | 一个StandardWrapper类实例就表示一个Wrapper容器,Wrapper容器负责管理一个Servlet,包括Servlet的装载、初始化、资源回收。Wrapper是最底层的容器,其不能在添加子容器了。Wrapper是一个接口,其标准实现类是StandardWrapper |
Server,Service,Engine一目了然,然后Host容器里可能会有点费解,这里先对host解释下:我们知道,在我们访问一个网站时,我们需要在浏览器的地址栏输入一个网页地址,浏览器会试图将域名解析成IP,这个IP代表了连接到互联网的一台主机(Host)。在浏览器向主机发送的HTTP请求中,也包含了请求的Host信息:例如,www.baoidu.com。在最简单的情况下,一台主机只需要对应一个IP,提供一个web服务即可,这种情况下一个IP就对应一台物理主机(Physical Host)。然而,在多数情况下,一台主机不会只提供一个web服务,因而一台物理主机就需要虚拟出多台主机来,这就是Virtual Host。Virtual Host根据实现技术的不同可以分为基于名称的Virtual Host和基于IP的Virtual Host。
基于名称的Virtual Host
基于名称的Virtual Host,对于基于名称的Virtual Host来说,每一个Virtual Host对应一个域名,这些域名都解析到同一个IP下去,这样,这些Virtual Host就共享了这个IP对应的物理主机的资源
基于IP的Virtual Host
不同于基于名称的Virtual Host,在基于IP的Virtual Host中,可以将多个IP地址绑定到同一台物理主机上去,这个是怎么做到的呢?一个方式在物理主机上配置多块网卡,另一个就是通过Virtual Network Interfaces来实现,在Tomcat中配置基于IP的Virtual Host。
好的,说完了host,我们看下请求的流程:
Connector:Connector 用于接收请求并将请求封装成Request 和Response 来具体处理,最底层是使用Socket 来进行连接的, Request 和Response 是按照HTTP 协议来封装的,所以Connector 同时实现了TCP/IP 协议和HTTP 协议, Request 和Response 封装完之后交给Container 进行处理,Container 就是Servlet 的容器, Container 处理完之后返回给Connector,最后Connector 使用Socket 将处理结果返回给客户端,这样整个请求就处理完了。
一个Connector将在某个指定端口上侦听客户请求,并将获得的请求交给Engine来处理,从Engine处获得回应并返回客户。
Tomcat有两个典型的Connector:
- 一个直接侦听来自browser的http请求
- 一个侦听来自其它WebServer的请求
Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求。
Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。
请求流程:
1、客户端发送请求:http://localhost:8080/wsota/wsota_index.jsp
2、请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得
3、 Connector把该请求交给它所在的Service的Engine来处理,并等待来自Engine的回应
4、 Engine获得请求localhost/wsota/wsota_index.jsp,匹配它所拥有的所有虚拟主机Host
5、Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)
6、localhost Host获得请求/wsota/wsota_index.jsp,匹配它所拥有的所有Context
7、Host匹配到路径为/wsota的Context(如果匹配不到就把该请求交给路径名为"“的Context去处理)
8、path=”/wsota"的Context获得请求/wsota_index.jsp,在它的mapping table中寻找对应的servlet
9、Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类
10、构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
11、Context把执行完了之后的HttpServletResponse对象返回给Host
12、Host把HttpServletResponse对象返回给Engine
13、Engine把HttpServletResponse对象返回给Connector
14、Connector把HttpServletResponse对象返回给客户browser
Tomcat的启动与类的具体实现
一个WEB应用对应一个context容器,也就是servlet运行时的负责管理servlet的容器。
添加一个web应用时将会创建一个StandardContext容器,并且给这个context容器设置必要的参数,url和path分别代表这个应用在tomcat中的访问路径和这个应用实际的物理路径,这两个参数与tomcat配置中的两个参数是一致的。
其中一个最重要的一个配置是ContextConfig,这个类会负责整个web应用配置的解析工作。
最后将这个context容器加入到父容器host中。
接下来会调用tomcat的start方法启动tomcat。
Tomcat的启动逻辑是基于观察者模式的,所有的容器都会继承Lifecycle接口,它管理着容器的整个生命周期,所有容器的修改和状态改变都会由它通知已经注册的观察者。
Tomcat启动的时序如下:
ContextConfig的init方法
当context容器初始状态设置Init时,添加到context容器的listener将会被调用。ContextConfig继承了LifecycleListener接口,它是在调用Tomcat.addWebapp时被加入到StandardContext容器中的。ContextConfig类会负责整个WEB应用的配置文件的解析工作。
ContextConfig的init方法将会主要完成一下工作:
创建用于解析XML配置文件的contextDigester对象
读取默认的context.xml文件,如果存在则解析它
读取默认的Host配置文件,如果存在则解析它
读取默认的Context自身的配置文件,如果存在则解析它
设置Context的DocBase
startInternal方法
ContextConfig的init方法完成后,Context容器会执行startInternal方法,这个方法包括如下几个部分:
创建读取资源文件的对象
创建ClassLoader对象
设置应用的工作目录
启动相关的辅助类,如logger,realm,resources等
修改启动状态,通知感兴趣的观察者
子容器的初始化
获取ServletContext并设置必要的参数
初始化“load on startuo”的Servlet
Web应用的初始化
web应用的初始化在14步,下面是该初始化的详细内容
WEB应用的初始化工作是在ContextConfig的configureStart方法中实现的,应用的初始化工作主要是解析web.xml文件,这个文件是一个WEB应用的入口。
Tomcat首先会找globalWebXml,这个文件的搜索路径是engine的工作目录下的org/apache/catalina/startup/NO-DEFAULT_XML或conf/web.xml。
接着会找hostWebXml,这个文件可能会在System.getProperty(“catalina.base”)/conf/$ {EngineName}/${HostName}/web.xml.default中。
接着寻找应用的配置文件examples/WEB-INF/web.xml,web.xml文件中的各个配置项将会被解析成相应的属性保存在WebXml对象中。
接下来会讲WebXml对象中的属性设置到context容器中,这里包括创建servlet对象,filter,listerner等,这些在WebXml的configureContext方法中。
以及下面是解析servlet的代码对象:
for (ServletDef servlet : servlets.values()) {
Wrapper wrapper = context.createWrapper();
String jspFile = servlet.getJspFile();
if (jspFile != null) {
wrapper.setJspFile(jspFile);
}
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
wrapper.setName(servlet.getServletName());
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
for (SecurityRoleRef roleRef : roleRefs) {
wrapper.addSecurityReference(roleRef.getName(), roleRef.getLink());
}
wrapper.setServletClass(servlet.getServletClass());
MultipartDef multipartdef = servlet.getMultipartDef();
if (multipartdef != null) {
if (multipartdef.getMaxFileSize() != null &&
multipartdef.getMaxRequestSize()!= null &&
multipartdef.getFileSizeThreshold() != null) {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation(),
Long.parseLong(multipartdef.getMaxFileSize()),
Long.parseLong(multipartdef.getMaxRequestSize()),
Integer.parseInt(
multipartdef.getFileSizeThreshold())));
} else {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation()));
}
}
if (servlet.getAsyncSupported() != null) {
wrapper.setAsyncSupported(
servlet.getAsyncSupported().booleanValue());
}
context.addChild(wrapper)