PHP cURL peut-il récupérer les en-têtes de réponse ET le corps en une seule demande?

314

Existe-t-il un moyen d'obtenir à la fois les en-têtes et le corps d'une demande cURL en utilisant PHP? J'ai trouvé que cette option:

curl_setopt($ch, CURLOPT_HEADER, true);

va renvoyer le corps et les en-têtes , mais je dois ensuite l'analyser pour obtenir le corps. Existe-t-il un moyen d'obtenir les deux de manière plus utilisable (et sécurisée)?

Notez que pour une "demande unique", je veux dire éviter d'émettre une demande HEAD avant GET / POST.

gremo
la source
3
Il existe une solution intégrée pour cela, voir cette réponse: stackoverflow.com/a/25118032/1334485 (a ajouté ce commentaire 'car cet article obtient toujours de nombreuses vues)
Skacc
Regardez ce joli commentaire: secure.php.net/manual/en/book.curl.php#117138
user956584
On m'a dit que ma question était un double de cette question. S'il ne s'agit pas d'un double, quelqu'un peut-il le rouvrir? stackoverflow.com/questions/43770246/… Dans ma question, j'ai une exigence concrète d'utiliser une méthode qui renvoie un objet avec des en-têtes et un corps séparés et non une chaîne.
1,21 gigawatts

Réponses:

466

Une solution à cela a été publiée dans les commentaires de la documentation PHP: http://www.php.net/manual/en/function.curl-exec.php#80442

Exemple de code:

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

Avertissement: comme indiqué dans les commentaires ci-dessous, cela peut ne pas être fiable lorsqu'il est utilisé avec des serveurs proxy ou lors de la gestion de certains types de redirections. @ La réponse de Geoffrey peut les gérer de manière plus fiable.

iblue
la source
22
Vous pouvez également list($header, $body) = explode("\r\n\r\n", $response, 2), mais cela peut prendre un peu plus de temps, selon la taille de votre demande.
iblue
43
c'est une mauvaise solution car si vous utilisez un serveur proxy et que votre serveur proxy (violoneux par exemple) ajoute ses propres en-têtes à la réponse - ces en-têtes ont interrompu tous les décalages et vous ne devez utiliser list($header, $body) = explode("\r\n\r\n", $response, 2)que la variante de travail
msangel
5
@msangel Votre solution ne fonctionne pas lorsqu'il y a plusieurs en-têtes dans la réponse, par exemple lorsque le serveur effectue une redirection 302. Aucune suggestion?
Nate
4
@Nate, oui, je le sais. AFAIK, mais il n'y a qu'un seul en-tête supplémentaire possible - avec le code 100(Continuer). Pour cet en-tête, vous pouvez définir l'option de demande correctement définie:, curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); désactiver l'envoi de cette réponse d'en-tête. Quant à 302cela, cela ne devrait pas se produire, car l'en-tête 302 est une redirection, il n'attend pas de corps, mais je sais, parfois les serveurs envoient du corps avec une 302réponse, mais il sera de toute façon ignoré par les navigateurs, jusqu'à présent, pourquoi curl devrait-il gérer cela? )
msangel
5
CURLOPT_VERBOSEest destiné à la sortie des informations de processus STDERR(peut déranger en CLI) et pour le problème discuté est inutile.
hejdav
205

La plupart des autres solutions proposées par ce fil ne le font pas correctement.

  • Le fractionnement \r\n\r\nn'est pas fiable lorsqu'il CURLOPT_FOLLOWLOCATIONest activé ou lorsque le serveur répond avec un code 100.
  • Tous les serveurs ne sont pas conformes aux normes et ne transmettent qu'un \npour les nouvelles lignes.
  • La détection de la taille des en-têtes via CURLINFO_HEADER_SIZEn'est également pas toujours fiable, en particulier lorsque des proxys sont utilisés ou dans certains des mêmes scénarios de redirection.

La méthode la plus correcte utilise CURLOPT_HEADERFUNCTION.

Voici une méthode très propre pour effectuer cela en utilisant des fermetures PHP. Il convertit également tous les en-têtes en minuscules pour une gestion cohérente sur les serveurs et les versions HTTP.

Cette version conservera les en-têtes en double

Ceci est conforme aux RFC822 et RFC2616, veuillez ne pas suggérer de modifications pour utiliser les mb_fonctions de chaîne, c'est incorrect!

$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
la source
12
IMO, c'est la meilleure réponse dans ce fil et résout les problèmes de redirection qui se sont produits avec d'autres réponses. Mieux vaut lire la documentation de CURLOPT_HEADERFUNCTION pour comprendre comment cela fonctionne et les pièges potentiels. J'ai également apporté quelques améliorations à la réponse pour aider les autres.
Simon East
Très bien, j'ai mis à jour la réponse pour répondre aux en-têtes en double. À l'avenir, ne reformatez pas le code selon ce que vous pensez qu'il devrait être. Ceci est écrit de manière à indiquer clairement où se trouvent les limites de la fonction de fermeture.
Geoffrey
@Geoffrey Le $headers = [];php est-il valide?
thealexbaron du
6
@thealexbaron Oui, c'est à partir de PHP 5.4, voir: php.net/manual/en/migration54.new-features.php
Geoffrey
4
Cette réponse est très sous-estimée pour une approche aussi soignée et conforme à la RFC. Cela devrait être rendu réponse collante et déplacé vers le haut. Je souhaite juste qu'il y ait une approche plus rapide pour obtenir la valeur d'un en-tête souhaité au lieu d'analyser tous les en-têtes en premier.
Fr0zenFyr
114

Curl a une option intégrée pour cela, appelée CURLOPT_HEADERFUNCTION. La valeur de cette option doit être le nom d'une fonction de rappel. Curl passera l'en-tête (et l'en-tête uniquement!) À cette fonction de rappel, ligne par ligne (donc la fonction sera appelée pour chaque ligne d'en-tête, en commençant par le haut de la section d'en-tête). Votre fonction de rappel peut alors tout faire (et doit renvoyer le nombre d'octets de la ligne donnée). Voici un code de travail testé:

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

Ce qui précède fonctionne avec tout, différents protocoles et proxys aussi, et vous n'avez pas à vous soucier de la taille de l'en-tête, ni à définir de nombreuses options de boucles différentes.

PS: pour gérer les lignes d'en-tête avec une méthode objet, procédez comme suit:

curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$object, 'methodName'))
Skacc
la source
Remarque: la fonction de rappel est appelée pour chaque en-tête et il semble qu'ils ne soient pas coupés. Vous pouvez utiliser une variable globale pour contenir tous les en-têtes ou vous pouvez utiliser une fonction anonyme pour le rappel et utiliser une variable locale (locale pour la portée parent, pas la fonction anonyme).
MV.
2
@MV Merci, oui, par "ligne par ligne", je voulais dire "chaque en-tête". J'ai édité ma réponse pour plus de clarté. Pour obtenir l'intégralité de la section d'en-tête (c'est-à-dire tous les en-têtes), vous pouvez également utiliser une méthode d'objet pour le rappel afin qu'une propriété d'objet puisse les contenir tous.
Skacc
8
C'est la meilleure réponse de l'OMI. Cela ne cause pas de problèmes avec plusieurs "\ r \ n \ r \ n" lors de l'utilisation de CURLOPT_FOLLOWLOCATION et je suppose que cela ne sera pas affecté par des en-têtes supplémentaires provenant des proxys.
Rafał G.
A très bien fonctionné pour moi, voir également stackoverflow.com/questions/6482068/… en cas de problème
RHH
1
Oui, c'est la meilleure approche mais la réponse de @ Geoffrey rend cela plus propre en utilisant une fonction anonyme sans avoir besoin de variables globales et autres.
Simon East
39

est-ce que tu cherches?

curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch); 
list($header, $body) = explode("\r\n\r\n", $response, 2);
user1031143
la source
8
Cela fonctionne normalement sauf quand il y a un HTTP / 1.1 100 Continue suivi d'une pause puis HTTP / 1.1 200 OK. J'irais avec l'autre méthode.
ghostfly
1
Jetez un œil à la réponse sélectionnée de stackoverflow.com/questions/14459704/… avant d'implémenter quelque chose comme ça. 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
Cette méthode échoue également sur les redirections 302 lorsque curl est défini pour suivre l'en-tête d'emplacement.
Simon East
10

Définissez simplement les options:

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

et utilisez curl_getinfo avec CURLINFO_HTTP_CODE (ou pas de paramètre opt et vous aurez un tableau associatif avec toutes les informations que vous voulez)

Plus sur: http://php.net/manual/fr/function.curl-getinfo.php

Cyril H.
la source
5
Cela ne semble pas du tout vous renvoyer les en-têtes de réponse. Ou du moins, il n'y a aucun moyen de les récupérer en utilisant curl_getinfo().
Simon East
8

Si vous voulez spécifiquement le Content-Type, il y a une option spéciale cURL pour le récupérer:

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
pr1001
la source
L'OP a demandé s'il y avait un moyen de récupérer les en-têtes, pas un en-tête spécifique, cela ne répond pas à la question de l'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);

Fonctionne avec HTTP/1.1 100 Continueavant les autres en-têtes.

Si vous avez besoin de travailler avec des serveurs buggy qui n'envoient que LF au lieu de CRLF comme sauts de ligne, vous pouvez utiliser preg_splitcomme suit:

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
la source
Ne devrait pas $parts = explode("\r\n\r\nHTTP/", $response);avoir le 3ème paramètre pour exploser en 2?
user4271704
@ user4271704 Non. Il permet de rechercher le dernier message HTTP. HTTP/1.1 100 Continuepeut apparaître plusieurs fois.
Enyby
Mais il dit autre chose: stackoverflow.com/questions/9183178/… lequel d'entre vous a raison?
user4271704
HTTP/1.1 100 Continuepeut apparaître plusieurs fois. Il voit le cas s'il n'apparaît qu'une seule fois, mais il se trompe dans le cas commun. Par exemple, 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...son code ne fonctionne pas correctement
Enyby
1
Le fractionnement sur \ r \ n n'est pas fiable, certains serveurs ne sont pas conformes aux spécifications HTTP et n'enverront qu'un \ n. La norme RFC stipule que les applications doivent ignorer \ r et se répartir sur \ n pour une fiabilité maximale.
Geoffrey
1

Ma voie est

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

Si nécessaire, appliquez une boucle for et supprimez la limite d'explosion.

Roy
la source
1

Voici ma contribution au débat ... Cela renvoie un tableau unique avec les données séparées et les en-têtes répertoriés. Cela fonctionne sur la base que CURL renverra des données de bloc d'en-têtes [ligne vierge]

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
la source
0

Le problème avec de nombreuses réponses ici est que cela "\r\n\r\n"peut légitimement apparaître dans le corps du HTML, vous ne pouvez donc pas être sûr de diviser correctement les en-têtes.

Il semble que la seule façon de stocker les en-têtes séparément avec un appel à curl_execest d'utiliser un rappel comme suggéré ci-dessus dans https://stackoverflow.com/a/25118032/3326494

Et pour obtenir (de manière fiable) uniquement le corps de la demande, vous devez passer la valeur de l'en- Content-Lengthtête à substr()une valeur de départ négative.

mal
la source
1
Il peut apparaître légitimement, mais votre réponse est incorrecte. Il n'est pas nécessaire que Content-Length soit présent dans une réponse HTTP. La bonne méthode pour analyser manuellement les en-têtes consiste à rechercher la première instance de \ r \ n (ou \ n \ n). Cela pourrait être fait simplement en limitant l'explosion pour ne renvoyer que deux éléments, c'est-à-dire: list($head, $body) = explode("\r\n\r\n", $response, 2);cependant CURL le fait déjà pour vous si vous utilisezcurl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
Geoffrey
-1

Juste au cas où vous ne pourriez / n'utilisez pas CURLOPT_HEADERFUNCTIONou d'autres solutions;

$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
la source
-2

Renvoie les en-têtes de réponse avec un paramètre de référence:

<?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;
         }
?>
bricolage
la source
Êtes-vous sûr d'avoir $rtn=explode("\r\n\r\nHTTP/", $rtn, 2);raison? Le troisième paramètre d'explosion ne devrait-il pas être supprimé?
user4271704
@ user4271704, le 3ème paramètre est de traiter l'en-tête "HTTP / 1.1 100 Continue \ r \ n \ r \ nHTTP / 1.1 200 OK ... \ r \ n \ r \ n ..."
diyism
Mais il a dit autre chose: stackoverflow.com/questions/9183178/… lequel d'entre vous a raison?
user4271704
@ user4271704 le lien auquel vous faites référence utilise également: explode("\r\n\r\n", $parts, 2); les deux ont donc raison.
Cyborg
-5

Si vous n'avez pas vraiment besoin d'utiliser curl;

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

Quelles sorties

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

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

Bevan
la source
16
euh, vous n'avez pas vraiment besoin de PHP non plus, mais il se trouve que c'est la question ...
Hans Z.