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.

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

iblue
quelle
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);
Geoffrey
quelle
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'))
Skacc
quelle
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);
user1031143
quelle
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

Cyril H.
quelle
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);
pr1001
quelle
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);
Enyby
quelle
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.

Roy
quelle
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;
    }
}
Antonius
quelle
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.

mal
quelle
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));
}
K-Gun
quelle
-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;
         }
?>
DIYismus
quelle
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

Bevan
quelle
16
ähm, du brauchst auch nicht wirklich PHP, aber genau darum geht es in der Frage ...
Hans Z.