Чи може PHP cURL отримати заголовки відповідей та тіло в одному запиті?


314

Чи є спосіб отримати як заголовки, так і тіло для запиту CURL за допомогою PHP? Я виявив, що такий варіант:

curl_setopt($ch, CURLOPT_HEADER, true);

поверне тіло плюс заголовки , але тоді мені потрібно проаналізувати його, щоб отримати тіло. Чи є спосіб отримати і більш зручним (і безпечним) способом?

Зауважте, що під "єдиним запитом" я маю на увазі уникання надсилання запиту HEAD перед GET / POST.


3
Для цього є вбудоване рішення, дивіться цю відповідь: stackoverflow.com/a/25118032/1334485 (додав цей коментар, оскільки цей пост все ще отримує багато переглядів)
Скакч,

Подивіться на цей приємний коментар: secure.php.net/manual/en/book.curl.php#117138
користувач956584


Мені сказали, що моє запитання було дублікатом цього питання. Якщо це не дублікат, може хтось, будь ласка, знову відкрити його? stackoverflow.com/questions/43770246/… У моєму питанні я маю конкретну вимогу використовувати метод, який повертає об’єкт із заголовками та тілом, а не одним рядком.
1,21 гігаватт

Відповіді:


466

Одне рішення для цього було розміщено в коментарях до документації 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);

Попередження: Як зазначено в коментарях нижче, це може бути не надійним при використанні з проксі-серверами або при обробці певних типів переадресацій. @ Відповідь Джеффрі може вирішити це надійніше.


22
Ви також можете list($header, $body) = explode("\r\n\r\n", $response, 2), але це може зайняти трохи більше часу, залежно від розміру вашого запиту.
iblue

43
це погане рішення, тому що якщо ви використовуєте проксі-сервер і ваш проксі-сервер (наприклад, скрипт), додайте до відповіді власні заголовки - цей заголовок порушив усі компенсації, і ви повинні використовувати list($header, $body) = explode("\r\n\r\n", $response, 2)лише робочий варіант
msangel

5
@msangel Ваше рішення не працює, коли у відповіді є кілька заголовків, наприклад, коли сервер переспрямовує 302. Будь-які пропозиції?
Нейт

4
@Nate, так, я знаю це. AFAIK, але існує лише один можливий додатковий заголовок - з кодом 100(Продовжити). Для цього заголовка можна обійтись із правильним визначенням параметра запиту: curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); відключення надсилання відповіді цього заголовка. Щодо 302цього не повинно відбуватися, тому що заголовок 302 перенаправляється, він не очікує, що тіло, проте я знаю, іноді сервери надсилають якесь тіло з 302відповіддю, але браузери це все одно будуть ігноруватися, поки що, чому curl має це впоратися? )
msangel

5
CURLOPT_VERBOSEпризначений для виведення інформації про процес до STDERR(може турбувати CLI), і обговорювана проблема є марною.
hejdav

205

Багато з інших рішень , пропонованих ця нитка НЕ роблять це правильно.

  • Розщеплення \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;

    $headers[strtolower(trim($header[0]))][] = trim($header[1]);

    return $len;
  }
);

$data = curl_exec($ch);
print_r($headers);

12
IMO - це найкраща відповідь у цій темі та виправляє проблеми з переадресаціями, які виникали з іншими відповідями. Найкраще прочитати документацію для CURLOPT_HEADERFUNCTION, щоб зрозуміти, як вона працює та потенційні дітки. Я також внесла деякі вдосконалення у відповідь, щоб допомогти іншим.
Саймон Іст

Чудово, я оновив відповідь на задоволення дублюваних заголовків. Надалі не переформатуйте код так, як ви вважаєте, він повинен бути. Це написано таким чином, щоб було зрозуміло, де знаходяться межі функції закриття.
Джеффрі

@Geoffrey Чи $headers = [];дійсний php?
thealexbaron

6
@thealexbaron Так, це як на PHP 5.4, див.: php.net/manual/en/migration54.new-features.php
Джеффрі

4
Ця відповідь сильно занижена для такого чіткого та сумісного з підходами підходу. На це слід зробити липку відповідь і перенести на верх. Я просто хочу, щоб був швидший підхід, щоб отримати значення потрібного заголовка, а не спершу розбирати всі заголовки.
Fr0zenFyr

114

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

Наведене вище працює з усім, різними протоколами та проксі, і вам не потрібно турбуватися про розмір заголовка або встановлювати безліч різних варіантів згортання.

PS: Щоб обробити рядки заголовків об'єктним методом, виконайте це:

curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$object, 'methodName'))

Як примітка, функція зворотного дзвінка викликається для кожного заголовка, і, здається, вони не оброблені. Ви можете використовувати глобальну змінну, щоб утримувати всі заголовки, або ви можете використовувати анонімну функцію зворотного виклику та використовувати локальну змінну (локальна для батьківської області, а не анонімна функція).
МВ.

2
@MV Спасибі, так, під "рядок за рядком" я мав на увазі "кожен заголовок". Я відредагував свою відповідь для наочності. Щоб отримати весь розділ заголовка (ака. Всі заголовки), ви також можете використовувати метод об'єкта для зворотного виклику, щоб властивість об'єкта вмістила їх усіх.
Скакч

8
Це найкраща відповідь ІМО. Це не спричиняє проблем із декількома "\ r \ n \ r \ n" при використанні CURLOPT_FOLLOWLOCATION, і я думаю, що додаткові заголовки проксі не вплинуть на нього.
Rafał G.

Дуже добре працював для мене, також дивіться stackoverflow.com/questions/6482068/… у разі проблем
RHH

1
Так, це найкращий підхід, однак відповідь Джеффрі робить це більш чистим, використовуючи анонімну функцію, не потребуючи глобальних змінних тощо.
Саймон Схід

39

це на що ти дивишся?

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 Продовжити з подальшим перервою, а потім HTTP / 1.1 200 ОК. Я б пішов з іншим методом.
привид

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.
Alrik


Цей метод також не працює при переадресації 302, коли curl встановлений для заголовка місця.
Simon Simon

10

Просто встановіть параметри:

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

і використовуйте curl_getinfo з CURLINFO_HTTP_CODE (або немає параметра param, і у вас буде асоціативний масив із усією інформацією, яку ви хочете)

Детальніше за посиланням: http://php.net/manual/fr/function.curl-getinfo.php


5
Схоже, це взагалі не повертає заголовків відповідей. Або принаймні немає способу отримати їх за допомогою curl_getinfo().
Simon Simon

8

Якщо ви конкретно хочете отримати 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);

Не повинен $parts = explode("\r\n\r\nHTTP/", $response);бути 3-й параметр для explode як 2?
user4271704

@ user4271704 Ні. Це дозволяє знайти останнє повідомлення HTTP. HTTP/1.1 100 Continueможуть з’являтися багато разів.
Енібі

Але він каже щось інше: stackoverflow.com/questions/9183178/… хто з вас прав?
user4271704

HTTP/1.1 100 Continueможуть з’являтися багато разів. Він розглядає випадок, якщо він з’являється лише один раз, але це неправильно у звичайній справі. Наприклад, HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK...\r\n\r\n...його код не працює належним чином
Enyby

1
Розщеплення на \ r \ n не є надійним, деякі сервери не відповідають специфікаціям HTTP і надсилають лише \ n. Стандарт RFC зазначає, що додатки повинні ігнорувати \ r і розділятись на \ n для найбільшої надійності.
Джеффрі

1

Мій шлях такий

$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 та зніміть межу вибуху.


1

Ось мій внесок у дебати ... Це повертає єдиний масив із розділеними даними та заголовками. Це працює на основі того, що 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;
    }
}

0

Проблема багатьох відповідей тут полягає в тому, що вони "\r\n\r\n"можуть законно відображатися в тілі html, тому ви не можете бути впевнені, що ви правильно розділяєте заголовки.

Схоже, єдиний спосіб збереження заголовків окремо одним викликом - curl_execце використання зворотного дзвінка, як це запропоновано вище в https://stackoverflow.com/a/25118032/3326494

А потім, щоб (надійно) отримати лише тіло запиту, вам слід передати значення Content-Lengthзаголовка substr()як негативне початкове значення.


1
Це може відображатися законно, але ваша відповідь неправильна. Довжина вмісту не повинна бути присутнім у відповіді HTTP. Правильний метод ручного розбору заголовків - це пошук першого екземпляра \ r \ n (або \ n \ n). Це можна зробити просто обмеживши вибух, щоб повернути лише два елементи, тобто:, list($head, $body) = explode("\r\n\r\n", $response, 2);проте CURL це робить для вас, якщо ви використовуєтеcurl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
Джеффрі

-1

Про всяк випадок, якщо ви не можете / не використовуєте CURLOPT_HEADERFUNCTIONчи інші рішення;

$nextCheck = function($body) {
    return ($body && strpos($body, 'HTTP/') === 0);
};

[$headers, $body] = explode("\r\n\r\n", $result, 2);
if ($nextCheck($body)) {
    do {
        [$headers, $body] = explode("\r\n\r\n", $body, 2);
    } while ($nextCheck($body));
}

-2

Повернення заголовків відповідей з еталонним параметром:

<?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;
         }
?>

Ви впевнені, що $rtn=explode("\r\n\r\nHTTP/", $rtn, 2);це правильно? Чи не слід видалити 3-й параметр вибуху?
user4271704

@ user4271704, 3-й парам - це мати справу з "HTTP / 1.1 100 Продовжити \ r \ n \ r \ nHTTP / 1.1 200 ОК ... \ r \ n \ r \ n ..." заголовок
дієїзм

Але він сказав щось інше: stackoverflow.com/questions/9183178/… хто з вас прав?
user4271704

@ user4271704 також використовується посилання, на яке ви посилаєтесь, explode("\r\n\r\n", $parts, 2); тому обидва мають рацію.
Кіборг

-5

Якщо вам не потрібно використовувати 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


16
Ум вам теж не потрібен PHP, але це, як буває, те, про що йдеться ...
Hans Z.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.