https://github.com/StevenGerrad/how-tomcat-work-lab
- chapter1:
sample-server
- chapter2:
sample-servlet-container
- chapter3:
sample-connector
- chapter4:
user-default-connector
- chapter5:
servlet-container
- chapter6:
lifecycle
- chapter7:
logger
- chapter8:
classloader
- chapter9:
session
- chapter10:
security
TODO
PrintWriter out = response.getWriter();
writer的内部实现
第一章 一个简单的Web服务器
HttpServer
中,使用ServerSocket模拟服务器(只能解析静态资源文件)
第二章 一个简单的Servlet容器
ServletProcessor
继承javax.servlet.Servlet接口,使用URLClassLoader加载参数指定servlet类
HttpServer
实例化ServerSocket
并监听8080端口HttpServer
接受请求后- 实例化
Request
和Response
。 - 并根据情况实例化
ServerProcessor
或StaticResourceProcessor
,将Request
和Response
作为参数传给它们
- 实例化
第三章 连接器
Catalina中有两个主要的模块,连接器(connector)和容器(container)。连接器不需要知道servlet对象的类型,只负责提供HttpServletRequest实例和HttpServletResponse实例。
本章连接器解析HTTP请求头,使servlet实例获取到请求头、cookie和请求参数/值等信息,从而进行功能增强,可以运行更复杂一点的servlet类。
之后每章的应用程序都会按照模块划分。本章包含三个模块:连接器模块、启动模块和核心模块。
Bootstrap
实例化并调用HttpConnector
,HttpConnector
继承Runnable
,每次被调用会为自己创建新的线程并启动HttpConnector
实例化ServerSocket
并监听8080端口- 接受请求后实例化并调用
HttpProcessor
- 接受请求后实例化并调用
HttpProcessor
实例化StringManager
以及SocketInputStream
等专职功能类,从而帮助组装同时实例化出来的HttpRequest
、HttpResponse
- 并根据情况实例化
ServerProcessor
或StaticResourceProcessor
,将HttpRequest
和HttpResponse
作为参数传给它们,其中<font style="background-color:#8A8F8D;">SocketInputStream</font>
用于解析Http请求头HttpHeader
辅助SocketInputStream
进行解析
<font style="background-color:#8A8F8D;">StringManager</font>
用于读取.properties
配置文件(单例模式)HttpRequestLine
- 并根据情况实例化
(<font style="background-color:#8A8F8D;">StringManager</font>
等“灰色底”类几乎为Tomcat同名类副本)
上一章的HttpServer是在判断请求类型(Servlet/静态资源)前就对与请求进行了全部解析
- 本章
HttpConnector
不进行请求解析 HttpProcessor
在判断请求类型前只解析servlet用到的参数- request.getParameterXxx()等方法在具体的Servlet中才调用
对应下表:
具体任务 | 第二章 负责对象 | 第三章 负责对象 |
---|---|---|
等待HTTP请求 | HttpServer类 | HttpConnector实例 |
创建Request和Response对象 | HttpServer类 | HttpProcessor实例 |
创建HttpRequest对象(HttpRequestFacade使用了外观设计模式)
在servlet/JSP编程中,参数名jessionid用于携带一个会话标识符。会话标识符通常是作为Cookie嵌入的。
第四章 Tomcat的默认连接器
本章的“默认连接器”是Tomcat4中的默认连接器。已经被Coyote取代,但仍是一个不错的学习工具。
先上源码
import com.lab.tomcat.core.SimpleContainer;
import org.apache.catalina.connector.http.HttpConnector;
HttpConnector connector = new HttpConnector();
SimpleContainer container = new SimpleContainer();
connector.setContainer(container);
try{
connector.initialize();
connector.start();
// make the application wait until we press any key.
System.in.read();
}
catch (Exception e){
e.printStackTrace();
}
该模块除了Bootstrap,只自定义了SimpleContainer
和Servlet,而HttpConnector和HttpProcessor都采用了apache.catalina包。编写SimpleContainer继承Container
接口,实现public void invoke(Request request, Response response)
方法从而创建并调用具体Servlet就好。
Tomcat4的默认连接器,相比于第三章简单连接器的优化处:使用了对象池避免频繁创建对象;在很多地方使用字符数组来代替字符串。该连接器实现了HTTP1.1的全部新特性,也可以为使用HTTP1.0和HTTP0.9协议的客户端提供服务。
4.1 HTTP 1.1 的新特性
4.1.1 持久连接
HTTP1.1前,无论浏览器何时连接到Web服务器,当服务器请求资源返回后,就会断开与浏览器的连接,但是网页上会包含一些自他资源如静态资源、applet等。如果页面和它引用的所有资源文件都使用不同链接进行下载的话,处理过程会很慢。——因而HTTP1.1引入持久连接
HTTP1.1默认使用持久连接,也可以显式使用,方法是请求头信息
connection: keep-alive
4.1.2 块编码
建立了持久连接后,服务器可以从多个资源发送字节流,而客户端也可以使用该连接发送多个请求。因而发送方必须在每个请求和响应中添加"content-length"头信息,这样接受方才知道如何解释这些字节信息。
但通常发送方并不知道要发送多少字节,如servlet容器可能在接收到一些字节后就开始发送响应信息而非接收完所有信息。因而需要接收方能在不知道发送内容长度的情况下解析。
其实即使没有发出多个请求或多个响应,服务器或客户端也不需要知道有多少字节要发送。在HTTP1.0中服务器可以不写"content-length"头信息,发送完响应信息后,它就直接关闭连接。此时客户端一致读取内容知道读方法返回-1
。
HTTP1.1使用一个名为transfer-encoding
的特殊请求头,来指明字节流会分块发送。对每个块,块的长度(十六进制表示)后面会有一个回车/换行符(CR/LF),然后是具体数据,一个事务以一个长度为0的块标记。
eg. 假设用两个块发送下面38个字节内容,其中i的一个块为29个字节,第二个块为9个字节:
I'm as helpess as a kitten up a tree.
实际应该发送如下内容
1D\r\n
I'm as helpess as a kitten u
9\r\n
p a tree.
0\r\n
其中0\r\n
表明事务已经完成。
4.1.3 状态码100的使用
使用HTTP1.1的客户端可以在向服务器发送请求体前发送如下请求头并等待确认:
Expect: 100-continue
当客户端准备发送较长的请求体而不确定服务端是否会接收时,可以发送该头信息避免浪费。若服务器可以接收并处理该请求则发送响应头:
HTTP/1.1 100 Continue
4.2 Connector 接口
Tomcat连接器必须实现org.apache.catalina.Connector
接口,接口中最重要的方法是getContainer()
、setContainer
、createRequest()
、createResponse()
连接器与servlet容器是一对一的关系,箭头指向表示的关系是连接器知道要用哪个servlet容器。
com.lab.tomcat.Bootstrap#main
- org.apache.catalina.connector.http.HttpConnector#initialize (Connector接口限定方法)
- 从工厂中获取自己的持有的ServerSocket
- org.apache.catalina.connector.http.HttpConnector#start(LifeCycle接口限定方法)
- 为自己另创建并启动一个独立线程
- 通过自己的#threadStart()方法与start()方法循环调用配合start布尔值属性实现。
- 依据上下范围参数(minProcessors、maxProcessors)先创建minProcessor个HttpProcessor入栈(用栈方式管理的HttpProcessor对象池)
- org.apache.catalina.connector.http.HttpConnector#newProcessor
- 创建HttpProccessor实例并启动
- org.apache.catalina.connector.http.HttpProcessor#start
- 创建HttpProccessor实例并启动
- org.apache.catalina.connector.http.HttpConnector#newProcessor
- 为自己另创建并启动一个独立线程
org.apache.catalina.connector.http.HttpConnector#run 提供HTTP请求服务
- java.net.ServerSocket#accept
- org.apache.catalina.connector.http.HttpConnector#createProcessor
- 获取或创建一个HttpProcessor,不超过maxProcessors参数
- org.apache.catalina.connector.http.HttpProcessor#assign(Socket)
- (注意HttpProcessor#assign方法是HttpConnector#run的“连接器线程”调用的)
- 使用java.lang.Object#wait()方法阻塞监听this.available布尔属性值
- 传入参数socket
- 修改this.available属性值
- java.lang.Object#notifyAll
第三章中的HttpProcessor中的processor()方法是同步的,因此在接受下一个HTTP请求前,run()方法会一致等待,直到processor()方法返回
or