Jetty9源码剖析 - Connection组件 - HttpConnection

一、概念

Connection是一条逻辑连接,主要完成Http请求的解析以及通知后续处理。在Jetty中,它需要配合EndPoint来完成任务,EndPoint在IO事件发生时回调Connection的回调函数,从而触发Connection的数据处理。对于HTTP/1.x的版本,使用HttpConnection来处理,而对于HTTP/2,则使用Http2Connection来处理

二、继承体系

 

HttpConnection实现了HttpTransport接口,HttpTransport主要是用户反向传输,即响应时使用;同时实现了UpgradeFrom,主要实现当协议切换时的操作;还实现了Runnable,表示可以被运行,继承了AbstractConnection

三、总体架构

图中绿色表示我们要分析的HttpConnection,蓝色的表示内部协作其他组件,浅灰色的表示POJO对象(不过严格来说Request、Response不属于)
请求:前面SelectChannelEndPoint讲解过,IO事件到来时,它会触发回调函数,从而调用到HttpConnection.onFillable,这时候会触发EndPoint读数据,读完后让HttpParser解析,HttpParser会将解析后的数据,包括请求行、请求头相关信息存到MetaData.Request里面(其中请求行是直接记录到MetaData.Request,而请求头放到HttpFields),只要完成一次请求的请求行+请求头即表示当前完成解析,之后将MetaData.Request绑定到Request上面,这样后续业务就能拿到相关的元数据了
响应:当应用代码完成业务处理后,会调用Response来操作响应流,向流里面写入数据,即通过HttpOutput组件写入,最后会触发到HttpConnection来写入(前面提到HttpConnection其实是HttpTransport,也就是让他来完成写入),HttpConnection会利用HttpGenerator来生成响应数据(包括之前用户向Response里面添加的Header),HttpConnection最后调用SelectChannelEndPoint.write往Channel写入数据,这样响应就完成了

四、源码剖析

1. 创建

之前在讲解ServerConnectorManager时说到,其实在连接接受后,会利用HttpConnectionFactory来创建一个HttpConnection,如下图

创建的时候利用HttpConfiguration来配置当前HttpConnection相关的参数,例如缓冲大小,Http头部大小

构造函数比较简洁,将这些关联的组件保存下来,这里可以看到,创建连接会创建HttpGenerator,HttpChannel,HttpParser,以及将HttpChannel关联的HttpInput拿到(HttpInput其实就是ServletInputStream,后续会在HttpInput章节讲解)
我们先来看下super在做什么

endPoint、executor赋值,没啥说的,注意看readCallback,这里直接给了个ReadCallback,这个callback就是我们前面提的SelectChannelEndPoint通知用的回调,我们继续看它的实现

实现了Callback接口,succeeded会触发AbstractConnection.onFillable,即让HttpConnection实现的onFillable方法,这个是我们最核心的集读取、解析、处理三个大操作的方法,会在第3小节详细讲解,failed会触发连接关闭操作

最后我们看下HttpGenerator,HttpChannel,HttpParser的创建

比较简单,直接new,我们就看下newHttpParser,里面会newRequestHandler,其实就是返回了当前与HttpConnection关联的HttpChannelOverHttp(实现了这个接口),同时会限制HttpParser对头部解析的Buffer的大小

2. 回调注册

前面提到了在AbstractConnection里面创建了一个ReadCallback,这个回调最终调用到了HttpConnection.onFillable,那是哪里设置的?
之前在ManagedSelector提到过,创建EndPoint的时候,会创建SelectChannelEndPoint,同时会创建HttpConnection,创建完成后会调用SelectorManager.connectionOpened,如下图

SelectorManager.connectionOpened这个方法会调用HttpConnection.onOpen,最后会触发到SelectChannelEndPoint.fillInterested,如下图

这里仍然是AbstractConnection的方法,前面提到的ReadCallback会注册到EndPoint上面,如下图,是AbstractEndPoint的方法,将这个callback设置到fillInterest上面

3. 解析与处理

3.1 回顾IO事件触发过程

SelectChannelEndPoint在EPC执行拿到onSelected的任务后,对于读任务会触发fillInterest.fillable方法,如下图

这个方法是FillInterest类的方法(这个实例对象是集成到SelectChannelEndPoint的),因此最后调用到了ReadCallback.succeeded,上面已经讲过,就是调用到了HttpConnection.onFillable

3.2 onFillable

可以看到主要就是三部分:读取、解析、处理

3.2.1 读取

如下图,读取基本思路就是申请一个Buffer(从缓冲池拿),然后利用EndPoint将数据填充到Buffer里面去,返回读取的数量

下图中,获取Buffer也比较简单,从ByteBufferPool申请一个,注意第二个参数,REQUEST_BUFFER_DIRECT,默认是false,也就是默认对于请求缓冲并不是走Direct Buffer,如果对于大量的数据传输,例如文件这样的服务器,建议是配置这个为true,走Direct Buffer毕竟可以少1次不必要的堆外到堆内的一次数据拷贝

读取的逻辑还是比较简单,这里就说这么多,至于这里说的缓冲池,后面有时间我会开一篇文章来讲解

3.2.2 解析与处理

解析就是利用HttpParser的parseNext方法来解析,后续将会在HttpParser章节讲解,这里略过。值得注意的是,这里如果这里缓冲的引用计数为0,会自动释放该缓冲内存

解析完成后,返回一个handle变量,表示后续是否可以进行处理了,其实HttpParser只要解析到请求头+请求行完毕时,就会认为当前这个请求可以处理了,就直接返回true,否则如果整个HTTP报文不完整,比如读到了半包这种情况,这里会返回false,会要求再继续向这个ByteBuffer里面读数据,直到没有数据或可以处理为止。如果可以处理,这里就会调用HttpChannelOverHttp的handle方法,交给Server以及后续和Server关联的Handler继续执行

3.2.3 其他细节

上面其实提到了一个异常的场景,读到了半包数据,那其实整个HTTP报文是不完整的,Jetty这里的处理会尝试解析,直到解析到完整的请求行和请求头才会返回继续处理,否则一直读,如果解析的过程发现有非法的报文,例如请求行乱写,不按照HTTP规范发,或者请求头不按照规范发K/V格式,这些都视为无效请求,直接返回错误,并关闭当前这条连接

这里可能有一些读者会问,请求体里面的数据怎么办?其实这个地方会以默认8192字节的字节数组读取传入的数据,这个时候其实能读到一部分body体里面的数据,不过可能并不完整,其实对于容器而言,请求体主要是在应用层使用,因此这里没必要去解析,当应用层利用Request去拿InputStream的时候,会触发HttpConnection继续从EndPoint读取数据,对于之前读多了的,也会合并起来返回给应用,这样其实就是延时处理请求体数据,也是非常合理的操作

经过上面的解释,大家应该明白onFillable加个while的作用了

另外还有一点如果读取数据量filled为0,并且这时handle为false,其实就是说当前数据包不完整,需要继续读,就会调用fillInterested,继续向Channel注册OP_READ,因为之前我们曾经在SelectChannelEndPoint的onSelected方法提到过,当事件发生后,会取消这个Channel的事件监听,因此这里需要重新注册回去,保证后续的读仍然能正常进行

4. 响应

4.1 写应用数据

当应用层利用HttpServletResponse的Writer或者OutputStream写数据的时候,这些数据会被积攒起来,形成一个大缓冲,当应用完成处理后,这个时候会触发Response.closeOutput,这个操作会导致触发HttpOutput.close,这个方法会调用HttpChannel.write方法,而HttpChannel继续调用HttpTransport来发送数据,也就是HttpConnection.send方法

这里可以看到利用了sendCallback来调用(sendCallback其实是一个IteratingCallback,这个是一个比较复杂的回调类,后续有专门的章节讲解),最终这个回调会调用到HttpConnection.SendCallback类的process方法,开始生成数据并发送,接下来我们就来分析

4.2 生成并完成响应

 

其实从上面这个SendCallback的process方法来看,主要就是FLUSH操作是我们的核心逻辑
while循环第一行利用HttpGenerator完成响应行、响应头、响应体的生成,并返下一步要执行的操作,通常就是我们的FLUSH操作,FLUSH操作就会触发EndPoint的write方法,这样数据就落到了真实的传输层

五、总结

Connection是Jetty通信层核心组件,它主要利用其它组件对请求数据解析,并能根据应用层提供的数据生成响应数据。逻辑相对还是有些复杂,特别是加上了SendCallback这个IteratingCallback,让本来可以很简洁的代码,看起来非常绕,不过仔细研究还是能最终看出端倪,希望这篇文章对读者们理解Jetty有很大帮助。接下来的文章我会继续深入HTTP协议的解析(HttpParser),以及响应(HttpGenerator)等组件,希望大家持续关注~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值