转载自ph0ly:http://www.ph0ly.com
一、概念
HttpChannel主要协助HttpConnection触发Server或Handler的处理操作,它内部会包装本次请求对应的Request、Response,并控制它们的生命周期。HttpChannelOverHttp是增强的HttpChannel,它对外暴露请求的参数处理接口(包括请求行、请求头、请求体等)。HttpChannel同时会提供Servlet3的异步特性,由于文章篇幅有限,这里会省略异步的分析,Servlet3异步特性将会在后续的专题文章中讲解
二、继承体系
继承体系比较简洁,HttpChannel实现了HttpOutput.Interceptor,也就是可以拦截HttpOutput的写入操作,同时自己是一个Runnable,而对于HttpChannelOverHttp,除了继承自HttpChannel外,还实现了HttpParser.RequestHandler,HttpParser.ComplianceHandler,其实最核心的还是HttpParser.HttpHandler接口,这个接口抽象了当HttpParser解析完每个模块时需要调用的方法,这样在HttpParser解析完每一部分的时候,就能向HttpChannelOverHttp发起调用,从而将数据与HttpChannelOverHttp关联
三、总体架构
HttpChannel主要就是串联Request、Response,而HttpChannelOverHttp在HttpChannel之上进一步提供增加Request需要的MetaData,HttpParser解析完成请求行会将数据放到MetaData.Request里面,而请求头解析完成后,会放到HttpFields,而这个HttpFields还会和MetaData.Request建立关联,这样MetaData就包含了一次请求需要的基础数据,Response也类似,在Response类会维护一个HttpFields对象,表示响应的头部,而响应行如果没有指定,默认会创建一个200 OK的行,这样后续HttpGenerator会拿着这些元数据生成响应报文。另外值得一提的是在Jetty里面每条连接都是共享一个Request、Response,因为HTTP/1.x是顺序执行,因此也就不存在在同一连接并发争抢这些对象,所以Jetty优化了这两个对象的生命周期,当Connection创建的时候,会创建HttpChannel,这样其实Request、Response都创建好了,对于每次请求处理完成后,HttpChannel会清空Request、Response的所有数据,这样又恢复到创建之初的数据,这个优化在大量请求的情况下,是非常有效的,要知道频繁的new对象一方面会增加内存分配的耗时,同时分配后GC的操作也会非常频繁,性能也会有一些影响,所以Jetty的这个优化还是不无道理的
四、源码剖析
1. 创建
HttpChannelOverHttp看起来还是比较简单,主要就是调super了
super自然就是HttpChannel,这里我们可以看到创建了HttpChannelState,这个主要用于Servlet3异步调用处理(这篇文章将会从同步的角度来分析),同时这里会创建Request、Response,并创建HttpInput、HttpOutput与它们两者关联,可以看到一个HttpChannel唯一映射一个Request、Response,而一个HttpChannel唯一映射一个HttpConnection,所以在同一条连接上,Request、Response是复用的(具体原因上面也提到过)
2. HTTP参数准备
前面的文章提到过,HttpParser在解析数据后,会调用HttpParser.RequestHandler,而HttpChannelOverHttp实现了这个接口,因此会触发
2.1 准备请求行
startRequest方法会在HttpParser请求行解析完成后调用,将请求行的方法、URI、Http版本号存下来
2.2 添加请求头
parsedHeader表示已经完成解析的请求头,HttpField表示一个请求头,里面包含请求头的K/V值
这里会判断一些特殊的头,例如Connection这样的,可能是要求处理完后Close,也可能是Keep-Alive,Host这种也需要拿到放到MetaData
之后将这个请求头放到HttpFileds类,保存下来
2.3 请求头完成
当HttpParser完成头部解析后,会触发HttpChannelOverHttp.headerComplete,这里会根据不同的协议请求头,例如对于HTTP/1.0,需要判断是否是持久连接,而对于HTTP/1.1,默认连接就是持久,那就需要判断是否带了Connection: Close,处理完立即关闭
如果不是持久连接,这里会把HttpGenerator的持久连接设置为false,以便后续Response完成后,关闭连接
然后调用onRequest,将MetaData设置到Request里面,如下图
这里会判断客户端是否要求服务器发送时间,如果是就直接将这个时间加到了Response的HttpFields了
中间我们可以看到Request接收了MetaData.Request,后续应用层就能从Request拿数据了
2.4 请求体处理与完成
当HttpParser对HTTP请求体解析的时候,解析到每一块请求体缓冲都将放到Request里面的HttpInput的缓冲队列(inputQ)里面去,如下图
通常情况对于HttpParser来说是不会立即处理请求体的,在应用层代码获取请求体时才会触发请求体解析,而请求体的解析其实就是一个个缓冲,在需要的时候挨个从EndPoint读出来,这样性能也就不存在问题
当整个请求体也处理完成了,这个时候HttpParser会调用contentComplete、messageComplete,其中messageComplete会通知HttpInput已经EOF了,这样就完成了整个请求体数据的处理
3. 调用
这里我们主要研究同步模式下的调用,异步模式涉及了多状态轮转,比较复杂,后续的专题文章将会讲解
HttpChannel对外提供的一个handle的核心方法,也就是HttpConnection解析完成后调用的
上图就是核心的同步模式下调用的逻辑,即action=DISPATCH。在DISPATCH分支里面会做一些准备,校验MetaData、设置Request为未处理,重新设置HttpOutput状态,然后把DispatcherType改为REQUEST,在利用用户自定义的调整Request里面的一些配置
核心的就是下面那一行,如果请求未被处理,就会调用getServer().handle(this),也就是让前面我们定义的Server来处理,而Server的handle方法会拿到之前设置的Handler来处理这个请求,这样就打通了整个调用链路
4. 完成
当Server触发Handler调用完成整个链路(Handler -> Filter -> Servlet),最后在Servlet触发往HttpOutput写数据,数据将会被放到缓冲,调用结束,结束后进入最下面的_state.unhandle,这时候action切换为COMPLETE,也就是进入上图的分支
如果这个请求进入COMPLETE还没完成处理,直接给404,如果处理完成了,关闭Response的HttpOutput(ServletOutputStream),HttpOutput.close会将积攒下来的Buffer全部刷出去,这个时候会调用HttpChannel.write方法(之前提到HttpChannel实现了HttpOutput.Interceptor)
HttpChannel.write实现如上图,其实就是简单记个数,然后调sendResponse
sendResponse会触发_transport来发送数据,前面提到过_transport其实就是HttpConnection。可以看到这里会将MetaData.Response传给HttpConnection.send,其实也就是将响应状态行、响应头传递下去了,而请求体作为另一个content参数传进来,同时附带一个回调,完成写入后调用
最后整个数据刷完后,会将HttpChannelState改为COMPLETED,已完成,然后调用自身onCompleted
这里会调用HttpConnection.onCompleted,我们一起来回顾下
从上面看这个方法逻辑很长,不过第一个分支在statusCode为101才会触发,通常我们也是>200,这里就忽略
可以看到调用了_channel.recycle,_generator.reset,_parser.reset,将这些复用组件全部置为初始状态。我们再来看下HttpChannel.recycle在干什么,准确来说HttpChannelOverHttp.recycle
HttpChannelOverHttp.recycle会把metadata回收了,以及HttpFields头也全部清空,再来看下super的回收
HttpChannel将Request、Response全部回收,而Request回收时会把HttpInput回收,Response回收时会把HttpOutput回收,至此整个HttpChannel请求调用完成调用
五、总结
HttpChannel从同步逻辑来看,还是比较容易分析的,由于Servlet3支持异步特性,因此HttpChannel在支持异步这块加入了HttpChannelState这样复杂的状态机,从而导致HttpChannel的代码异常复杂,个人觉得Jetty这块的抽象不算非常简洁。感兴趣的读者可以下来分析下源码,相信也能收获不少知识。接下来的文章会继续分析HttpParser、HttpInput、HttpOutput等组件,欢迎大家持续关注~