前几天面试阿里,面试官问我如何解析HTTP协议,我大概说了一下的思路,他最后得出的结论是我对HTTP协议不了解,让我很受打击。回来看《深入剖析Tomcat》,研究一下Tomcat是如何解析HTTP协议的
1. 环境说明
- 《深入剖析Tomcat》是基于
tomcat-4.1.12
进行分析,这个版本在2002年发布,可以说是老古董了。不过就学习而言还是很好的工具. - Http协议的解析在连接器(connector) 中进行,连接器是一个独立的模块,可以被插入到容器中,
tomcat-4.1.12
里提供了默认连接器,但已被标注为过时。
2. 源码分析
2.1 连接器
默认连接器在org.apache.catalina.connector.http
包下,它实现了Connector
接口和Runnable
接口。分析的入口在run
方法中
2.1.1 run()
|
|
2.1.2 createProcessor()
具体的处理将在HttpProcessor
中进行,一个连接器会创建多个处理器,连接器的数量通过maxProcessors
和minProcessors
进行控制。今天的重点在http协议的解析,创建HttpProcessor
的一些细节就不说了
2.2 处理器
HttpProcessor
在独立的线程中对请求进行处理,连接器将请求分配给处理器(调用处理器的assign()
方法),处理器处理完成后将进行回收重复利用
2.2.1 run()
HttpProcessor
同样实现了Runnable
接口,在后台一直运行(被设置为守护线程),等待处理请求
2.2.2 await()
await()
监视available
变量,如果没有新的请求,就进入阻塞状态,同时run()
方法也会被阻塞
2.2.3 assign(Socket socket)
连接器调用assign
方法分配请求,它会唤醒阻塞的线程.这实际上是一个生产者-,消费者模型,通过available
变量,将请求从连接器传递到处理器。但这个实现并不优雅,并且效率也不高
2.2.5 parseConnection(Socket socket)
解析连接信息,获取Internet地址,检查是否使用代理
2.2.6 parseRequest(SocketInputStream input, OutputStream output)
requestLine
是一个HttpRequest
实例,其中包含3个char[]
,分别对应method,uri,protocol.调用SocketInputStream
的readRequestLine()
方法填充请求行,再获得对应的请求方法,URI,协议版本,(查询参数,session ID)
2.2.7 readRequestLine(HttpRequestLine requestLine)
readRequestLine
方法会分别填充请求行,URI,协议版本
2.2.8 parseHeaders(SocketInputStream input)
一个HttpHeader
包含一个name数组和value数组,通过SocketInputStream
中的readHeader
方法填充HttpHeader
对象,整个过程和readRequestLine
类似.
通过HttpHeader
对象,设置request对象对应的属性
至此整个HTTP协议的解析流程就完成了
3. 总结
- 连接器负责接收请求,处理器负责解析请求,每个处理器拥有自己的Request和Response对象,这两个对象可以重复使用
- 处理器处理流程
- 解析连接信息:设置Internet地址和代理信息
- 解析请求行:请求方法,URI,协议版本,查询参数(如果有),keep-alive属性,session ID(如果禁用了cookie),标准化URI地址
- 解析请求头:将请求头设置到对应的属性中,其中有几个重要的属性,cookie,content-length和content-type(在处理正文时会用到),conection(主要检查是否close值)
- 解析参数:先解析URI中的查询参数,再解析正文中的参数
- 调用容器的invoke方法
PS:
看完Tomcat如何解析才发现自己对HTTP协议不了解,只考虑到了HTTP协议的格式,没有考虑到不同版本的区别,特殊请求头的处理,不同请求方法的处理,cookie的解析,session的处理。
随着HTTP协议的发展,解析的难度越来越大,要求也越来越高(高效+正确);以上的代码在Tomcat4中已经废弃,换了更高效的连接器.等有时间去看一下Tomcat8的源码