Уже 100500 раз использовал функцию gzinflate() для разжатия страницы полученной от сервера. Всё по стандартной схеме - разжимаем контент, начиная с 11 символа после двойного переноса PHP: $b=substr($p,strpos($p,"\r\n\r\n")+14); $b=gzinflate($b); Всё было неплохо, пока функция вдруг не стала выдавать ошибку "data error". Что особенно странно - это происходит не сразу, а через несколько запросов, т.е. сначала нормально разжимает, а потом вдруг начинает ошибки выдавать. Проверял переменные - в $p содержится свежая страница. Вот что это может быть? Пока не могу понять
Я когда-то давно отыскал вот такую функцию: PHP: function gzBody($gzData) { if(substr($gzData,0,3)=="\x1f\x8b\x08") { $i=10; $flg=ord(substr($gzData,3,1)); if($flg>0) { if($flg&4) { list($xlen)=unpack('v',substr($gzData,$i,2)); $i=$i+2+$xlen; } if($flg&8) $i=strpos($gzData,"\0",$i)+1; if($flg&16) $i=strpos($gzData,"\0",$i)+1; if($flg&2) $i=$i+2; } return gzinflate(substr($gzData,$i,-8)); } else return false; } Она, по всей видимости, больше соответствует стандартам. Хотя по-хорошему, конечно, прочитать стандарт и сделать полностью так, как там написано.
Большое спасибо. А можно ссылку получить на русский вариант, или какую-нибудь статью более менее понятную. http://www.faqs.org/rfcs/rfc1952.html это же он?
Надо читать не про gzip, а про то, как упаковываются данные в HTTP. Вот здесь можно почитать варианты (на английском правда) Еще вроде бы есть методы распаковки в Zend, то это только для Content-Encoding: gzip, как я понял.
GRRRL Power, тебе огромное спасибо. Честно говоря, ни функция (я её потом нашёл на просторах, даже несколько вариантов), ни ссылки особо не помогли. Функция упорно не хотела работать, сколько с ней не бился. Но зато ты направил меня немного. Оказывается, страницы, на которых gzinflate() выдавала ошибку приходили с заголовком PHP: Transfer-Encoding: chunked . А это означает, что данные будут приходить по частям. Каждой части предшествует маркер, содержащий длину "чунка" в шестнадцатеричном представлении, и перенос строки (\r\n). Таким образом, прежде чем разжимать страницу строчкой PHP: //---в $page уже содержится страница без заголовков и двойного переноса $page=gzinflate(substr($page,10)); необходимо отсечь маркер и перенос строки PHP: $page=substr($page,strpos($page,"\r\n")+2); $page=gzinflate(substr($page,10)); При работе с более менее серьёзными сайтами это должно работать, так как они придерживаются наиболее распространённых стандартов. Проблемы могут возникнуть с менее "продвинутыми", или специфичными сайтами. Вот там уже может понадобится более серьёзная функция для обработки разжимаемых данных. Ещё могут возникнуть проблемы из-за плохого интернет-соединения - даже без Transfer-Encoding: chunked ответ может приходить частями и это нужно учитывать при работе через сокеты. В этом случае при каждом дополучении перед разжатием нужно собирать воедино уже полученный неразжатый контент и свежий. Для контроля целостности можно использовать заголовок Content-Length, но не все серверы и не всегда его шлют. Теоретически, даже с Transfer-Encoding: chunked сами чунки могут приходить не полностью, поэтому может понадобится сбор каждого маркера и проверка длины пришедшего чунка. Но это уже слишком плохое соединение и медленный интернет. В таких условиях лучше не работать.
На самом деле чанки могут приходить не целиком даже с не самым медленным интернетом (чанк может быть довольно большим). Проверять длину чанков мне показалось слишком затратно, да и целостность страницы для меня не является необходимым пунктом - главное, чтобы в уже полученном ответе содержались нужные данные. Поэтому решил выложить сюда унифицированную функцию, которая вычитывает и обрабатывает страницу в зависимости от заголовков и других нюансов, а также пояснения к ней, так как мне не удалось разобраться с функциями, найденными в интернете, без мануала к ним. Авось кому понадобится Способ вычитки предполагает, что из-за интернет соединения ответ может прийти по частям и при этом после каждой части не будет feof. Поэтому после пяти подряд пустых вычиток вылетаем из цикла вычитки и после функции page_combine() можно выполнить какие-то другие действия, или определить стоит ли ещё ждать ответа, или полученных данных уже достаточно и можно двигаться дальше Использование этой функции налагает некоторые обязательства - необходимо следить чтобы каждый раз перед обработкой нового ответа очищались переменные $page и $body, а также обнулялся счётчик дополучений страницы перед обработкой нового ответа и, соответственно, увеличивался, когда страница получена не полностью и нужно довычитать. И перед началом новой сессии очищался массив $session. PHP: function page_combine($s,$c){ //------$s - дескриптор на сокет, из которого нужно вычитывать //------$c - счётчик количества попыток дополучить страницу global $page;//-------переменная с разжатой страницей global $body;//-------переменная с неразжатым телом ответа global $session;//-------куки //------вычитываем ответ for($i=0,$p="";!feof($s);$i++){ $a=fgets($s[$k],2048); $p.=$a; if(!empty($a))$i=0; else{ if($i==4):break;endif; } } //------$h - заголовки ответа, $b - тело ответа //------разделяем ответ на заголовки и тело if($c==0){ $h=substr($p,0,strpos($p,"\r\n\r\n")); $b=substr($p,strpos($p,"\r\n\r\n")+4); } else{ $h=substr($page,0,strpos($page,"\r\n\r\n")); $b=$p; } //-------вот здесь самый смак - разжатие страницы в зависимости от условий if(strpos($h,"Content-Encoding: gzip")!==false){ if(strpos($h,"Transfer-Encoding: chunked")!==false){ if($c==0)$b=substr($b,strpos($b,"\r\n")+12); else{ $a=ord(substr($b,0,1)); //------символы маркера длины чанка имеют определённый диапазон ASCII кодов //------поэтому определяем с чего начинается свежедовычитанный ответ //------если с маркера, то отсекаем его, а если нет, то ничего не трогаем if($a>96&&$a<123||$a>64&&$a<91||$a>47&&$a<58):$b=substr($b,strpos($b,"\r\n")+2);endif; } } else{ if($с==0):$b=substr($b,10);endif; } $body.=$b;//-----соединяем воедино неразжатое тело ответа, это важно if(strlen($b)>0):$b=gzinflate($body);endif; }else{} //-----далее дополнительная примочка - перевод в нужную кодировку. Мои исполняемые фалы сохранены в utf-8 if(strpos($h,"charset=windows-1251")!==false):$b=iconv("cp1251","utf-8",$b);endif; //-----собираем страницу if($с==0)$page=$h."\r\n\r\n".$b; else $page.=$b; //-----теперь в глобальной переменной $page содержится целый, разжатый и перекодированный ответ, //-----с которым уже можно работать //-----и как бонус - обработка кукисов. //-----чтобы отправить куки, в запрос нужно засунуть строчку $request.="Cookie: ".implode("; ",$session)."\r\n"; //-----куки будут собираться только при первом получении страницы, //-----а также можно работать с каждой отдельной кукой, //-----каждая из которых является элементом ассоциативного массива $session с ключом равным названию куки //-----и значением в формате<имя куки=значение куки> if($c==0){ $arr=explode("\r\n",$h); foreach($arr as $v){ $b1=strpos($v,"Set-Cookie"); if($b1!==false){ $v=explode(";",substr($v,12)); $v=explode("=",$v[0]); $session[$v[0]]=$v[0]."=".$v[1]; if($v[1]=="deleted"||$v[1]=="DELETED")unset($session[$v[0]]);//--если сервер просит удалить куку }else{} } }else{} } После вызова этой функции у вас будет разжатая страница и все куки
Про чанки мне известно, но не думал, что здесь дело в этом. Чанки приходят, только если запрашиваешь страницу с HTTP/1.1. Если использовать HTTP/1.0, то она не бьется.
Так я тоже не думал, что дело в чанках. Насчёт запрашивания в HTTP/1.0 не уверен. Не буду точно утверждать, но вроде бы видел как на запрос в 1.0 сервер всё равно отвечал в 1.1. Но по правилам, конечно, сервер должен отвечать в 1.0
Извиняюсь что влезаю, но вот описание на русском, если кому надо (и не знает инглиш). http://rfc2.ru/2068.rfc