Phụ thuộc vào phản hồi, nếu số transfer-encoding
của phản hồi là chunked
, thì bạn đọc cho đến khi bạn gặp "đoạn cuối cùng" (\r\n0\r\n
).
Nếu content-encoding
là gzip
, thì bạn hãy xem tiêu đề phản hồi content-length
và đọc nhiều dữ liệu đó rồi thổi phồng nó. Nếu transfer-encoding
cũng được đặt thành chunked, sau đó bạn phải dechunk các phản ứng giải mã.
Điều dễ nhất là xây dựng một máy trạng thái đơn giản để đọc phản hồi từ ổ cắm trong khi vẫn còn dữ liệu cho phản hồi.
Khi đọc dữ liệu chunked, bạn nên đọc chiều dài đoạn đầu tiên (và bất kỳ phần mở rộng chunked) và sau đó đọc dữ liệu nhiều như kích thước đoạn, và làm như vậy cho đến đoạn cuối cùng.
Nói cách khác:
- đọc các tiêu đề HTTP response (đọc khối dữ liệu nhỏ cho đến khi bạn gặp
\r\n\r\n
)
- Phân tích các tiêu đề phản ứng vào một mảng
- Nếu
transfer-encoding
được chunked, đọc và dechunk mảnh dữ liệu từng mảnh.
- Nếu header
content-length
được thiết lập, bạn có thể đọc dữ liệu mà nhiều từ các ổ cắm
- Nếu
content-encoding
là gzip, giải nén các dữ liệu đọc
Một khi bạn đã thực hiện các bước trên, bạn nên đã đọc toàn bộ phản hồi và bây giờ bạn có thể gửi một yêu cầu HTTP khác trên cùng một socket và lặp lại quy trình.
Mặt khác, trừ khi bạn có nhu cầu tuyệt đối về kết nối duy trì sự sống, chỉ cần đặt Connection: close
trong yêu cầu và bạn có thể đọc an toàn while (!feof($f))
.
Tôi không có bất kỳ mã PHP nào để đọc và phân tích cú pháp phản hồi HTTP tại thời điểm này (tôi chỉ sử dụng cURL) nhưng nếu bạn muốn xem mã thực tế, hãy cho tôi biết và tôi có thể làm việc gì đó. Tôi cũng có thể giới thiệu cho bạn một số mã C# mà tôi đã thực hiện để thực hiện tất cả những điều trên.
EDIT: Đây là mã làm việc sử dụng fsockopen
để phát hành yêu cầu HTTP và chứng minh việc đọc kết nối liên tục với khả năng nén mã hóa và nén gzip. Thử nghiệm, nhưng không bị tra tấn - sử dụng có nguy cơ của riêng bạn !!!
<?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;
}
Là một sang một bên, bạn nhận ra bạn có lỗi báo giá, phải không? – Daedalus
@Daedalus Tôi có thể xác minh rằng lỗi báo giá không phải là vấn đề. Tôi đặt tiền thưởng bởi vì tôi có một vấn đề tương tự và đã lãng phí quá nhiều thời gian cố gắng sửa chữa nó :) – rdlowrey
Tốt bắt, @Daedalus, tôi đã không bắt được điều đó. Tôi với rdlowrey mặc dù, loại lỗi sẽ có được khác nhau. –