【源码收藏网会员】【卡盟psd源码】【mysql优化器源码】ChunkedStream源码

时间:2024-11-30 04:56:22 来源:仿168开奖 源码 分类:知识

1.Prometheus-Remote Read Meets Streaming【翻译】
2.面试官:说一下大文件分片下载
3.websocket+netty实时视频弹幕交互功能(Java版)
4.transfer-encoding和content-length的不同实现

ChunkedStream源码

Prometheus-Remote Read Meets Streaming【翻译】

        Prometheus新版本 2..0 的发布,他包含了许多bug fix和改进。你可以在这儿 查看变更 。这里有一个许多用户或项目十分期待的功能: chunked, streamed version of remote read API .

        在本文中,我将深入介绍我们在远程协议中更改了哪些内容,更改的原因以及如何有效地使用它。

        自从1.x版本,Prometheus有了与远程存储通过 remote API 交互的能力。

        这个API允许第三方系统通过两种方法与metrics 数据交互:

        这是将Prometheus数据放于第三方系统存储的最常见做法。在这种模式中,Prometheus周期性地向给定端点发送一批样本数据。

        在3月份基于 WAL 的Remote write在可靠性和资源占用率上都得到了巨大的提升。值得一提的是 这里 所有的第三方存储都支持该种方式。

        read方法不太常见。它是在 March

        添加的(服务端内部包含),自那以后没有看到显著的发展。

       æ™®ç½—米修斯2..0版本修复了Read API中已知的资源瓶颈。本文将重点讨论这些改进。

        remote read的关键点是不经过PromQL计算的前提下直接查询Prometheus storage ( TSDB )。 他和 Querier 接口类似,用于从存储层获取数据。

        这个特性允许对Prometheus采集到的时序数据进行访问, remote read主要使用场景如下:

        远程读API暴露了一个简单的HTTP endpoint ,它期望数据格式如下:

        通过这个协议,客户端可以通过 matchers 和 start 以及 end 的时间区间来获取到特定的时序数据

        返回数据格式如下:

        Remote read返回匹配的时序数据,包含raw数据(包含vaule和时间戳)

        对于remote read有如下2个关键问题。它虽然很容易使用和理解,但是我们定义的protobuf格式的单个HTTP请求中没有streaming功能。其次,响应的是raw格式数据(float值和int时间戳),而不是TSDB存储的基于‘Chunk’的经过编码和压缩后的数据。

        没有streaming的远程读取的server端逻辑是:

        下面是Prometheus和Thanos Sidecar(remote read 客户端)在remote read请求期间内的内存使用情况:

        值得注意的是,即便对于Prometheus原生Http接口的 query_range 方法而言, 查询,个series 也并不是一个好主意,因为你的浏览器也不希望获取,存储然后渲染数百兆字节的数据。此外,对于仪表板和呈现目的来说,拥有这么多数据也是不现实的,因为人不可能读取这么大量的数据。这就是为什么我们通常我们不会发起大于个时序的查询请求。

        虽然这样做很好,但是另外一种常规技术做法是通过聚合查询去获得聚合好的个时间序列,但底层查询引擎必须通过上千个序列去计算响应(例如使用 aggregators 。这就是为什么像Thanos这样的系统,在其他数据中,使用TSDB数据从远程读取,通常情况下,请求很重。

        理解Prometheus在查询时是如何迭代数据的对理解这个问题很有帮助。核心概念可以从 Querier 的 Select 方法返回的一个叫 SeriesSet 的类型中看出来。接口如下:

        上面这一系列接口为进程提供了一种基于“流”的能力。我们不再需要预先计算包含样本的序列列表。使用这个接口,每个 SeriesSet.Next() 实现都可以根据需要获取序列。我们还可以通过 SeriesIterator.Next 动态地分别获取每个样本。

        通过这个协议,Prometheus可以尽可能小的分配内存,因为PromQL引擎可以更优的对样本进行迭代,以计算出查询结果。TSDB以同样的方式实现SeriesSet,以一种最佳的方式从存储在文件系统中的块中一个接一个地获取序列,从而最小化分配。

        这对 remote read API来说十分重要,因为我们可以以相同的调用形式来迭代式的向客户端发送单个时序中的一部分chunk形式的数据。因为protobuf原生没有分割消息数据的机制,所以我们 扩展了 proto定义来允许发送一组小的protocol buffer消息,而不是单个巨大的消息体。我们把这种remote read模式称作 STREAMED_XOR_CHUNKS 而老的模式叫 SAMPLES 。扩展了protocol意味着Prometheus再也不需要缓冲整个响应了,他可以在调用 SeriesSet.Next 或者 SeriesIterator.Next 迭代时发送一个有序的独立帧,以尽可能的与下一个时序数据复用同一个内存页。

        现在, STREAMED_XOR_CHUNKS 模式的响应是以下一组Protobuf消息(帧)

        你可以发现消息帧不包含raw格式数据了。这是我们做的第二点提升:我们以chunk的形式发送消息样本(观看 这个视频 来了解更多关于chunk的知识)它与我们存储在TSDB中的chunk完全相同。

        我们最终使用了以下服务端逻辑:

        我们所有的设计都在 这里

        与旧的解决方案相比,这种新方法的性能如何?

        我们来将Prometheus 2..0 和 2..0 remote read特型进行对比。就如本文开头给出的初步结果,我用Prometheus做服务端,用Thanos的sidecar做远程读取的客户端。我使用 grpcurl 对Thanos sidecar执行gRPC调用来测试remote read。整个测试在我的笔记本((Lenovo X1 GB, i7 8th)上docker中的k8s环境里进行。(使用 kind )

        数据是人工生成的,表示高度动态的,个序列(最坏的情况)。

        测试的详细结果请见 thanosbench repo

        减少内存是我们解决方案的主要目标。在整个请求过程中,Prometheus的缓冲区大约为MB,而Thanos只有少量的内存使用。多亏了流式Thanos gRPC StoreAPI, sidecar现在是一个非常简单的代理。

        此外,我尝试了不同的时间范围和序列数量,但正如预期的那样,我一直看到Prometheus的分配最多为MB,而Thanos的分配则没有。这证明无论你单个请求多少个样本,我们的remote read始终使用恒定的内存大小。分配内存的多少受请求数据数量的影响大大减少,无论你请求多少数据,他都始终分配相同的内存。

        在并发性限制的帮助下,这使得针对用户流量进行容量规划变得更加容易。

        在我的测试期间,CPU使用率也有所提高,使用的CPU时间减少了两倍。

        通过streaming和更少的编码次数,我们同时还实现了减少remote read请求的响应延迟。

        Remote read 8小时时间跨度包含个序列:

        2h时间跨度:

        在Prometheus和Thanos侧处理和序列化的时间上,除了低2.5倍的响应延迟外, stream版本的响应是及时的,而非 stream版本客户端延迟s( real minus user time)。

        Remote read以向后和向前兼容的方式进行了扩展。这要归功于protobuf和 accepted_response_types 被旧版本所忽略的字段,同时如果旧的客户端(假设采用 SAMPLES 模式的remote read)不支持 accepted_response_types 他也能正常工作。

        remote read协议前后版本兼容方式:

        为了使用新的基于streamed remote read的Prometheus v2..0,第三方系统必须将 accepted_response_types = [STREAMED_XOR_CHUNKS] 添加到请求中。

        Prometheus就会用 ChunkedReadResponse 来替代老版本消息体。每个 ChunkedReadResponse 消息都符合varint大小和固定大小bigendian uint用于CRC Castagnoli校验和。

        对于go语音来说我们推荐使用 ChunkedReader 来直接读取流

        注意, storage.remote.read-sample-limit 设置将在 STREAMED_XOR_CHUNKS. storage.remote.read-concurrent-limit 条件下不再起作用。

        也提供了一个新的配置项 storage.remote.read-max-bytes-in-frame 来控制每个消息体的最大大小。建议默认为1MB,因为谷歌建议protobuf消息 不大于1MB .。

        正如前面提到的, Thanos 因为这个特性收益颇丰。Streamed remote read 在 v0.7.0 被增加,因此,配合Prometheus 2..0 或更高版本,都将自动采用 streamed remote read 的形式。

        Release 2..0引入了扩展的 remote read和Prometheus服务器端实现,然而,为了充分利用扩展的远程读协议的优势,目前还需要做一些事情:

        综上所述,分块、流远程读取的主要好处是:

        如果您有任何问题或反馈,一如既往,请随时在GitHub上提交问题或在邮件列表上提问。

面试官:说一下大文件分片下载

       文件上传、下载是常见需求,大文件上传通过分片上传优化,如阿里云 OSS 的大文件分片上传。那么,大文件下载如何优化呢?答案同样是源码收藏网会员分片下载,或称为流式传输。

       我们通过 Nest 项目探索实现。首先,创建 Nest 项目并在 AppController 中添加下载路由。运行服务,浏览器访问下载路由,触发下载,Devtools 显示正确设置的 header。header 也可通过 @Header 装饰器添加,效果相同。

       然而,卡盟psd源码直接返回文件全部内容会占用大量内存,对于大文件下载,需要实现流式下载,即读取一部分返回一部分。HTTP 协议提供此功能,通过设置 transfer-encoding:chunked。这种方式下,文件以块形式分段传输,直至传输结束。

       流式传输原理基于 transfer-encoding:chunked,它让服务器不断返回内容直至返回一个空块表示结束。例如,文件内容可以分块为“Hello”、“,”、“World”、“!”,mysql优化器源码每块长度分别为 5、1、5、1,最后以长度为 0 的空块代表传输结束。

       在代码中实现流式传输,使用 Node.js 的 stream API 读取内容,配合 Nest 封装的 StreamableFile 类实现流式返回。不再返回 Content-Length,而是返回 Transfer-Encoding:chunked,实现流式传输。

       Nest 项目中不要直接使用 Node.js 的 stream API,因为其事件处理较为复杂。StreamableFile 类简化了操作,自动处理流式传输的流程。使用此类时,玖伍社区源码破解Content-Type 默认为 application/octet-stream,可修改为其他格式。

       浏览器和 HTTP 协议内置了流式下载支持,只需设置对应 header 即可,无需额外实现。下载响应体结构通过 wireshark 抓包验证,每个分块包含 chunk size 和 chunk data。Nest 小册仓库上传了相关案例代码。

       总结,大文件下载优化通过分片下载实现,HTTP 协议支持流式传输,只需设置 transfer-encoding:chunked。Nest 项目中使用 StreamableFile 类简化了流式传输操作。通过 wireshark 抓包可验证响应体结构。更多内容可参考 Nest 通关秘籍小册。

websocket+netty实时视频弹幕交互功能(Java版)

       年了,商业大亨源码还有不支持弹幕的视频网站吗,现在各种弹幕玩法层出不穷,抽奖,ppt都上弹幕玩法了,不整个弹幕都说不过去了,今天笔者就抽空做了一个实时视频弹幕交互功能的实现,不得不说这样的形式为看视频看直播,讲义PPT,抽奖等形式增加了许多乐趣。

一.技术选型1.1netty

       官方对于netty的描述:netty.io/

       主要关键词描述:netty是异步事件驱动网络框架,可做各种协议服务端,并且支持了FTP,SMTP,HTTP等很多协议,并且性能,稳定性,灵活性都很棒。

       可以看到netty整体架构上分了三个部分:

       以零拷贝,一致性接口,扩展事件模型的底层核心。

       Socket,Datagram,Pipe,HttpTunnel作为传输媒介。

       传输支持的各种协议,HTTP&WebSocket,SSL,大文件,zlib/gzip压缩,文本,二进制,GoogleProtobuf等各种各种的传输形式。

1.2WebSocket

       WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于年被IETF定为标准RFC,并由RFC补充规范。WebSocketAPI也被W3C定为标准。

       WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocketAPI中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

1.3为什么做这样的技术选型。

       由上述可知,实时直播交互作为互动式是一个双向数据传输过程。所以使用webSocket。netty本身支持了webSocket协议的实现,让实现更加简单方便。

二.实现思路2.1服务架构

       整体架构是所有客户端都和我的服务端开启一个双向通道的架构。

       如下图:

2.2传输流程

       如下图:

三.实现效果

       先看看效果吧,是不是perfect,接下来就来看具体代码是怎么实现的吧。

视频直播弹幕示例四.代码实现4.1项目结构

       一个maven项目,将代码放一个包下就行。

4.2Java服务端

       Java服务端代码,总共三个类,Server,Initailizer和Handler。

       4.2.1先做一个nettynio的服务端:一个nio的服务,开启一个tcp端口。

importio.netty.bootstrap.ServerBootstrap;importio.netty.channel.ChannelFuture;importio.netty.channel.EventLoopGroup;importio.netty.channel.nio.NioEventLoopGroup;importio.netty.channel.socket.nio.NioServerSocketChannel;/***Copyright(c)lbhbinhao@.com*@authorliubinhao*@date/1/*++++__________________*+++//|//|//|*+/_____/|/_____/|/_____/|*|||*________|||*|||||/|||*|||||/___________|||*|||___________________||____________|||*||//|*||/_________________//||/||/*|_________________________|/b|_____|/|_____|/*/publicenumBulletChatServer{ /***Serverinstance*/SERVER;privateBulletChatServer(){ EventLoopGroupmainGroup=newNioEventLoopGroup();EventLoopGroupsubGroup=newNioEventLoopGroup();ServerBootstrapserver=newServerBootstrap();server.group(mainGroup,subGroup).channel(NioServerSocketChannel.class).childHandler(newBulletChatInitializer());ChannelFuturefuture=server.bind();}publicstaticvoidmain(String[]args){ }}

       4.2.2服务端的具体处理逻辑

importio.netty.channel.ChannelInitializer;importio.netty.channel.ChannelPipeline;importio.netty.channel.socket.SocketChannel;importio.netty.handler.codec.**@authorliubinhao*@date/1/*++++__________________*+++//|//|//|*+/_____/|/_____/|/_____/|*|||*________|||*|||||/|||*|||||/___________|||*|||___________________||____________|||*||//|*||/_________________//||/||/*|_________________________|/b|_____|/|_____|/*/publicclassBulletChatInitializerextendsChannelInitializer<SocketChannel>{ @OverrideprotectedvoidinitChannel(SocketChannelch)throwsException{ ChannelPipelinepipeline=ch.pipeline();pipeline.addLast(newHttpServerCodec());pipeline.addLast(newChunkedWriteHandler());pipeline.addLast(newHttpObjectAggregator(*));pipeline.addLast(newIdleStateHandler(8,,));pipeline.addLast(newWebSocketServerProtocolHandler("/lbh"));pipeline.addLast(newBulletChatHandler());}}

       后台处理逻辑,接受到消息,写出到所有的客户端:

importio.netty.channel.Channel;importio.netty.channel.ChannelHandler;importio.netty.channel.ChannelHandlerContext;importio.netty.channel.SimpleChannelInboundHandler;importio.netty.channel.group.ChannelGroup;importio.netty.channel.group.DefaultChannelGroup;importio.netty.handler.codec.**@authorliubinhao*@date/1/*++++__________________*+++//|//|//|*+/_____/|/_____/|/_____/|*|||*________|||*|||||/|||*|||||/___________|||*|||___________________||____________|||*||//|*||/_________________//||/||/*|_________________________|/b|_____|/|_____|/*/publicclassBulletChatHandlerextendsSimpleChannelInboundHandler<TextWebSocketFrame>{ //用于记录和管理所有客户端的channelpublicstaticChannelGroupchannels=newDefaultChannelGroup(GlobalEventExecutor.INSTANCE);@OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,TextWebSocketFramemsg)throwsException{ //获取客户端传输过来的消息Stringcontent=msg.text();System.err.println("收到消息:"+content);channels.writeAndFlush(newTextWebSocketFrame(content));System.err.println("写出消息完成:"+content);}@OverridepublicvoidhandlerAdded(ChannelHandlerContextctx)throwsException{ channels.add(ctx.channel());}@OverridepublicvoidhandlerRemoved(ChannelHandlerContextctx)throwsException{ StringchannelId=ctx.channel().id().asShortText();System.out.println("客户端被移除,channelId为:"+channelId);channels.remove(ctx.channel());}@OverridepublicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause)throwsException{ cause.printStackTrace();//发生异常之后关闭连接(关闭channel),随后从ChannelGroup中移除ctx.channel().close();channels.remove(ctx.channel());}}

       4.3网页客户端实现

<!DOCTYPEhtml><html><head><metacharset="utf-8"><meta/libs/jquery/2.1.4/jquery.min.js"type="text/javascript"></script><script>//1.获取元素varoBox=document.querySelector('.box');//获取.box元素varcW=oBox.offsetWidth;//获取box的宽度varcH=oBox.offsetHeight;//获取box的高度functioncreateEle(txt){ //动态生成span标签varoMessage=document.createElement('span');//创建标签oMessage.innerHTML=txt;//接收参数txt并且生成替换内容oMessage.style.left=cW+'px';//初始化生成位置xoBox.appendChild(oMessage);//把标签塞到oBox里面roll.call(oMessage,{ //call改变函数内部this的指向timing:['linear','ease-out'][~~(Math.random()*2)],color:'#'+(~~(Math.random()*(1<<))).toString(),top:random(0,cH),fontSize:random(,)});}functionroll(opt){ //弹幕滚动//如果对象中不存在timing初始化opt.timing=opt.timing||'linear';opt.color=opt.color||'#fff';opt.top=opt.top||0;opt.fontSize=opt.fontSize||;this._left=parseInt(this.offsetLeft);//获取当前left的值this.style.color=opt.color;//初始化颜色this.style.top=opt.top+'px';this.style.fontSize=opt.fontSize+'px';this.timer=setInterval(function(){ if(this._left<=){ clearInterval(this.timer);//终止定时器this.parentNode.removeChild(this);return;//终止函数}switch(opt.timing){ case'linear'://如果匀速this._left+=-2;break;case'ease-out'://this._left+=(0-this._left)*.;break;}this.style.left=this._left+'px';}.bind(this),/);}functionrandom(start,end){ //随机数封装returnstart+~~(Math.random()*(end-start));}varaLi=document.querySelectorAll('li');//functionforEach(ele,cb){ for(vari=0,len=aLi.length;i<len;i++){ cb&&cb(ele[i],i);}}forEach(aLi,function(ele,i){ ele.style.left=i*+'px';});//产生闭包varobj={ num:1,add:function(){ this.num++;//obj.num=2;(function(){ console.log(this.num);})}};obj.add();//window</script></body></html>

       这样一个实时的视频弹幕功能就完成啦,是不是很简单,各位小伙伴快来试试吧。

五.小结

       上班撸代码,下班继续撸代码写博客,这个还是很简单,笔者写这个的时候一会儿就写完了,不过这也得益于笔者很久以前就写过netty的服务,对于Http,Tcp之类协议也比较熟悉,只有前端会有些难度,问下度娘,也很快能做完,在此分享出来与诸君分享。

       来源:binhao.blog.csdn.net/article/details/

transfer-encoding和content-length的不同实现

       å‰æ®µæ—¶é—´åœ¨é¡¹ç›®ä¸­çœ‹åˆ°å¦‚下的代码:

       1

       2

       3

       HttpServletResponse response = (HttpServletResponse)servletResponse;

       response.setHeader("Transfer-Encoding", "utf8");

       filterChain.doFilter(servletRequest, servletResponse);

       åŽŸæ„æ˜¯æƒ³å¯¹è¾“出的内容进行编码,却用错了响应头,结果这个错误的响应头对后面的客户端程序带来了许多麻烦。这里有必要对这个这块的内容进行详细地了解。

       ä¼ è¾“数据编码:Transfer-Encoding

       æ•°æ®ç¼–码,即表示数据在网络传输当中,使用怎么样的保证方式来保证数据是安全成功地传输处理。可以是分段传输,也可以是不分段,直接使用原数据进行传输。

       æœ‰æ•ˆçš„值为:Trunked和Identity.

       ä¼ è¾“内容编码:Content-Encoding

       å†…容编码,即整个数据信息是在数据器端经过怎样的编码处理,然后客户端会以怎么的编码来反向处理,以得到原始的内容。这里的内容编码主要是指压缩编码,即服务器端压缩,客户端解压缩。

       å¯ä»¥å‚考的值为:gzip,compress,deflate和identity。

       ä¼ è¾“内容格式:Content-Type

       å†…容格式,即接收的数据最终是以何种的形式显示在浏览器中。可以是一个图片,还是一段文本,或者是一段html。内容格式额外支持可选参数,charset,即实际内容的字符集。通过字符集,客户端可以对数据进行解编码,以最终显示可以看得懂的文字(而不是一段byte[]或者是乱码)。

       3种描述信息,可以由下图来表示(来源于《Http权威指南》):

       ä»Žä¸Šæ–‡ä¸­ï¼Œå¯ä»¥çœ‹å‡ºï¼Œå®žé™…上原filter中的内容可能是想表达以下的意思:

       1

       2

       3

       response.setContentType("text/html;charset=UTF8");

       //或者

       response.setContentType("application/json;charset=UTF8");

       å†…容长度:Content-Length

       å†…容长度,即表示整个传输内容的有效长度信息。客户端可以通过此头信息来判断接收的数据是否已经完全接收。此编码和transfer-encoding相冲突,因为transfer-encoding会通过额外的处理方式来改变数据的组织方式,就会改变实际的数据长度,如果客户端仍按照原content-length来处理的话,则不会接收到完整的数据。

       ç”±äºŽtransfer-encoding和content-length之间存在冲突问题,因此在服务端和客户端就会有相应的实现来支持相应的数据处理。整个处理过程按照RFC 来处理。

       å¤„理规则:(ion:close时,即表示客户端只会请求一次,不会使用Keep-Alive,这样的话,不需要使用trunked传输,

       //因为客户端知道何时数据已经传输完,使用read() == -1即可判断

        if (entityBody && http && !connectionClosePresent) {

       //使用ChunkedOutputFilter来对传输的数据二次处理,即分段传输

        getOutputBuffer().addActiveFilter

        (outputFilters[Constants.CHUNKED_FILTER]);

        contentDelimitation = true;

        headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);

        } else {

       //最后使用原始数据传输方式

        getOutputBuffer().addActiveFilter

        (outputFilters[Constants.IDENTITY_FILTER]);

        }

        }

       æ­¤æ®µä»£ç å°†åœ¨åº”用处理完逻辑或者调用response.outputStream.write时会调用。详细的处理逻辑,可参考上文中的注释。

       éœ€è¦æ³¨æ„çš„是,由于Http工作在TCP/IP之上,因此数据的完整性保证已经不需要由Http来处理了。所以依靠trunked来保证数据完整性已经没有太大意义。现在trunked的意义在于针对keep alive传输时,trunked可以通过特殊的处理来告诉客户端(通过发送头长度0来标识),该次的数据已经响应完毕。客户端可以处理并再次使用该连接进行下一次处理了。所以在上面的trunked处理中,tomcat如果认为没有使用trunked的必要时,就不会强制使用trunked了(如connection:close这种一次性请求模型)

       å®¢æˆ·ç«¯å®žçŽ°HttpClient

       httpclient(版本4.3.3)的主要实现在类EntityDeserializer中,即如何去获取数据并反向解码。实现方法为doDeserialize,主要的实现如下所示:

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       final long len = this.lenStrategy.determineLength(message);

       if (len == ContentLengthStrategy.CHUNKED) {

        entity.setChunked(true);

        entity.setContentLength(-1);

        entity.setContent(new ChunkedInputStream(inbuffer));

       } else if (len == ContentLengthStrategy.IDENTITY) {

        entity.setChunked(false);

        entity.setContentLength(-1);

        entity.setContent(new IdentityInputStream(inbuffer));

       } else {

        entity.setChunked(false);

        entity.setContentLength(len);

        entity.setContent(new ContentLengthInputStream(inbuffer, len));

       }

       å³é€šè¿‡åˆ¤æ–­lengh值来确定是使用不同的数据解析。解析出来的流处理共有3种不同的处理方式,即transfer-encoding中指定的chunked和identity,以及由content-length指定的处理方式。

       å¯¹length的判断逻辑如下所示:

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

        final Header transferEncodingHeader = message.getFirstHeader(HTTP.TRANSFER_ENCODING);

        // We use Transfer-Encoding if present and ignore Content-Length.

        // RFC, 4.4 item number 3

       //首先根据RFC 4.4中介绍的,首先处理transfer-encoding

        if (transferEncodingHeader != null) {

        final HeaderElement[] encodings;

        encodings = transferEncodingHeader.getElements();

        // The chunked encoding must be the last one applied RFC, .

        final int len = encodings.length;

       //只判断是否和trunked和identity相等,在都不相等的情况下默认使用identity,以避免解析出错的情况

        if (HTTP.IDENTITY_CODING.equalsIgnoreCase(transferEncodingHeader.getValue())) {

        return IDENTITY;

        } else if ((len > 0) && (HTTP.CHUNK_CODING.equalsIgnoreCase(

        encodings[len - 1].getName()))) {

        return CHUNKED;

        } else {

        return IDENTITY;

        }

        }

       //然后再使用content-length,这里同样判断,只有在确定好conten-length的情况下才使用,如果确定不好,仍然使用identity

        final Header contentLengthHeader = message.getFirstHeader(HTTP.CONTENT_LEN);

        if (contentLengthHeader != null) {

        long contentlen = -1;

        final Header[] headers = message.getHeaders(HTTP.CONTENT_LEN);

        contentlen = Long.parseLong(header.getValue());

        }

        if (contentlen >= 0) {

        return contentlen;

        } else {

        return IDENTITY;

        }

        }

       åœ¨ä»¥ä¸Šçš„处理当中,我们看到identity处理方式最多的。可以理解为,只要是不能解析的都使用identity,即原始处理方式。

       é€šè¿‡ä»¥ä¸Šçš„实现,可以了解客户端在接收完数据之后的不同响应方式和处理逻辑,这也能解释在项目中的奇怪情况了。

       é—®é¢˜å‡ºçŽ°åŠè§£å†³

       åœ¨æˆ‘们的项目中,由于上面的错误的filter的问题。我们在之前使用httpclient在接收数据时(使用httpPost),本来想接收一个json数据串,即总是返回类似以下的数据:

       1

       2

       3

       

       { abxxx.......}

       0

       è¿™ç§æƒ…况只在处理我们请求的服务才会出现,请求其它之前的项目服务不会出现。多次发现这个特殊的值,好像表示数据长度。在不知道原因的情况下,我们通过在服务中加入以下代码之后,问题好像就解决了:

       1

       2

       3

       byte[] bytes = generatedBytes();//生成json bytes数组信息

       response.setContentLength(bytes.length);//强制性设置contentLength值

       response.getoutputStream.write(bytes);

       ä½†åˆæœ‰ä¸€ä¸ªé—®é¢˜å‘生了,发现响应很慢。每次请求都要花费接近s的时间,但监控服务代码,响应很快的。而且在服务返回之后,客户端需要继续等待一段时间才返回数据。经debug代码,最终发现httpclient在使用EntityUtils.toString中是这样写的:

       1

       2

       3

       4

       5

       6

       7

       8

       final Reader reader = new InputStreamReader(instream, charset);

       final CharArrayBuffer buffer = new CharArrayBuffer(i);

       final char[] tmp = new char[];

       int l;

       while((l = reader.read(tmp)) != -1) {

        buffer.append(tmp, 0, l);

       }

       return buffer.toString();

       è¿™é‡Œçš„while循环在连接未关闭的情况下会一直等待。结合到keepAlive属性,这里肯定默认会使用keepAlive进行请求,而后服务器也肯定没有关闭连接。因此,我们又在使用httpClient时,强制加入以下头:

       1

       httpPost.addHeader("Connection","Close");

       è¿™æ ·å£°æ˜Žå®¢æˆ·ç«¯åªä¼šè¯·æ±‚一次,就断开连接。加入之后,好像问题再一次解决。

       ä½†è¿™ç§è§£å†³æ–¹å¼æ€Žä¹ˆæ„Ÿè§‰ä¹Ÿä¸æ˜¯æ­£è§„的解决方法,因为只是碰巧处理了这个问题。最终的解决思路,还是在认真了解了Http协议之后,并查看相应的服务代码之后,删除错误的输出头,直接就解决了此问题。至于增加content-length头,这个在删除header之后,可以直接删除了(因为增加了额外的代码)。