Czy PHP cURL może pobrać nagłówki odpowiedzi ORAZ treść w jednym żądaniu?

314

Czy jest jakiś sposób na uzyskanie zarówno nagłówków, jak i treści dla żądania cURL za pomocą PHP? Odkryłem, że ta opcja:

curl_setopt($ch, CURLOPT_HEADER, true);

zwróci ciało wraz z nagłówkami , ale potem muszę je przeanalizować, aby uzyskać ciało. Czy jest jakiś sposób, aby uzyskać oba w bardziej użyteczny (i bezpieczny) sposób?

Zauważ, że w przypadku „pojedynczego żądania” mam na myśli unikanie wydawania żądania HEAD przed GET / POST.

gremo
źródło
3
Jest na to wbudowane rozwiązanie, zobacz tę odpowiedź: stackoverflow.com/a/25118032/1334485 (dodał ten komentarz, ponieważ ten post wciąż ma wiele wyświetleń)
Skacc
Spójrz na ten miły komentarz: secure.php.net/manual/en/book.curl.php#117138
user956584
Powiedziano mi, że moje pytanie jest duplikatem tego pytania. Jeśli nie jest to duplikat, czy ktoś może go ponownie otworzyć? stackoverflow.com/questions/43770246/... W moim pytaniu mam konkretny wymóg użycia metody, która zwraca obiekt z osobnymi nagłówkami i treścią, a nie jednym ciągiem znaków.
1,21 gigawata

Odpowiedzi:

466

Jedno rozwiązanie tego zostało zamieszczone w komentarzach do dokumentacji PHP: http://www.php.net/manual/en/function.curl-exec.php#80442

Przykład kodu:

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

Ostrzeżenie: jak zauważono w komentarzach poniżej, może to nie być niezawodne, gdy jest używane z serwerami proxy lub podczas obsługi niektórych rodzajów przekierowań. Odpowiedź Geoffreya może obsłużyć je bardziej niezawodnie.

iblue
źródło
22
Możesz także list($header, $body) = explode("\r\n\r\n", $response, 2), ale może to potrwać nieco dłużej, w zależności od wielkości żądania.
iblue
43
jest to złe rozwiązanie, ponieważ jeśli korzystasz z serwera proxy, a twój serwer proxy (na przykład skrzypek) dodaje własne nagłówki do odpowiedzi - nagłówki te złamały wszystkie przesunięcia i powinieneś używać list($header, $body) = explode("\r\n\r\n", $response, 2)jako jedynego działającego wariantu
msangel
5
@msangel Twoje rozwiązanie nie działa, gdy w odpowiedzi jest wiele nagłówków, na przykład gdy serwer dokonuje przekierowania 302. Jakieś sugestie?
Nate
4
@Nate, tak, wiem o tym. AFAIK, ale istnieje tylko jeden możliwy dodatkowy nagłówek - z kodem 100(Kontynuuj). W przypadku tego nagłówka możesz obejść z poprawnie zdefiniowaną opcją żądania: curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); wyłączając wysyłanie tej odpowiedzi nagłówka. Co do 302tego, nie powinno się to zdarzyć, ponieważ nagłówek 302 jest przekierowany, nie oczekuje treści, jednak wiem, że czasami serwery wysyłają jakieś 302odpowiedzi z odpowiedzią, ale i tak będą ignorowane przez przeglądarki, do tej pory, dlaczego curl powinien sobie z tym poradzić? )
msangel
5
CURLOPT_VERBOSEma na celu przekazywanie informacji o procesie do STDERR(może przeszkadzać w CLI) i dla omawianego problemu jest bezużyteczny.
hejdav
205

Wiele innych rozwiązań oferowanych w tym wątku nie robi tego poprawnie.

  • Dzielenie \r\n\r\nnie jest niezawodne, gdy CURLOPT_FOLLOWLOCATIONjest włączone lub gdy serwer odpowiada kodem 100.
  • Nie wszystkie serwery są zgodne ze standardami i nadają tylko \ndla nowych linii.
  • Wykrywanie rozmiaru nagłówków za pomocą CURLINFO_HEADER_SIZErównież nie zawsze jest niezawodne, szczególnie gdy używane są serwery proxy lub w niektórych scenariuszach przekierowania.

Używana jest najbardziej poprawna metoda CURLOPT_HEADERFUNCTION.

Oto bardzo czysta metoda wykonywania tego przy użyciu zamknięć PHP. Konwertuje również wszystkie nagłówki na małe litery, aby zapewnić spójną obsługę serwerów i wersji HTTP.

Ta wersja zachowa zduplikowane nagłówki

Jest to zgodne z RFC822 i RFC2616, proszę nie sugerować zmian w celu korzystania z mb_funkcji ciągów, jest to niepoprawne!

$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);
Geoffrey
źródło
12
IMO to najlepsza odpowiedź w tym wątku i naprawia problemy z przekierowaniami występujące przy innych odpowiedziach. Najlepiej przeczytaj dokumentację CURLOPT_HEADERFUNCTION, aby zrozumieć, jak to działa i potencjalne problemy. Wprowadziłem również kilka poprawek w odpowiedzi, aby pomóc innym.
Simon East,
Świetnie, zaktualizowałem odpowiedź, aby uwzględnić zduplikowane nagłówki. W przyszłości nie należy ponownie formatować kodu zgodnie z jego oczekiwaniami. Jest to napisane w taki sposób, aby wyjaśnić, gdzie znajdują się granice funkcji zamknięcia.
Geoffrey,
@Geoffrey Czy $headers = [];ważny jest php?
thealexbaron
6
@ thealexbaron Tak, to jest od PHP 5.4, patrz: php.net/manual/en/migration54.new-features.php
Geoffrey
4
Ta odpowiedź jest bardzo niedoceniana w przypadku tak zgrabnego i zgodnego z RFC podejścia. Należy udzielić lepkiej odpowiedzi i przenieść na górę. Chciałbym tylko, żeby było szybsze podejście do uzyskania wartości pożądanego nagłówka zamiast parsowania wszystkich nagłówków w pierwszej kolejności.
Fr0zenFyr
114

Curl ma wbudowaną opcję tego, o nazwie CURLOPT_HEADERFUNCTION. Wartością tej opcji musi być nazwa funkcji zwrotnej. Curl przekazuje nagłówek (i tylko nagłówek!) Do tej funkcji zwrotnej, linia po linii (więc funkcja będzie wywoływana dla każdej linii nagłówka, zaczynając od góry sekcji nagłówka). Twoja funkcja zwrotna może wtedy z nią zrobić wszystko (i musi zwrócić liczbę bajtów danej linii). Oto sprawdzony działający kod:

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

Powyższe działa ze wszystkim, różnymi protokołami i serwerami proxy, i nie musisz martwić się o rozmiar nagłówka lub ustawić wiele różnych opcji zwijania.

PS: Aby obsłużyć linie nagłówka za pomocą metody obiektowej, wykonaj następujące czynności:

curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$object, 'methodName'))
Skacc
źródło
Uwaga: funkcja zwrotna jest wywoływana dla każdego nagłówka i wygląda na to, że nie są one przycinane. Możesz użyć zmiennej globalnej do przechowywania wszystkich nagłówków lub możesz użyć funkcji anonimowej dla wywołania zwrotnego i użyć zmiennej lokalnej (lokalnej dla zakresu nadrzędnego, a nie funkcji anonimowej).
MV.
2
@MV Dzięki, tak, przez „linia po linii” miałem na myśli „każdy nagłówek”. Zredagowałem swoją odpowiedź dla jasności. Aby uzyskać całą sekcję nagłówka (aka. Wszystkie nagłówki), można również użyć metody obiektowej dla wywołania zwrotnego, aby właściwość obiektu mogła przechowywać wszystkie z nich.
Skacc
8
To najlepsza odpowiedź IMO. Nie powoduje problemów z wieloma „\ r \ n \ r \ n” podczas korzystania z CURLOPT_FOLLOWLOCATION i myślę, że nie będą miały na to wpływu dodatkowe nagłówki z serwerów proxy.
Rafał G.
Pracował dla mnie bardzo dobrze, zobacz także stackoverflow.com/questions/6482068/... w przypadku problemów
RHH
1
Tak, jest to najlepsze podejście, jednak odpowiedź @ Geoffrey'a sprawia, że ​​jest to czystsze dzięki użyciu anonimowej funkcji bez potrzeby stosowania zmiennych globalnych i tym podobnych.
Simon East,
39

czy tego właśnie szukasz?

curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch); 
list($header, $body) = explode("\r\n\r\n", $response, 2);
użytkownik1031143
źródło
8
Działa to normalnie, z wyjątkiem sytuacji, gdy występuje HTTP / 1.1 100 Kontynuuj, po którym następuje przerwa, a następnie HTTP / 1.1 200 OK. Wybrałbym inną metodę.
widmo
1
Spójrz na wybraną odpowiedź stackoverflow.com/questions/14459704/... przed zaimplementowaniem czegoś takiego. 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
Ta metoda również nie działa w przypadku przekierowań 302, gdy curl jest ustawiony tak, aby podążał za nagłówkiem lokalizacji.
Simon East
10

Wystarczy ustawić opcje:

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

i użyj curl_getinfo z CURLINFO_HTTP_CODE (lub bez parametru opt, a będziesz miał tablicę asocjacyjną ze wszystkimi potrzebnymi informacjami)

Więcej na: http://php.net/manual/fr/function.curl-getinfo.php

Cyryl H.
źródło
5
Wydaje się, że wcale nie zwraca to nagłówków odpowiedzi. A przynajmniej nie ma możliwości ich odzyskania za pomocą curl_getinfo().
Simon East
8

Jeśli chcesz Content-Type, jest dostępna specjalna opcja cURL:

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
pr1001
źródło
OP zapytał, czy istnieje sposób na odzyskanie nagłówków, a nie jednego konkretnego nagłówka, to nie odpowiada na pytanie OP.
Geoffrey
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);

Działa z HTTP/1.1 100 Continueinnymi nagłówkami.

Jeśli potrzebujesz pracy z błędnymi serwerami, które wysyłają tylko LF zamiast CRLF jako podziały linii, możesz użyć preg_splitw następujący sposób:

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);
Enyby
źródło
Czy $parts = explode("\r\n\r\nHTTP/", $response);trzeci parametr parametru Explode nie powinien mieć wartości 2?
user4271704,
@ user4271704 Nie. Pozwala znaleźć ostatnią wiadomość HTTP. HTTP/1.1 100 Continuemoże pojawić się wiele razy.
Enyby,
Ale mówi coś jeszcze: stackoverflow.com/questions/9183178/... który z was ma rację?
user4271704,
HTTP/1.1 100 Continuemoże pojawić się wiele razy. Wyświetlany jest przypadek, jeśli pojawia się tylko jeden raz, ale w powszechnym przypadku jest niepoprawny. Na przykład 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...jego kod nie działa poprawnie
Enyby
1
Podział na \ r \ n nie jest niezawodny, niektóre serwery nie są zgodne ze specyfikacjami HTTP i wysyłają tylko \ n. Standard RFC stwierdza, że ​​aplikacje powinny ignorować \ ri podzielić się na \ n, aby uzyskać najwyższą niezawodność.
Geoffrey,
1

Moja droga jest taka

$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];
}

W razie potrzeby zastosuj pętlę for i usuń limit wybuchu.

Roy
źródło
1

Oto mój wkład w debatę ... Zwraca jedną tablicę z oddzielonymi danymi i wymienionymi nagłówkami. Działa to na podstawie tego, że CURL zwróci dane fragmentu nagłówków [pusta linia]

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;
    }
}
Antony
źródło
0

Problem z wieloma odpowiedziami tutaj "\r\n\r\n"może polegać na tym, że mogą pojawić się w treści html, więc nie możesz być pewien, że poprawnie dzielisz nagłówki.

Wydaje się, że jedynym sposobem na przechowywanie nagłówków osobno za pomocą jednego wywołania curl_execjest użycie wywołania zwrotnego, jak sugerowano powyżej w https://stackoverflow.com/a/25118032/3326494

Następnie, aby (niezawodnie) uzyskać tylko treść żądania, musisz przekazać wartość Content-Lengthnagłówka substr()jako ujemną wartość początkową.

mal
źródło
1
Może wydawać się uzasadniony, ale twoja odpowiedź jest nieprawidłowa. Długość treści nie musi być obecna w odpowiedzi HTTP. Prawidłową metodą ręcznego parsowania nagłówków jest poszukiwanie pierwszej instancji \ r \ n (lub \ n \ n). Można to zrobić po prostu ograniczając eksplozję, aby zwrócić tylko dwa elementy, tj .: list($head, $body) = explode("\r\n\r\n", $response, 2);jednak CURL już to robi, jeśli używaszcurl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
Geoffrey
-1

Na wypadek, gdybyś nie mógł / nie używał CURLOPT_HEADERFUNCTIONlub innych rozwiązań;

$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));
}
K-Gun
źródło
-2

Zwróć nagłówki odpowiedzi z parametrem referencyjnym:

<?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;
         }
?>
diyizm
źródło
Czy na pewno masz $rtn=explode("\r\n\r\nHTTP/", $rtn, 2);rację? Czy trzeci parametr rozstrzelenia nie powinien zostać usunięty?
user4271704,
@ user4271704, trzeci parametr dotyczy nagłówka „HTTP / 1.1 100 Kontynuuj \ r \ n \ r \ nHTTP / 1.1 200 OK ... \ r \ n \ r \ n ...” nagłówek
diyizm
Ale powiedział coś jeszcze: stackoverflow.com/questions/9183178/... który z was ma rację?
user4271704,
@ user4271704 link, którego dotyczysz, również używasz: explode("\r\n\r\n", $parts, 2); więc oba mają rację.
Cyborg
-5

Jeśli tak naprawdę nie musisz używać curl;

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

Które wyjścia

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

Zobacz http://php.net/manual/en/reserved.variables.httpresponseheader.php

Bevan
źródło
16
uhm, tak naprawdę nie potrzebujesz PHP, ale tak się składa właśnie o to pytanie ...
Hans Z.