Tomcat是非常流行的Web Server,它还是一个满足Servlet规范的容器。那么想一想,Tomcat和我们的Web应用是什么关系?
从感性上来说,我们一般需要把Web应用打成WAR包部署到Tomcat中,在我们的Web应用中,我们要指明URL被哪个类的哪个方法所处理(不论是原始的Servlet开发,还是现在流行的Spring MVC都必须指明)。
由于我们的Web应用是运行在Tomcat中,那么显然,请求必定是先到达Tomcat的。Tomcat对于请求实际上会进行下面的处理:
第一:提供Socket服务
Tomcat的启动,必然是Socket服务,只不过它支持HTTP协议而已!
这里其实可以扩展思考下,Tomcat既然是基于Socket,那么是基于BIO or NIO or AIO呢?
第二:进行请求的分发
要知道一个Tomcat可以为多个Web应用提供服务,那么很显然,Tomcat可以把URL下发到不同的Web应用。
第三:需要把请求和响应封装成request/response
我们在Web应用这一层,可从来没有封装过request/response的,我们都是直接使用的,这就是因为Tomcat已经为你做好了!
Tomcat的处理流程:把URL对应处理的Servlet关系形成,解析HTTP协议,封装请求/响应对象,利用反射实例化具体的Servlet进行处理即可。【Tomcat启动后,在接收到某个请求后,就可以根据请求路径和url-pattern进行匹配,如果匹配成功,则会把这个请求交给对应的servlet进行处理。】
处理流程简述:
- 浏览器在请求一个Servlet时,会按照HTTP协议构造一个HTTP请求,通过Socket连接发送给Tomcat
- Tomcat通过不同的IO模型都可以接收到Socket的字节流数据
- 接收到数据后,按HTTP协议解析字节流,得到HttpServletRequest对象
- 再通过HttpServletRequest对象,也就是请求信息,找到该请求对应的Host、Context、Wrapper
- 然后将请求交给Engine层处理
- Engine层处理完,就会将请求交给Host层处理
- Host层处理完,就会将请求交给Context层处理
- Context层处理完,就会将请求交给Wrapper层处理
- Wrapper层在拿到一个请求后,就会生成一个请求所要访问的Servlet实例对象
- 调用Servlet实例对象的service()方法,并把HttpServletRequest对象当做入参
- 从而就调用到Servlet所定义的逻辑
问题一:
Tomcat是如何根据HTTP协议来解析字节流的。
获取字节流
Tomcat底层是通过TCP协议,也就是Socket来获取网络数据的,那么从Socket上获取数据,就涉及到IO模型,在Tomcat8以后,就同时支持了NIO和BIO。
在Tomcat中,有一个组件叫做Connector,他就是专门用来接收Socket连接的,在Connector内部有一个组件叫ProtocolHandler,它有好几种实现:
Http11Protocol
Http11NioProtocol
Http11AprProtocol
解析字节流
不同的IO模型只是表示从Socket上获取字节流的方式不同而已,而获取到字节流之后,就需要进行解析了,之前我们说过,Tomcat需要按照HTTP协议的格式来解析字节流,下面是HTTP协议的格式:
所以,浏览器或者HttpClient在发送数据时,同样需要按照Http协议来构造数据(字符串),然后将字符串转成字节发送出去,所以Tomcat解析字节流的逻辑就是:
从获得的第一个字节开始,遍历每个字节,当遇到空格时,那么之前所遍历到的字节数据就是请求方法
然后继续遍历每个字节,当遇到空格时,那么之前遍历到的字节数据就是URL
然后继续遍历每个字节,当遇到回车、换行符时,那么之前遍历到的字节数据就是协议版本,并且表示请求行遍历结束
然后继续遍历当遇到一个回车符和换行符时,那么所遍历的数据就是一个请求头
继续遍历当遍历到两个回车符和换行符时,那么所遍历的数据就是一个请求头,并且表示请求头全部遍历完毕
剩下的字节流数据就表示请求体
值得注意的是,如果使用的是长连接,那么就有可能多个HTTP请求共用一个Socket连接,那么Tomcat在获取并解析Socket连接中的字节流时,该如何判断某个HTTP请求的数据在哪个位置结束了呢?也就是如何判断一个请求的请求体何时结束?
有两种方式:
-
设置Content-Length:在发送请求时直接设置请求体的长度,那么Tomcat在解析时,自然就知道了当前请求的请求体在哪个字节结束
-
设置Transfer-Encoding为chunk:也就是分块传输,在发送请求时,按如下格式来传输请求体,[chunk
size][\r\n][chunk data][\r\n][chunk size][\r\n][chunk
data][\r\n][chunk size = 0][\r\n][\r\n],注意最后的chunk
size=0和两个回车换行符,只要Tomcat解析到这些时,就表示接收到了最后一块,也就表示请求体结束了
问题二
在Tomcat中存在四大Servlet容器:
-
Engine:直接理解为一个Tomcat即可,一个Tomcat一个Engine
-
Host:一个Host表示一个虚拟服务器,可以给每个Host配置一个域名
-
Context:一个Context就是一个应用,一个项目
-
Wrapper:一个Wrapper表示一个Servlet的包装,Wrapper在后文详解
并且这四个Servlet容器是具有层次关系的:一个Engine下可以有多个Host,一个Host下可以有多个Context,一个Context下可以有多个Wrapper,一个Wrapper下可以有多个Servlet实例对象。
Tomcat接收到某个请求后,首先会判断该请求的域名,根据域名找到对应的Host对象,Host对象再根据请求信息找到请求所要访问的应用,也就是找到一个Context对象,Context对象拿到请求后,会根据请求信息找到对应的Servlet,那么Wrapper是什么?
我们定义的某个Servlet,在Tomcat中可能会存在多个该类型的实例对象,所以Tomcat需要再抽象出来一层,这一层就是Wrapper,一个Wrapper对应一个Servlet类型,Wrapper中有一个集合,用来存储该Wrapper对应的Servlet类型的实例对象。
四大容器的作用:
Engine:可以处理Tomcat所接收到所有请求,不管这些请求是请求哪个应用或哪个Servlet的。
Host:可以处理某个特定域名的所有请求
Context:可以处理某个应用的所有请求
Wrapper:可以处理某个Servlet的所有请求