Kann PHP cURL Antwortheader UND -text in einer einzigen Anforderung abrufen?


314

Gibt es eine Möglichkeit, sowohl Header als auch Body für eine cURL-Anfrage mit PHP abzurufen? Ich fand, dass diese Option:

curl_setopt($ch, CURLOPT_HEADER, true);

wird den Körper plus Überschriften zurückgeben , aber dann muss ich ihn analysieren, um den Körper zu erhalten. Gibt es eine Möglichkeit, beides benutzerfreundlicher (und sicherer) zu machen?

Beachten Sie, dass ich für "Einzelanforderung" die Ausgabe einer HEAD-Anforderung vor GET / POST vermeiden möchte.


3
Hierfür gibt es eine integrierte Lösung, siehe folgende Antwort: stackoverflow.com/a/25118032/1334485 (Kommentar hinzugefügt, da dieser Beitrag immer noch viele Aufrufe enthält)
Skacc

Schauen Sie sich diesen schönen Kommentar an: Secure.php.net/manual/en/book.curl.php#117138
user956584


Mir wurde gesagt, meine Frage sei ein Duplikat dieser Frage. Wenn es kein Duplikat ist, kann jemand es bitte wieder öffnen? stackoverflow.com/questions/43770246/… In meiner Frage habe ich eine konkrete Anforderung, eine Methode zu verwenden, die ein Objekt mit getrennten Headern und Körpern und nicht einer Zeichenfolge zurückgibt.
1,21 Gigawatt

Antworten:


466

Eine Lösung hierfür wurde in den Kommentaren zur PHP-Dokumentation veröffentlicht: http://www.php.net/manual/en/function.curl-exec.php#80442

Codebeispiel:

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

Warnung: Wie in den Kommentaren unten angegeben, ist dies möglicherweise nicht zuverlässig, wenn es mit Proxyservern verwendet wird oder wenn bestimmte Arten von Weiterleitungen verarbeitet werden. @ Geoffrey Antwort kann diese zuverlässiger behandeln.


22
Sie können dies auch list($header, $body) = explode("\r\n\r\n", $response, 2), dies kann jedoch je nach Anforderungsgröße etwas länger dauern.
iblue

43
Dies ist eine schlechte Lösung, denn wenn Sie einen Proxyserver verwenden und Ihr Proxyserver (z. B. Geiger) der Antwort eigene Header hinzufügen - diese Header haben alle Offsets gebrochen und Sie sollten sie list($header, $body) = explode("\r\n\r\n", $response, 2)als einzige funktionierende Variante verwenden
msangel

5
@msangel Ihre Lösung funktioniert nicht, wenn die Antwort mehrere Header enthält, z. B. wenn der Server eine 302-Umleitung ausführt. Irgendwelche Vorschläge?
Nate

4
@Nate, ja, ich weiß das. AFAIK, aber es gibt nur einen möglichen zusätzlichen Header - mit Code 100(Weiter). Für diesen Header können Sie die Anforderungsoption korrekt definieren: curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); Deaktivieren Sie das Senden dieser Header-Antwort. 302Dies sollte nicht passieren, da der 302-Header umgeleitet wird und keinen Body erwartet. Wie ich jedoch weiß, senden Server manchmal einen Body mit 302Antwort, aber er wird von den Browsern bisher trotzdem ignoriert. Warum sollte Curl damit umgehen? )
Msangel

5
CURLOPT_VERBOSEist dazu gedacht, Prozessinformationen an STDERR(kann in der CLI stören) auszugeben und ist für das diskutierte Problem nutzlos.
Hejdav

205

Viele der anderen in diesem Thread angebotenen Lösungen machen dies nicht richtig.

  • Das Aufteilen \r\n\r\nist nicht zuverlässig, wenn es eingeschaltet CURLOPT_FOLLOWLOCATIONist oder wenn der Server mit einem 100-Code antwortet.
  • Nicht alle Server sind standardkonform und übertragen nur eine \nfür neue Leitungen.
  • Das Erkennen der Größe der Header über CURLINFO_HEADER_SIZEist auch nicht immer zuverlässig, insbesondere wenn Proxys verwendet werden oder in einigen der gleichen Umleitungsszenarien.

Die korrekteste Methode ist die Verwendung CURLOPT_HEADERFUNCTION.

Hier ist eine sehr saubere Methode, um dies mit PHP-Closures durchzuführen. Außerdem werden alle Header in Kleinbuchstaben konvertiert, um eine konsistente Behandlung zwischen Servern und HTTP-Versionen zu gewährleisten.

Diese Version behält doppelte Header bei

Dies entspricht RFC822 und RFC2616. Bitte schlagen Sie keine Änderungen vor, um die mb_Zeichenfolgenfunktionen nutzen zu können. Es ist falsch!

$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 ist dies die beste Antwort in diesem Thread und behebt Probleme mit Weiterleitungen, die bei anderen Antworten aufgetreten sind. Lesen Sie am besten die Dokumentation zu CURLOPT_HEADERFUNCTION, um zu verstehen, wie es funktioniert und mögliche Fallstricke auftreten. Ich habe auch einige Verbesserungen an der Antwort vorgenommen, um anderen zu helfen.
Simon East

Großartig, ich habe die Antwort aktualisiert, um doppelte Header zu berücksichtigen. Formatieren Sie den Code in Zukunft nicht mehr so, wie Sie es für richtig halten. Dies ist so geschrieben, dass klar wird, wo sich die Grenzen der Schließfunktionen befinden.
Geoffrey

@Geoffrey Ist $headers = [];gültiges PHP?
Thealexbaron

6
@thealexbaron Ja, es ist ab PHP 5.4, siehe: php.net/manual/en/migration54.new-features.php
Geoffrey

4
Diese Antwort wird für einen so ordentlichen und RFC-konformen Ansatz stark unterschätzt. Dies sollte eine klebrige Antwort sein und nach oben verschoben werden. Ich wünschte nur, es gäbe einen schnelleren Ansatz, um den Wert eines gewünschten Headers zu ermitteln, anstatt zuerst alle Header zu analysieren.
Fr0zenFyr

114

Curl verfügt hierfür über eine integrierte Option namens CURLOPT_HEADERFUNCTION. Der Wert dieser Option muss der Name einer Rückruffunktion sein. Curl übergibt den Header (und nur den Header!) Zeile für Zeile an diese Rückruffunktion (die Funktion wird also für jede Headerzeile beginnend am oberen Rand des Header-Abschnitts aufgerufen). Ihre Rückruffunktion kann dann alles damit machen (und muss die Anzahl der Bytes der angegebenen Zeile zurückgeben). Hier ist ein getesteter Arbeitscode:

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

Das Obige funktioniert mit allem, auch mit verschiedenen Protokollen und Proxys, und Sie müssen sich keine Gedanken über die Headergröße machen oder viele verschiedene Curl-Optionen festlegen.

PS: Um die Kopfzeilen mit einer Objektmethode zu behandeln, gehen Sie folgendermaßen vor:

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

Als Hinweis wird die Rückruffunktion für jeden Header aufgerufen und es scheint, dass sie nicht gekürzt werden. Sie können eine globale Variable verwenden, um alle Header zu speichern, oder Sie können eine anonyme Funktion für den Rückruf verwenden und eine lokale Variable verwenden (lokal für den übergeordneten Bereich, nicht die anonyme Funktion).
MV.

2
@MV Danke, ja, mit "Zeile für Zeile" meinte ich "jeden Header". Ich habe meine Antwort aus Gründen der Klarheit bearbeitet. Um den gesamten Header-Abschnitt (auch bekannt als alle Header) abzurufen, können Sie auch eine Objektmethode für den Rückruf verwenden, damit eine Objekteigenschaft alle enthalten kann.
Skacc

8
Dies ist die beste Antwort IMO. Es verursacht keine Probleme mit mehreren "\ r \ n \ r \ n", wenn CURLOPT_FOLLOWLOCATION verwendet wird, und ich denke, es wird nicht durch zusätzliche Header von Proxys beeinflusst.
Rafał G.

Hat sehr gut für mich funktioniert , siehe auch stackoverflow.com/questions/6482068/… bei Problemen
RHH

1
Ja, dies ist der beste Ansatz. Die Antwort von @ Geoffrey macht dies jedoch sauberer, indem eine anonyme Funktion verwendet wird, ohne dass globale Variablen und dergleichen erforderlich sind.
Simon East

39

ist es das, wonach du suchst?

curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch); 
list($header, $body) = explode("\r\n\r\n", $response, 2);

8
Dies funktioniert normalerweise, außer wenn es ein HTTP / 1.1 100 Continue gibt, gefolgt von einer Pause, dann HTTP / 1.1 200 OK. Ich würde mit der anderen Methode gehen.
Ghostfly

1
Schauen Sie sich die ausgewählte Antwort von stackoverflow.com/questions/14459704/… an, bevor Sie so etwas implementieren. 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


Diese Methode schlägt auch bei 302-Weiterleitungen fehl, wenn die Locke so eingestellt ist, dass sie dem Positionsheader folgt.
Simon East

10

Einfach Optionen einstellen:

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

und verwenden Sie curl_getinfo mit CURLINFO_HTTP_CODE (oder ohne opt param und Sie haben ein assoziatives Array mit allen gewünschten Informationen)

Mehr unter: http://php.net/manual/fr/function.curl-getinfo.php


5
Dies scheint die Antwort-Header überhaupt nicht an Sie zurückzugeben. Zumindest gibt es keine Möglichkeit, sie mit abzurufen curl_getinfo().
Simon East

8

Wenn Sie das speziell möchten Content-Type, gibt es eine spezielle cURL-Option, um es abzurufen:

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

Das OP fragte, ob es eine Möglichkeit gibt, die Header abzurufen, nicht einen bestimmten Header. Dies beantwortet die Frage des OP nicht.
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);

Funktioniert mit HTTP/1.1 100 Continuevor anderen Headern.

Wenn Sie mit fehlerhaften Servern arbeiten müssen, die nur LF anstelle von CRLF als Zeilenumbrüche senden, können Sie preg_splitFolgendes verwenden:

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

Sollte der $parts = explode("\r\n\r\nHTTP/", $response);3. Parameter für die Explosion nicht 2 sein?
user4271704

@ user4271704 Nein. Es ermöglicht das Auffinden der letzten HTTP-Nachricht. HTTP/1.1 100 Continuekann viele Male auftreten.
Enyby

Aber er sagt noch etwas anderes: stackoverflow.com/questions/9183178/… Wer von euch hat Recht ?
user4271704

HTTP/1.1 100 Continuekann viele Male auftreten. Er sieht den Fall an, wenn er nur einmal erscheint, aber im allgemeinen Fall ist er falsch. Zum Beispiel für 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...seinen Code nicht richtig funktionieren
Enyby

1
Das Aufteilen auf \ r \ n ist nicht zuverlässig, einige Server entsprechen nicht den HTTP-Spezifikationen und senden nur ein \ n. Der RFC-Standard besagt, dass Anwendungen \ r ignorieren und auf \ n aufteilen sollten, um höchste Zuverlässigkeit zu gewährleisten.
Geoffrey

1

Mein Weg ist

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

Wenden Sie bei Bedarf eine for-Schleife an und entfernen Sie die Explosionsgrenze.


1

Hier ist mein Beitrag zur Debatte ... Dies gibt ein einzelnes Array mit getrennten Daten und aufgelisteten Headern zurück. Dies funktioniert auf der Grundlage, dass CURL einen Header-Chunk [leere Zeile] zurückgibt

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

Das Problem mit vielen Antworten hier ist, dass "\r\n\r\n"es legitimerweise im HTML-Text erscheinen kann, sodass Sie nicht sicher sein können, ob Sie die Header korrekt aufteilen.

Es scheint, dass die einzige Möglichkeit, Header mit einem Aufruf von separat zu speichern, darin curl_execbesteht, einen Rückruf zu verwenden, wie oben unter https://stackoverflow.com/a/25118032/3326494 vorgeschlagen

Und um dann (zuverlässig) nur den Hauptteil der Anforderung zu erhalten, müssten Sie den Wert des Content-LengthHeaders substr()als negativen Startwert übergeben.


1
Es kann legitim erscheinen, aber Ihre Antwort ist falsch. Die Inhaltslänge muss in einer HTTP-Antwort nicht vorhanden sein. Die richtige Methode zum manuellen Parsen der Header besteht darin, nach der ersten Instanz von \ r \ n (oder \ n \ n) zu suchen. Dies könnte einfach dadurch geschehen, dass die Explosion so begrenzt wird, dass nur zwei Elemente zurückgegeben werden, dh: list($head, $body) = explode("\r\n\r\n", $response, 2);CURL erledigt dies jedoch bereits für Sie, wenn Siecurl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
Geoffrey

-1

Nur für den Fall, dass Sie keine CURLOPT_HEADERFUNCTIONanderen Lösungen verwenden können / wollen ;

$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

Antwortheader mit einem Referenzparameter zurückgeben:

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

Bist du sicher, dass $rtn=explode("\r\n\r\nHTTP/", $rtn, 2);es richtig ist? Sollte der dritte Explosionsparameter nicht entfernt werden?
user4271704

@ user4271704, der 3. Parameter befasst sich mit "HTTP / 1.1 100 Weiter \ r \ n \ r \ nHTTP / 1.1 200 OK ... \ r \ n \ r \ n ..." Header
Diyismus

Aber er sagte noch etwas anderes: stackoverflow.com/questions/9183178/… wer von euch hat recht ?
user4271704

@ user4271704 Der Link, auf den Sie sich beziehen, verwendet auch: explode("\r\n\r\n", $parts, 2); also sind beide richtig.
Cyborg

-5

Wenn Sie Curl nicht wirklich verwenden müssen;

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

Welche Ausgänge

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

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


16
ähm, du brauchst auch nicht wirklich PHP, aber genau darum geht es in der Frage ...
Hans Z.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.