这里分析一个实际的请求是如何在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的处理过程:

  1. 在端口8080启动Server,并通知Service完成启动,Service通知Connector完成初始化和启动的过程
  2. Connector首先收到这个请求,会调用ProtocolHandler完成http协议的解析,然后交给SocketProcessor处理,解析请求头,再交给CoyoteAdapter解析请求行和请求体,并把解析信息封装到Request和Response对象中
  3. 把请求(此时应该是Request对象,这里的Request对象已经封装了Http请求的信息)交给Container容器
  4. Container容器交给其子容器——Engine容器,并等待Engine容器的处理结果
  5. Engine容器匹配其所有的虚拟主机,这里匹配到Host
  6. 请求被移交给hostname为localhost的Host容器,host匹配其所有子容器Context,这里找到contextPath为/examples的Context容器。如果匹配不到就把该请求交给路径名为”“的Context去处理
  7. 请求再次被移交给Context容器,Context继续匹配其子容器Wrapper,由Wrapper容器加载composite.jsp对应的servlet,这里编译的servlet是basic_002dcomparisons_jsp.class文件
  8. Context容器根据后缀匹配原则*.jsp找到composite.jsp编译的java类的class文件
  9. Connector构建一个org.apache.catalina.connector.Request以及org.apache.catalina.connector.Response对象,使用反射调用Servelt的service方法
  10. Context容器把封装了响应消息的Response对象返回给Host容器
  11. Host容器把Response返回给Engine容器
  12. Engine容器返回给Connector
  13. Connetor容器把Response返回给浏览器
  14. 浏览器解析Response报文
  15. 显示资源内容

其中的映射关系是由MapperListener类完成的。


原文博主地址:rhwayfunn

Tomcat 详解URL请求的更多相关文章

  1. TOMCAT原理详解及请求过程(转载)

    转自https://www.cnblogs.com/hggen/p/6264475.html TOMCAT原理详解及请求过程 Tomcat: Tomcat是一个JSP/Servlet容器.其作为Ser ...

  2. 详解SpringMVC请求的时候是如何找到正确的Controller

    详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析] 目录 前言 源码分析 重要接口介绍 SpringMVC初始化的时候做了什么 HandlerExecutionCha ...

  3. Tomcat详解及SNS系统的部署实现

    Tomcat详解及SNS系统的部署实现   http://jungege.blog.51cto.com/4102814/1409290

  4. Tomcat详解系列(1) - 如何设计一个简单的web容器

    Tomcat - 如何设计一个简单的web容器 在学习Tomcat前,很多人先入为主的对它的认知是巨复杂的:所以第一步,在学习它之前,要打破这种观念,我们通过学习如何设计一个最基本的web容器来看它需 ...

  5. Tomcat详解系列(2) - 理解Tomcat架构设计

    Tomcat - 理解Tomcat架构设计 前文我们已经介绍了一个简单的Servlet容器是如何设计出来,我们就可以开始正式学习Tomcat了,在学习开始,我们有必要站在高点去看看Tomcat的架构设 ...

  6. ASP.NET 运行时详解 揭开请求过程神秘面纱

    对于ASP.NET开发,排在前五的话题离不开请求生命周期.像什么Cache.身份认证.Role管理.Routing映射,微软到底在请求过程中干了哪些隐秘的事,现在是时候揭晓了.抛开乌云见晴天,接下来就 ...

  7. day08:软件系统的体系结构&Tomcat详解&Web应用&http协议

        day08 软件系统体系结构     常见软件系统体系结构B/S.C/S 1.1 C/S C/S结构即客户端/服务器(Client/Server),例如QQ: 需要编写服务器端程序,以及客户端 ...

  8. Tomcat详解系列(3) - 源码分析准备和分析入口

    Tomcat - 源码分析准备和分析入口 上文我们介绍了Tomcat的架构设计,接下来我们便可以下载源码以及寻找源码入口了.@pdai 源代码下载和编译 首先是去官网下载Tomcat的源代码和二进制安 ...

  9. Linux 安装 Tomcat 详解

    说明:安装的 tomcat 为解压版(即免安装版):apache-tomcat-8.5.15.tar.gz (1)使用 root 用户登录虚拟机,在根目录下的 opt 文件夹新建一个 software ...

随机推荐

  1. 这是一篇SQL注入文章

    目录 注入原理: 1.寻找注入点的方式或注入的地方可能包括. 2.注入点判断方法. 3.注入分类. 数字型: 字符型: 搜索型: XX型(也叫其他型): 4.注入提交方式. 5.注入攻击类型与方式. ...

  2. Daphile 安装手册 -- 官方文档译文 [原创]

    Daphile 安装手册(Daphile Installation) 英文原文:https://www.daphile.com/download/DaphileInstallation.pdf 采集日 ...

  3. javaNio 通道和缓冲区

    /** * 大多数操作系统可以利用虚拟内存将文件或文件一部分映射到内存中,然后这个文件就可以被当做内存数组一样被访问:避免底层IO的开销<p> * [通道]是一种用于磁盘文件的一种抽象:& ...

  4. 【转载】VUE的背景图引入

    我现在的项目要将登录页面的背景引一图片做为背景图片,按原jsp中的写法,发现无法找到背景图片,最后从网上查资料,采用上面的写法,成功显示出背景图片,参考网址 https://blog.csdn.net ...

  5. 如何使用容器镜像服务 TCR 轻松实现容器 DevOps

    作者周明,腾讯云容器产品工程师.目前主要负责腾讯云TKE.TCR等产品控制台的相关研发工作. 概述 当你使用云厂商提供的容器服务部署业务服务后,是否对交付部署全链路的效率有更高的需求,例如实现基于容器 ...

  6. k8s之HTTP请求负载分发

    一.导读 对于基于HTTP的服务来说,不同的URL地址经常对应不同的后端服务或者虚拟服务器,通常的做法是在应用前添加一个反向代理服务器Nginx,进行请求的负载转发,在Spring Cloud这个微服 ...

  7. asp.net core 5.0 中的 JsonConsole

    asp.net core 5.0 中的 JsonConsole Intro asp.net core 5.0 中日志新增了 JsonConsole,还是输出日志到 Console,但是会应用 Json ...

  8. Laya 断点调试

    Laya 打断点调试并查看堆栈的方法 发现直接加断点不行没办法调试,直接使用这中方法好像可以,选择F5调试 var camera =this.GameScene.getChildByName(&quo ...

  9. 【JavaWeb】Tomcat 使用

    Tomcat 使用 基础概念 JavaWeb: JavaWeb:所有通过 Java 语言编写可以通过浏览器访问的程序的总称,它是是基于请求和响应来开发的: 请求:客户端给服务器发送数据,即 Reque ...

  10. docker 数据卷的挂载和使用

    容器之间的数据共享技术, Docker容器产生的数据同步到本地 卷技术 --> 目录挂载, 将容器内的目录挂载到服务器上 使用命令来挂载 -v # 可以挂载多个目录 docker run -it ...