Может ли PHP cURL получить заголовки ответа и тело в одном запросе?

223

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

curl_setopt($ch, CURLOPT_HEADER, true);

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

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

  • 3
    Для этого есть встроенное решение, см. Ответ: stackoverflow.com/a/25118032/1334485 (добавил этот комментарий, потому что этот пост по-прежнему получает много просмотров)
  • 0
    Посмотрите на этот хороший комментарий: secure.php.net/manual/en/book.curl.php#117138
Показать ещё 2 комментария
Теги:
curl
http

12 ответов

384
Лучший ответ

Одно из решений этого было опубликовано в комментариях документации 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);

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

  • 0
    Это надежно? Я имею в виду использование размера заголовка. Благодарю.
  • 18
    Вы также можете list($header, $body) = explode("\r\n\r\n", $response, 2) , но это может занять немного больше времени, в зависимости от размера вашего запроса.
Показать ещё 10 комментариев
87

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); 

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

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

curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$object, 'methodName'))
  • 0
    Как примечание, функция обратного вызова вызывается для каждого заголовка, и кажется, что они не обрезаются. Вы можете использовать глобальную переменную для хранения всех заголовков или анонимную функцию для обратного вызова и использовать локальную переменную (локальную для родительской области, а не анонимную функцию).
  • 2
    @MV Спасибо, да, под «построчно» я имел в виду «каждый заголовок». Я отредактировал свой ответ для ясности. Чтобы получить весь раздел заголовка (он же все заголовки), вы также можете использовать метод объекта для обратного вызова, чтобы свойство объекта могло содержать их все.
Показать ещё 3 комментария
44

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

  • Разделение на \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);
  • 6
    IMO, это лучший ответ в этой теме и исправляет проблемы с перенаправлениями, которые возникали с другими ответами. Лучше всего прочитать документацию для CURLOPT_HEADERFUNCTION, чтобы понять, как она работает и потенциальные ошибки. Я также внес некоторые улучшения в ответ, чтобы помочь другим.
  • 0
    Отлично, я обновил ответ, чтобы обслужить дублированные заголовки. В будущем не переформатируйте код так, как вам кажется. Это написано так, чтобы было ясно, где находятся границы функции замыкания.
Показать ещё 7 комментариев
38

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

curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch); 
list($header, $body) = explode("\r\n\r\n", $response, 2);
  • 8
    Это работает нормально, за исключением случаев, когда есть HTTP / 1.1 100 Continue с последующим перерывом, а затем HTTP / 1.1 200 OK. Я бы пошел с другим методом.
  • 1
    Посмотрите на выбранный ответ stackoverflow.com/questions/14459704/…, прежде чем реализовывать что-то подобное. w3.org/Protocols/rfc2616/rfc2616-sec14.html (14.20) A server that does not understand or is unable to comply with any of the expectation values in the Expect field of a request MUST respond with appropriate error status. The server MUST respond with a 417 (Expectation Failed) status if any of the expectations cannot be met or, if there are other problems with the request, some other 4xx status.
Показать ещё 2 комментария
7

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

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

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

Подробнее: http://php.net/manual/fr/function.curl-getinfo.php

  • 1
    Самый идеальный подход
  • 3
    Похоже, это не возвращает вам заголовки ответа. Или, по крайней мере, нет способа получить их с помощью curl_getinfo() .
7

Если вы специально хотите 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
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);
  • 0
    Не должен $parts = explode("\r\n\r\nHTTP/", $response); есть 3-й параметр для взрыва как 2?
  • 0
    @ user4271704 Нет. Позволяет найти последнее HTTP-сообщение. HTTP/1.1 100 Continue может появляться много раз.
Показать ещё 4 комментария
0

Проблема с множеством ответов здесь заключается в том, что "\r\n\r\n" может законно отображаться в теле html, поэтому вы не можете быть уверены, что правильно разделяете заголовки.

Кажется, что единственный способ сохранить заголовки отдельно с одним вызовом curl_exec - использовать обратный вызов, как предлагается выше в https://stackoverflow.com/questions/9183178/can-php-curl-retrieve-response-headers-and-body-in-a-single-request

И затем, чтобы (надежно) получить только тело запроса, вам нужно передать значение заголовка Content-Length в substr() как отрицательное начальное значение.

  • 1
    Это может показаться законным, но ваш ответ неверен. Content-Length не обязательно должен присутствовать в ответе HTTP. Правильный метод ручного разбора заголовков - поиск первого экземпляра \ r \ n (или \ n \ n). Это можно сделать, просто ограничив explode, чтобы он возвращал только два элемента, а именно: list($head, $body) = explode("\r\n\r\n", $response, 2); однако CURL уже делает это за вас, если вы используете curl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
0

Мой способ

$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
    требует PECL, но спасибо за совет :)
-1

Будьте осторожны, когда вам понадобятся последние материалы, возвращаемые с сервера. Этот код может прервать ваше ожидание, ожидая реальных (последних) заголовков и тела: 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;
-3

Вернуть ответные заголовки с опорным параметром:

<?php
$data=array('device_token'=>'5641c5b10751c49c07ceb4',
            'content'=>'测试测试test'
           );
$rtn=curl_to_host('POST', 'http://test.com/send_by_device_token', array(), $data, $resp_headers);
echo $rtn;
var_export($resp_headers);

function curl_to_host($method, $url, $headers, $data, &$resp_headers)
         {$ch=curl_init($url);
          curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $GLOBALS['POST_TO_HOST.LINE_TIMEOUT']?$GLOBALS['POST_TO_HOST.LINE_TIMEOUT']:5);
          curl_setopt($ch, CURLOPT_TIMEOUT, $GLOBALS['POST_TO_HOST.TOTAL_TIMEOUT']?$GLOBALS['POST_TO_HOST.TOTAL_TIMEOUT']:20);
          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
          curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
          curl_setopt($ch, CURLOPT_HEADER, 1);

          if ($method=='POST')
             {curl_setopt($ch, CURLOPT_POST, true);
              curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
             }
          foreach ($headers as $k=>$v)
                  {$headers[$k]=str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $k)))).': '.$v;
                  }
          curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
          $rtn=curl_exec($ch);
          curl_close($ch);

          $rtn=explode("\r\n\r\nHTTP/", $rtn, 2);    //to deal with "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK...\r\n\r\n..." header
          $rtn=(count($rtn)>1 ? 'HTTP/' : '').array_pop($rtn);
          list($str_resp_headers, $rtn)=explode("\r\n\r\n", $rtn, 2);

          $str_resp_headers=explode("\r\n", $str_resp_headers);
          array_shift($str_resp_headers);    //get rid of "HTTP/1.1 200 OK"
          $resp_headers=array();
          foreach ($str_resp_headers as $k=>$v)
                  {$v=explode(': ', $v, 2);
                   $resp_headers[$v[0]]=$v[1];
                  }

          return $rtn;
         }
?>
  • 0
    Вы уверены, что $rtn=explode("\r\n\r\nHTTP/", $rtn, 2); верно? Не следует ли удалить 3-й параметр разнесения?
  • 0
    @ user4271704, третий параметр должен иметь дело с заголовком «HTTP / 1.1 100 Continue \ r \ n \ r \ nHTTP / 1.1 200 OK ... \ r \ n \ r \ n ...»
Показать ещё 1 комментарий
-6

Если вам действительно не нужно использовать curl;

$body = file_get_contents('http://example.com');
var_export($http_response_header);
var_export($body);

Какие выходы

array (
  0 => 'HTTP/1.0 200 OK',
  1 => 'Accept-Ranges: bytes',
  2 => 'Cache-Control: max-age=604800',
  3 => 'Content-Type: text/html',
  4 => 'Date: Tue, 24 Feb 2015 20:37:13 GMT',
  5 => 'Etag: "359670651"',
  6 => 'Expires: Tue, 03 Mar 2015 20:37:13 GMT',
  7 => 'Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT',
  8 => 'Server: ECS (cpm/F9D5)',
  9 => 'X-Cache: HIT',
  10 => 'x-ec-custom-error: 1',
  11 => 'Content-Length: 1270',
  12 => 'Connection: close',
)'<!doctype html>
<html>
<head>
    <title>Example Domain</title>...

См. http://php.net/manual/en/reserved.variables.httpresponseheader.php

  • 15
    хм, вам на самом деле тоже не нужен PHP, но именно об этом и идет вопрос ...

Ещё вопросы

Сообщество Overcoder
Наверх
Меню