Может ли PHP cURL получить заголовки ответа и тело в одном запросе? - PullRequest
292 голосов
/ 08 февраля 2012

Есть ли способ получить заголовки и тело для запроса cURL, используя PHP?Я обнаружил, что эта опция:

curl_setopt($ch, CURLOPT_HEADER, true);

собирается вернуть тело плюс заголовки , но затем мне нужно проанализировать его, чтобы получить тело.Есть ли способ получить как более удобный (и безопасный) способ?

Обратите внимание, что для "одного запроса" я имею в виду избегать выдачи запроса HEAD до GET / POST.

Ответы [ 13 ]

437 голосов
/ 08 февраля 2012

Одно из решений было опубликовано в комментариях к документации PHP: http://www.php.net/manual/en/function.curl-exec.php#80442

Пример кода:

$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
// ...

$response = curl_exec($ch);

// Then, after your curl_exec call:
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);

Предупреждение: Как отмечено в комментариях ниже, это может быть ненадежно при использовании с прокси-серверами или при обработке определенных типов перенаправлений. Ответ @ Джеффри может обработать их более надежно.

142 голосов
/ 14 декабря 2016

Многие другие решения, предлагаемые этим потоком, не делают это правильно.

  • Разделение на \r\n\r\n не надежно, когда CURLOPT_FOLLOWLOCATION включен или когда серверотвечает кодом 100.
  • Не все серверы соответствуют стандартам и передают только \n для новых линий.
  • Определение размера заголовков с помощью CURLINFO_HEADER_SIZE также не всегда надежноособенно при использовании прокси-серверов или в некоторых из тех же сценариев перенаправления.

Наиболее правильный метод - использование CURLOPT_HEADERFUNCTION.

Вот очень чистый метод выполнения этого с использованием замыканий на PHP.Он также преобразует все заголовки в нижний регистр для согласованной обработки между серверами и версиями HTTP.

В этой версии будут сохранены дублированные заголовки

Это соответствует RFC822 и RFC2616,пожалуйста, не предлагайте правки для использования строковых функций mb_, это неверно!

$ch = curl_init();
$headers = [];
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// this function is called by curl for each header received
curl_setopt($ch, CURLOPT_HEADERFUNCTION,
  function($curl, $header) use (&$headers)
  {
    $len = strlen($header);
    $header = explode(':', $header, 2);
    if (count($header) < 2) // ignore invalid headers
      return $len;

    $name = strtolower(trim($header[0]));
    if (!array_key_exists($name, $headers))
      $headers[$name] = [trim($header[1])];
    else
      $headers[$name][] = trim($header[1]);

    return $len;
  }
);

$data = curl_exec($ch);
print_r($headers);
113 голосов
/ 04 августа 2014

Curl имеет встроенную опцию для этого, которая называется CURLOPT_HEADERFUNCTION.Значением этой опции должно быть имя функции обратного вызова.Curl будет передавать заголовок (и только заголовок!) Этой функции обратного вызова, строка за строкой (поэтому функция будет вызываться для каждой строки заголовка, начиная с верхней части раздела заголовка).Ваша функция обратного вызова затем может делать с ней все что угодно (и должна возвращать количество байтов в данной строке).Вот проверенный рабочий код:

function HandleHeaderLine( $curl, $header_line ) {
    echo "<br>YEAH: ".$header_line; // or do whatever
    return strlen($header_line);
}


$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://www.google.com");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADERFUNCTION, "HandleHeaderLine");
$body = curl_exec($ch); 

Вышеописанное работает со всеми, с разными протоколами и прокси-серверами, и вам не нужно беспокоиться о размере заголовка или устанавливать множество различных параметров curl.

PS: Чтобы обработать строки заголовка методом объекта, сделайте следующее:

curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$object, 'methodName'))
40 голосов
/ 08 февраля 2012

это то, что вы ищете?

curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch); 
list($header, $body) = explode("\r\n\r\n", $response, 2);
10 голосов
/ 20 июня 2014

Просто установите параметры:

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

и используйте curl_getinfo с CURLINFO_HTTP_CODE (или без параметра opt, и у вас будет ассоциативный массив со всей необходимой информацией)

Больше на: http://php.net/manual/fr/function.curl-getinfo.php

7 голосов
/ 25 марта 2014

Если вы конкретно хотите Content-Type, есть специальная опция cURL для его получения:

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
2 голосов
/ 31 июля 2013
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);

$parts = explode("\r\n\r\nHTTP/", $response);
$parts = (count($parts) > 1 ? 'HTTP/' : '').array_pop($parts);
list($headers, $body) = explode("\r\n\r\n", $parts, 2);

Работает с HTTP/1.1 100 Continue перед другими заголовками.

Если вам нужна работа с ошибочными серверами, которые отправляют только LF вместо CRLF в качестве разрывов строк, вы можете использовать preg_split следующим образом:

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);

$parts = preg_split("@\r?\n\r?\nHTTP/@u", $response);
$parts = (count($parts) > 1 ? 'HTTP/' : '').array_pop($parts);
list($headers, $body) = preg_split("@\r?\n\r?\n@u", $parts, 2);
1 голос
/ 28 февраля 2019

Вот мой вклад в дебаты ... Это возвращает один массив с разделенными данными и заголовками в списке.Это работает на основе того, что CURL будет возвращать данные блока заголовков [пустая строка]

curl_setopt($ch, CURLOPT_HEADER, 1); // we need this to get headers back
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, true);

// $output contains the output string
$output = curl_exec($ch);

$lines = explode("\n",$output);

$out = array();
$headers = true;

foreach ($lines as $l){
    $l = trim($l);

    if ($headers && !empty($l)){
        if (strpos($l,'HTTP') !== false){
            $p = explode(' ',$l);
            $out['Headers']['Status'] = trim($p[1]);
        } else {
            $p = explode(':',$l);
            $out['Headers'][$p[0]] = trim($p[1]);
        }
    } elseif (!empty($l)) {
        $out['Data'] = $l;
    }

    if (empty($l)){
        $headers = false;
    }
}
1 голос
/ 29 мая 2014

Мой путь

$response = curl_exec($ch);
$x = explode("\r\n\r\n", $v, 3);
$header=http_parse_headers($x[0]);
if ($header=['Response Code']==100){ //use the other "header"
    $header=http_parse_headers($x[1]);
    $body=$x[2];
}else{
    $body=$x[1];
}

При необходимости примените цикл for и снимите предел разнесения.

0 голосов
/ 04 августа 2017

Будьте осторожны, когда вам нужны последние данные, которые вернулись с сервера.Этот код может нарушить ваши ожидания при ожидании реальных (последних) заголовков и тела: list($headers, $body) = explode("\r\n\r\n", $result, 2);

Вот простой способ получить окончательные заголовки и части тела;

$result = explode("\r\n\r\n", $result);

// drop redirect etc. headers
while (count($result) > 2) {
    array_shift($result);
}

// split headers / body parts
@ list($headers, $body) = $result;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...