Я хочу сделать запрос HTTP без зависимости от cURL и allow_url_fopen = 1
, открыв соединение сокета и отправить необработанный HTTP-запрос:
/** * Make HTTP GET request * * @param string the URL * @param int will be filled with HTTP response status code * @param string will be filled with HTTP response header * @return string HTTP response body */ function http_get_request($url, &$http_code = '', &$res_head = '') { $scheme = $host = $user = $pass = $query = $fragment = ''; $path = '/'; $port = substr($url, 0, 5) == 'https' ? 443 : 80; extract(parse_url($url)); $path .= ($query ? "?$query" : '').($fragment ? "#$fragment" : ''); $head = "GET $path HTTP/1.1\r\n" . "Host: $host\r\n" . "Authorization: Basic ".base64_encode("$user:$pass")."\r\n" . "Connection: close\r\n\r\n"; $fp = fsockopen($scheme == 'https' ? "ssl://$host" : $host, $port) or die('Cannot connect!'); fputs($fp, $head); while(!feof($fp)) { $res .= fgets($fp, 4096); } fclose($fp); list($res_head, $res_body) = explode("\r\n\r\n", $res, 2); list(, $http_code, ) = explode(' ', $res_head, 3); return $res_body; }
Функция работает нормально, но поскольку я использую HTTP / 1.1, тело ответа обычно возвращается в закодированной в Chunked строке. Например (из Википедии):
25 This is the data in the first chunk 1C and this is the second one 3 con 8 sequence 0
Я не хочу использовать http_chunked_decode()
поскольку он имеет зависимость PECL, и я хочу очень портативный код.
Как легко декодировать закодированную строку HTTP-chunked, чтобы моя функция могла вернуть исходный HTML? Я также должен убедиться, что длина декодированной строки соответствует заголовку Content-Length:
Любая помощь будет оценена по достоинству. Благодарю.
Поскольку функция возвращает заголовок ответа HTTP, вы должны проверить, является ли 'Transfer-Encoding'
'chunked'
затем декодировать строку с закодированным кодированием. В псевдокоде:
CALL parse_http_header IF 'Transfer-Encoding' IS 'chunked' CALL decode_chunked
Разбор заголовка HTTP-ответа:
Ниже приведена функция для разбора заголовка HTTP-ответа на ассоциативный массив.
function parse_http_header($str) { $lines = explode("\r\n", $str); $head = array(array_shift($lines)); foreach ($lines as $line) { list($key, $val) = explode(':', $line, 2); if ($key == 'Set-Cookie') { $head['Set-Cookie'][] = trim($val); } else { $head[$key] = trim($val); } } return $head; }
Функция вернет массив следующим образом:
Array ( [0] => HTTP/1.1 200 OK [Expires] => Tue, 31 Mar 1981 05:00:00 GMT [Content-Type] => text/html; charset=utf-8 [Transfer-Encoding] => chunked [Set-Cookie] => Array ( [0] => k=10.34; path=/; expires=Sat, 09-Jun-12 01:58:23 GMT; domain=.example.com [1] => guest_id=v1%3A13; domain=.example.com; path=/; expires=Mon, 02-Jun-2014 13:58:23 GMT ) [Content-Length] => 43560 )
Обратите внимание, как заголовки Set-Cookie
разбираются в массиве. Вам необходимо проанализировать файлы cookie позже, чтобы связать URL-адрес с куки-файлами, которые необходимо отправить.
Декодировать строку с закодированным кодированием
Нижеприведенная функция использует строку chunked-encoded в качестве аргумента и возвращает декодированную строку.
function decode_chunked($str) { for ($res = ''; !empty($str); $str = trim($str)) { $pos = strpos($str, "\r\n"); $len = hexdec(substr($str, 0, $pos)); $res.= substr($str, $pos + 2, $len); $str = substr($str, $pos + 2 + $len); } return $res; } // Given the string in the question, the function above will returns: // // This is the data in the first chunk // and this is the second one // consequence
Я не знаю, насколько оптимально для вас то, что вам нужно сделать, но если вы укажете HTTP/1.0
вместо HTTP/1.1
, вы не получите ответный ответ.
эта функция используется в WordPress.
function decode_chunked($data) { if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) { return $data; } $decoded = ''; $encoded = $data; while (true) { $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches); if (!$is_chunked) { // Looks like it's not chunked after all return $data; } $length = hexdec(trim($matches[1])); if ($length === 0) { // Ignore trailer headers return $decoded; } $chunk_length = strlen($matches[0]); $decoded .= substr($encoded, $chunk_length, $length); $encoded = substr($encoded, $chunk_length + $length + 2); if (trim($encoded) === '0' || empty($encoded)) { return $decoded; } } // We'll never actually get down here // @codeCoverageIgnoreStart }