关于PHP 如何用 curl 读取 HTTP chunked 数据
对于Web服务器返回的HTTPchunked数据,我们可能希望在每一个chunk返回时得到回调,而不是所有的响应返回后再回调.例如,当服务器是icomet的时候.
在PHP中使用curl代码如下:
<?php $url="http://127.0.0.1:8100/stream"; $ch=curl_init($url); curl_setopt($ch,CURLOPT_WRITEFUNCTION,'myfunc'); $result=curl_exec($ch); curl_close($ch); functionmyfunc($ch,$data){ $bytes=strlen($data); //处理data return$bytes; }
但是,这里有一个问题.对于一个chunk,回调函数可能会被调用多次,每一次大概是16k的数据.这显然不是我们希望得到的.因为icomet的一个chunk是以"\n"结尾,所以回调函数可以做一下缓冲.
functionmyfunc($ch,$data){ $bytes=strlen($data); static$buf=''; $buf.=$data; while(1){ $pos=strpos($buf,"\n"); if($pos===false){ break; } $data=substr($buf,0,$pos+1); $buf=substr($buf,$pos+1); //处理data } }
下面给大家介绍下chunkedphp使用fsockopen读取分段数据(transfer-encoding:chunked)
使用fsockopen读取数据时遇到了一个神奇的问题,具体情况如下:
读取地址:http://blog.maxthon.cn/?feed=rss2
读取代码:
<?php $fp=fsockopen("blog.maxthon.cn",80,$errno,$errstr,30); if(!$fp){ echo"$errstr($errno)<br/>\n"; }else{ $out="GET/?feed=rss2HTTP/1.1\r\n"; $out.="Host:blog.maxthon.cn\r\n"; $out.="Connection:Close\r\n\r\n"; fwrite($fp,$out); while(!feof($fp)){ echofgets($fp,128); } fclose($fp); } ?>
返回http内容:
Date:Mon,29Mar201010:16:13GMT Server:Apache/2.2.8(Unix)mod_ssl/2.2.8OpenSSL/0.9.8bPHP/5.2.6 X-Powered-By:PHP/5.2.6 X-Pingback:http://blog.maxthon.cn/xmlrpc.php Last-Modified:Wed,03Mar201003:13:41GMT ETag:"8f16b619f32188bde3bc008a60c2cc11" Keep-Alive:timeout=15,max=120 Connection:Keep-Alive Transfer-Encoding:chunked Content-Type:text/xml;charset=UTF-8 22de <?xmlversion="1.0"encoding="UTF-8"?> <rssversion="2.0" <description><![CDATA[2009年12月31日 1711 ....... 1fe8 ]]></description> <content:encoded><![CDATA[<p>2009年12月31日<br/> 1711</p>
请注意上面那些标红的4个字符,它们每隔一段数据就会出现一次,但是用其他的方法如curl,file_get_contents等取回的数据则没有这些玩意。换成其他的网站来抓取,也只是少数的网站会出现这种情况,多方搜索无解后,我无意中看到了上面返回头中有这么一个声明:Transfer-Encoding:chunked,而常见的Content-lenght字段没有了。这个声明的大致的意思是传输编码为分段方式。
在Google上搜索该关键词,在维基百科上找到对这个声明的解释(由于没有中文版,我只能自己按照意思翻译):
ChunkedTransferEncodingisamechanismthatallowsHTTPmessagestobesplitinseveralparts.ThiscanbeappliedtobothHTTPrequests(fromclienttoserver)andHTTPresponses(fromservertoclient)
分块传输编码是一种机制,允许将HTTP消息分成几个部分传输。同时适用于HTTP请求(从客户端到服务器)和HTTP响应(从服务器到客户端)
Forexample,letusconsiderthewayinwhichanHTTPservermaytransmitdatatoaclientapplication(usuallyawebbrowser).Normally,datadeliveredinHTTPresponsesissentinonepiece,whoselengthisindicatedbytheContent-Lengthheaderfield.Thelengthofthedataisimportant,becausetheclientneedstoknowwheretheresponseendsandanyfollowingresponsestarts.Withchunkedencoding,however,thedataisbrokenupintoaseriesofblocksofdataandtransmittedinoneormore"chunks"sothataservermaystartsendingdatabeforeitknowsthefinalsizeofthecontentthatit'ssending.Often,thesizeoftheseblocksisthesame,butthisisnotalwaysthecase.
例如,让我们考虑HTTP服务器可将数据传输到客户端应用程序(通常是一个网络浏览器)使用哪些方式。通常情况下,在HTTP响应数据是按照一整块发送给客户端的,数据的长度是由Content-Length头域表示。数据的长度很重要,因为客户需要知道在哪里响应结束和后面的响应何时启动。而使用Chunked编码方式,不管怎样,数据都会分割成一系列的数据块和一个或多个转发的“块”,因此服务器在知道内容的长度之前,就可以开始发送数据后。通常情况下,这些数据块的大小是一样的,但也并不是绝对的。
大概意思了解后,我们来看例子:
Chunked编码使用若干个Chunk串连而成,由一个标明长度为0的chunk标示结束。每个Chunk分为头部和正文两部分,头部内容指定下一段正文的字符总数(十六进制的数字)和数量单位(一般不写),正文部分就是指定长度的实际内容,两部分之间用回车换行(CRLF)隔开。在最后一个长度为0的Chunk中的内容是称为footer的内容,是一些附加的Header信息(通常可以直接忽略)。具体的Chunk编码格式如下:
编过码的响应内容:
HTTP/1.1200OK
Content-Type:text/plain
Transfer-Encoding:chunked
25
这是第一段数据
1A
然后这是第二段数据
0
解码的数据:
这是第一段内容,然后这是第二段数据
情况搞清楚了,那么我们怎么来解码这个编码后的数据呢?
在php官方手册fsockopen函数下面的评论中,已经有很多人提出了解决方法
方法1.
<?php functionunchunk($result){ returnpreg_replace_callback( '/(?:(?:\r\n|\n)|^)([0-9A-F]+)(?:\r\n|\n){1,2}(.*?)'. '((?:\r\n|\n)(?:[0-9A-F]+(?:\r\n|\n))|$)/si', create_function( '$matches', 'returnhexdec($matches[1])==strlen($matches[2])?$matches[2]:$matches[0];' ), $result ); }
方法二.
functionunchunkHttp11($data){ $fp=0; $outData=""; while($fp<strlen($data)){ $rawnum=substr($data,$fp,strpos(substr($data,$fp),"\r\n")+2); $num=hexdec(trim($rawnum)); $fp+=strlen($rawnum); $chunk=substr($data,$fp,$num); $outData.=$chunk; $fp+=strlen($chunk); } return$outData; }
注意:这两个函数的参数都是返回的http原始数据(包括头)