Ответ 1
Это зависит от ответа, если transfer-encoding
ответа chunked
, тогда вы читаете, пока не встретите "последний кусок" (\r\n0\r\n
).
Если content-encoding
- gzip
, вы можете посмотреть заголовок ответа content-length
и прочитать много данных, а затем раздуть его. Если параметр transfer-encoding
также установлен в chunked, вы должны дешифровать декодированный ответ.
Самое простое - создать простую машину состояний для чтения ответа из сокета, пока все еще остаются данные для ответа.
При чтении фрагментированных данных вы должны прочитать первую длину блока (и любое выделенное расширение), а затем прочитать столько данных, сколько размер блока, и сделать это до последнего фрагмента.
Поставьте другой способ:
- Прочитайте заголовки ответа HTTP (прочитайте небольшие фрагменты данных, пока не встретите
\r\n\r\n
) - Разберите заголовки ответов в массив
- Если
transfer-encoding
помечен, прочитайте и отбросите данные по частям. - Если заголовок
content-length
установлен, вы можете прочитать, что много данных из сокета - Если
content-encoding
является gzip, распакуйте прочитанные данные
После выполнения вышеуказанных шагов вы должны прочитать весь ответ, и теперь вы можете отправить другой HTTP-запрос в тот же сокет и повторить процесс.
С другой стороны, если у вас нет абсолютной потребности в подключении keep-alive, просто установите Connection: close
в запросе, и вы можете спокойно читать while (!feof($f))
.
У меня нет PHP-кода для чтения и анализа HTTP-ответов на данный момент (я просто использую cURL), но если вы хотите увидеть фактический код, сообщите мне, и я могу что-то с этим справиться. Я мог бы также ссылаться на некоторый код С#, который я сделал, который делает все вышеперечисленное.
EDIT: Вот рабочий код, который использует fsockopen
для выдачи HTTP-запроса и демонстрирует чтение keep-alive соединений с возможностью кодирования и сжатия gzip. Протестировано, но не подвергается пыткам - используйте на свой страх и риск!!!
<?php
/**
* PHP HTTP request demo
* Makes HTTP requests using PHP and fsockopen
* Supports chunked transfer encoding, gzip compression, and keep-alive
*
* @author drew010 <http://stackoverflow.com/questions/11125463/if-connection-is-keep-alive-how-to-read-until-end-of-stream-php/11812536#11812536>
* @date 2012-08-05
* Public domain
*
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
$host = 'www.kernel.org';
$sock = fsockopen($host, 80, $errno, $errstr, 30);
if (!$sock) {
die("Connection failed. $errno: $errstr\n");
}
request($sock, $host, 'GET', '/');
$headers = readResponseHeaders($sock, $resp, $msg);
$body = readResponseBody($sock, $headers);
echo "Response status: $resp - $msg\n\n";
echo '<pre>' . var_export($headers, true) . '</pre>';
echo "\n\n";
echo $body;
// if the connection is keep-alive, you can make another request here
// as demonstrated below
request($sock, $host, 'GET', '/kernel.css');
$headers = readResponseHeaders($sock, $resp, $msg);
$body = readResponseBody($sock, $headers);
echo "Response status: $resp - $msg\n\n";
echo '<pre>' . var_export($headers, true) . '</pre>';
echo "\n\n";
echo $body;
exit;
function request($sock, $host, $method = 'GET', $uri = '/', $params = null)
{
$method = strtoupper($method);
if ($method != 'GET' && $method != 'POST') $method = 'GET';
$request = "$method $uri HTTP/1.1\r\n"
."Host: $host\r\n"
."Connection: keep-alive\r\n"
."Accept-encoding: gzip, deflate\r\n"
."\r\n";
fwrite($sock, $request);
}
function readResponseHeaders($sock, &$response_code, &$response_status)
{
$headers = '';
$read = 0;
while (true) {
$headers .= fread($sock, 1);
$read += 1;
if ($read >= 4 && $headers[$read - 1] == "\n" && substr($headers, -4) == "\r\n\r\n") {
break;
}
}
$headers = parseHeaders($headers, $resp, $msg);
$response_code = $resp;
$response_status = $msg;
return $headers;
}
function readResponseBody($sock, array $headers)
{
$responseIsChunked = (isset($headers['transfer-encoding']) && stripos($headers['transfer-encoding'], 'chunked') !== false);
$contentLength = (isset($headers['content-length'])) ? $headers['content-length'] : -1;
$isGzip = (isset($headers['content-encoding']) && $headers['content-encoding'] == 'gzip') ? true : false;
$close = (isset($headers['connection']) && stripos($headers['connection'], 'close') !== false) ? true : false;
$body = '';
if ($contentLength >= 0) {
$read = 0;
do {
$buf = fread($sock, $contentLength - $read);
$read += strlen($buf);
$body .= $buf;
} while ($read < $contentLength);
} else if ($responseIsChunked) {
$body = readChunked($sock);
} else if ($close) {
while (!feof($sock)) {
$body .= fgets($sock, 1024);
}
}
if ($isGzip) {
$body = gzinflate(substr($body, 10));
}
return $body;
}
function readChunked($sock)
{
$body = '';
while (true) {
$data = '';
do {
$data .= fread($sock, 1);
} while (strpos($data, "\r\n") === false);
if (strpos($data, ' ') !== false) {
list($chunksize, $chunkext) = explode(' ', $data, 2);
} else {
$chunksize = $data;
$chunkext = '';
}
$chunksize = (int)base_convert($chunksize, 16, 10);
if ($chunksize === 0) {
fread($sock, 2); // read trailing "\r\n"
return $body;
} else {
$data = '';
$datalen = 0;
while ($datalen < $chunksize + 2) {
$data .= fread($sock, $chunksize - $datalen + 2);
$datalen = strlen($data);
}
$body .= substr($data, 0, -2); // -2 to remove the "\r\n" before the next chunk
}
} // while (true)
}
function parseHeaders($headers, &$response_code = null, &$response_message = null)
{
$lines = explode("\r\n", $headers);
$return = array();
$response = array_shift($lines);
if (func_num_args() > 1) {
list($proto, $code, $message) = explode(' ', $response, 3);
$response_code = $code;
if (func_num_args() > 2) {
$response_message = $message;
}
}
foreach($lines as $header) {
if (trim($header) == '') continue;
list($name, $value) = explode(':', $header, 2);
$return[strtolower(trim($name))] = trim($value);
}
return $return;
}