Tomcat 详解URL请求
这里分析一个实际的请求是如何在Tomcat中被处理的,以及最后是怎么样找到要处理的Servlet的?当我们在浏览器中输入http://hostname:port/contextPath/servletPath,前面的hostname与port用于建立tcp连接,由于Http也是基于Tcp协议的,所以这里涉及TCP连接的三次握手。后面的contextPath与servletPath则是与服务器进行请求的信息,contextPath指明了与服务器中哪个Context容器进行交互,服务器会根据这个URL与对应的Context容器建立连接,那么这个过程是如何实现的呢?
在Tomcat7(本文也是基于Tomcat7)中主要通过一个映射来完成的,这个映射的工作交给org.apache.tomcat.util.http.mapper.Mapper类来完成的,这个类保存了Container容器所有子容器的信息,在请求从Connector交给Container容器之前,Mapper会根据hostname和port将host容器与context容器设置到Request的mappingData属性中,这样在Connector的请求进入Container容器之前就知道了交给哪个容器了。
// Virtual host mapping
if (mappingData.host == null) {
Host[] hosts = this.hosts;
int pos = findIgnoreCase(hosts, host);
if ((pos != -1) && (host.equalsIgnoreCase(hosts[pos].name))) {
mappingData.host = hosts[pos].object;
contexts = hosts[pos].contextList.contexts;
nesting = hosts[pos].contextList.nesting;
} else {
if (defaultHostName == null) {
return;
}
pos = find(hosts, defaultHostName);
if ((pos != -1) && (defaultHostName.equals(hosts[pos].name))) {
mappingData.host = hosts[pos].object;
contexts = hosts[pos].contextList.contexts;
nesting = hosts[pos].contextList.nesting;
} else {
return;
}
}
}
// Context mapping
if (mappingData.context == null) {
int pos = find(contexts, uri);
if (pos == -1) {
return;
}
int lastSlash = -1;
int uriEnd = uri.getEnd();
int length = -1;
boolean found = false;
while (pos >= 0) {
if (uri.startsWith(contexts[pos].name)) {
length = contexts[pos].name.length();
if (uri.getLength() == length) {
found = true;
break;
} else if (uri.startsWithIgnoreCase("/", length)) {
found = true;
break;
}
}
if (lastSlash == -1) {
lastSlash = nthSlash(uri, nesting + 1);
} else {
lastSlash = lastSlash(uri);
}
uri.setEnd(lastSlash);
pos = find(contexts, uri);
}
uri.setEnd(uriEnd);
if (!found) {
if (contexts[0].name.equals("")) {
context = contexts[0];
}
} else {
context = contexts[pos];
}
if (context != null) {
mappingData.contextPath.setString(context.name);
}
}
if (context != null) {
ContextVersion[] contextVersions = context.versions;
int versionCount = contextVersions.length;
if (versionCount > 1) {
Object[] contextObjects = new Object[contextVersions.length];
for (int i = 0; i < contextObjects.length; i++) {
contextObjects[i] = contextVersions[i].object;
}
mappingData.contexts = contextObjects;
}
if (version == null) {
// Return the latest version
contextVersion = contextVersions[versionCount - 1];
} else {
int pos = find(contextVersions, version);
if (pos < 0 || !contextVersions[pos].name.equals(version)) {
// Return the latest version
contextVersion = contextVersions[versionCount - 1];
} else {
contextVersion = contextVersions[pos];
}
}
mappingData.context = contextVersion.object;
mappingData.contextSlashCount = contextVersion.slashCount;
}
// Wrapper mapping
if ((contextVersion != null) && (mappingData.wrapper == null)) {
internalMapWrapper(contextVersion, uri, mappingData);
}
下面启动服务器,在浏览器中输入http://localhost:8080/examples/jsp/jsp2/el/composite.jsp,断点调试可以mappingData.host属性为localhost,mappingData.contextPath.setString(context.name)中context.name为examples,mappingData.wrapperPath为/jsp/jsp2/el/composite.jsp,这验证了mappingData属性的有效性,那么mappingData属性是如何设置到Request对象的属性中的呢?
通过org.apache.catalina.connector.Request的源码可以知道,其是通过setContextPath方法与setHost方法设置进去的,其源码如下:
public void setHost(Host host) {
mappingData.host = host;
}
public void setContextPath(String path) {
if (path == null) {
mappingData.contextPath.setString("");
} else {
mappingData.contextPath.setString(path);
}
}
由于请求是从Connector传过来的,而CoyoteAdapter是Connector中处理请求的最后一个类,那么设置这两个属性的代码肯定在CoyoteAdapter类中,果不其然:
// This will map the the latest version by default
connector.getMapper().map(serverName, decodedURI, version,
request.getMappingData());
request.setContext((Context) request.getMappingData().context);
request.setWrapper((Wrapper) request.getMappingData().wrapper);
//Mapper的map方法
public void map(MessageBytes host, MessageBytes uri, String version,
MappingData mappingData)
throws Exception {
if (host.isNull()) {
host.getCharChunk().append(defaultHostName);
}
host.toChars();
uri.toChars();
internalMap(host.getCharChunk(), uri.getCharChunk(), version,
mappingData);
}
intenalMap方法执行就是代码清单5-4的内容,这样就把从Connector传入请求,并设置Request对象的mappingData属性的整个流程就打通了。还有一个疑问是为什么Mapper类中可以拥有Container所有子容器的信息呢?答案需要回到Tomcat启动过程图的第21步的startIntenal方法了:
public void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
// Find any components that have already been initialized since the
// MBean listener won't be notified as those components will have
// already registered their MBeans
findDefaultHost();
Engine engine = (Engine) connector.getService().getContainer();
addListeners(engine);
Container[] conHosts = engine.findChildren();
for (Container conHost : conHosts) {
Host host = (Host) conHost;
if (!LifecycleState.NEW.equals(host.getState())) {
// Registering the host will register the context and wrappers
registerHost(host);
}
}
}
这段代码就是将MapperListener作为一个监听者加到整个Container容器的每一个子容器中,这样任何一个子容器发生变化,MapperListener都将被通知,响应的mappingData属性也会改变。最后可以总结访问请求地址为http://localhost:8080/examples/composite.jsp的处理过程:
- 在端口8080启动Server,并通知Service完成启动,Service通知Connector完成初始化和启动的过程
- Connector首先收到这个请求,会调用ProtocolHandler完成http协议的解析,然后交给SocketProcessor处理,解析请求头,再交给CoyoteAdapter解析请求行和请求体,并把解析信息封装到Request和Response对象中
- 把请求(此时应该是Request对象,这里的Request对象已经封装了Http请求的信息)交给Container容器
- Container容器交给其子容器——Engine容器,并等待Engine容器的处理结果
- Engine容器匹配其所有的虚拟主机,这里匹配到Host
- 请求被移交给hostname为localhost的Host容器,host匹配其所有子容器Context,这里找到contextPath为/examples的Context容器。如果匹配不到就把该请求交给路径名为”“的Context去处理
- 请求再次被移交给Context容器,Context继续匹配其子容器Wrapper,由Wrapper容器加载composite.jsp对应的servlet,这里编译的servlet是basic_002dcomparisons_jsp.class文件
- Context容器根据后缀匹配原则*.jsp找到composite.jsp编译的java类的class文件
- Connector构建一个org.apache.catalina.connector.Request以及org.apache.catalina.connector.Response对象,使用反射调用Servelt的service方法
- Context容器把封装了响应消息的Response对象返回给Host容器
- Host容器把Response返回给Engine容器
- Engine容器返回给Connector
- Connetor容器把Response返回给浏览器
- 浏览器解析Response报文
- 显示资源内容
其中的映射关系是由MapperListener类完成的。
原文博主地址:rhwayfunn
Tomcat 详解URL请求的更多相关文章
- TOMCAT原理详解及请求过程(转载)
转自https://www.cnblogs.com/hggen/p/6264475.html TOMCAT原理详解及请求过程 Tomcat: Tomcat是一个JSP/Servlet容器.其作为Ser ...
- 详解SpringMVC请求的时候是如何找到正确的Controller
详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析] 目录 前言 源码分析 重要接口介绍 SpringMVC初始化的时候做了什么 HandlerExecutionCha ...
- Tomcat详解及SNS系统的部署实现
Tomcat详解及SNS系统的部署实现 http://jungege.blog.51cto.com/4102814/1409290
- Tomcat详解系列(1) - 如何设计一个简单的web容器
Tomcat - 如何设计一个简单的web容器 在学习Tomcat前,很多人先入为主的对它的认知是巨复杂的:所以第一步,在学习它之前,要打破这种观念,我们通过学习如何设计一个最基本的web容器来看它需 ...
- Tomcat详解系列(2) - 理解Tomcat架构设计
Tomcat - 理解Tomcat架构设计 前文我们已经介绍了一个简单的Servlet容器是如何设计出来,我们就可以开始正式学习Tomcat了,在学习开始,我们有必要站在高点去看看Tomcat的架构设 ...
- ASP.NET 运行时详解 揭开请求过程神秘面纱
对于ASP.NET开发,排在前五的话题离不开请求生命周期.像什么Cache.身份认证.Role管理.Routing映射,微软到底在请求过程中干了哪些隐秘的事,现在是时候揭晓了.抛开乌云见晴天,接下来就 ...
- day08:软件系统的体系结构&Tomcat详解&Web应用&http协议
day08 软件系统体系结构 常见软件系统体系结构B/S.C/S 1.1 C/S C/S结构即客户端/服务器(Client/Server),例如QQ: 需要编写服务器端程序,以及客户端 ...
- Tomcat详解系列(3) - 源码分析准备和分析入口
Tomcat - 源码分析准备和分析入口 上文我们介绍了Tomcat的架构设计,接下来我们便可以下载源码以及寻找源码入口了.@pdai 源代码下载和编译 首先是去官网下载Tomcat的源代码和二进制安 ...
- Linux 安装 Tomcat 详解
说明:安装的 tomcat 为解压版(即免安装版):apache-tomcat-8.5.15.tar.gz (1)使用 root 用户登录虚拟机,在根目录下的 opt 文件夹新建一个 software ...
随机推荐
- hadoop3.2+Centos7+5个节点主从模式配置
准备工作: hadoop3.2.0+jdk1.8+centos7+zookeeper3.4.5 以上是我搭建集群使用的基础包 一.环境准备 master1 master2 slave1 slave2 ...
- 解决threadLocal父子变量传递问题
一.问题的提出 在系统开发过程中常使用ThreadLocal进行传递日志的RequestId,由此来获取整条请求链路.然而当线程中开启了其他的线程,此时ThreadLocal里面的数据将会出现无法获取 ...
- 一文教你轻松搞定ANR异常捕获与分析方法
1. ANR 产生原理 关于 ANR 的触发原因,Android 官方开发者文档中 "What Triggers ANR?" 有介绍,如下: Generally, the syst ...
- mysql的binlog+maxwell+kakka
1.业务库痛点及解决⽅案 初期出⾏业务的订单相关,是以mysql作为业务库为基准的,但是随着业务线增多,每⽇新增数据指 数上涨,⼏乎在每天的⾼峰期期间,都会出现业务库所在服务器的cpu.IO.内存等跑 ...
- CentOS8_在线安装_网络源_网络镜像源填写格式_以及其他笔记
CentOS8_在线安装_网络源_网络镜像源填写格式_以及其他笔记 转载注明来源: 本文链接 来自osnosn的博客,写于 2020-10-1. 参考: Centos8.0.1905 在线安装源选择 ...
- 深入理解MySQL索引(上)
简单来说,索引的出现就是为了提高数据查询的效率,就像字典的目录一样.如果你想快速找一个不认识的字,在不借助目录的情况下,那我估计你的找好长时间.索引其实就相当于目录. 几种常见的索引模型 索引的出现是 ...
- .NET斗鱼直播弹幕客户端(2021)
.NET斗鱼直播弹幕客户端(2021) 离之前更新的两篇<.NET斗鱼直播弹幕客户端>已经有一段时间,近期有许多客户向我反馈刚好有这方面的需求,但之前的代码不能用了--但网上许多流传的No ...
- 内存性能测试 Memtester+mbw
Memtester简单介绍 Memtester主要是捕获内存错误和一直处于很高或者很低的坏位, 其测试的主要项目有随机值,异或比较,减法,乘法,除法,与或运算等等. 通过给定测试内存的大小和次数, 可 ...
- 【Linux】cron
每五分钟执行 */5 * * * * 每小时执行 0 * * * * 0 */1 * * * 效果相同 每天执行 0 0 * * * 每周执行 0 0 ...
- java中如何踢人下线?封禁某个账号后使其会话立即掉线!
需求场景 封禁账号是一个比较常见的业务需求,尤其是在论坛.社区类型的项目中,当出现了违规用户时我们需要将其账号立即封禁. 常规的设计思路是:在设计用户表时增加一个状态字段,例如:status,其值为1 ...