PHP cURL può recuperare le intestazioni di risposta E il corpo in una singola richiesta?


314

C'è un modo per ottenere sia le intestazioni che il corpo per una richiesta cURL usando PHP? Ho scoperto che questa opzione:

curl_setopt($ch, CURLOPT_HEADER, true);

restituirà il corpo più le intestazioni , ma poi devo analizzarlo per ottenere il corpo. Esiste un modo per ottenere entrambi in un modo più utilizzabile (e sicuro)?

Si noti che per "singola richiesta" intendo evitare di emettere una richiesta HEAD prima di GET / POST.


3
C'è una soluzione integrata per questo, vedi questa risposta: stackoverflow.com/a/25118032/1334485 (aggiunto questo commento 'Coz questo post ottiene ancora molte visualizzazioni)
Skacc



Mi è stato detto che la mia domanda era un duplicato di questa domanda. Se non è un duplicato, qualcuno può riaprirlo? stackoverflow.com/questions/43770246/… Nella mia domanda ho un requisito concreto di utilizzare un metodo che restituisce un oggetto con intestazioni e corpo separati e non una stringa.
1,21 gigawatt

Risposte:


466

Una soluzione a questo è stata pubblicata nei commenti sulla documentazione di PHP: http://www.php.net/manual/en/function.curl-exec.php#80442

Esempio di codice:

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

Avviso: come indicato nei commenti seguenti, questo potrebbe non essere affidabile se utilizzato con server proxy o durante la gestione di determinati tipi di reindirizzamenti. La risposta di @ Geoffrey può gestirli in modo più affidabile.


22
Puoi anche list($header, $body) = explode("\r\n\r\n", $response, 2), ma ciò potrebbe richiedere un po 'più di tempo, a seconda delle dimensioni della tua richiesta.
iblue,

43
questa è una cattiva soluzione perché se usi il server proxy e il tuo server proxy (fiddler per esempio) aggiungi le tue intestazioni alla risposta - queste intestazioni hanno rotto tutti gli offset e dovresti usare list($header, $body) = explode("\r\n\r\n", $response, 2)come unica variante funzionante
msangel

5
@msangel La soluzione non funziona quando nella risposta sono presenti più intestazioni, ad esempio quando il server esegue un reindirizzamento 302. Eventuali suggerimenti?
Nate

4
@Nate, sì, lo so. AFAIK, ma esiste solo una possibile intestazione aggiuntiva - con codice 100(Continua). Per questa intestazione puoi andare in giro con la definizione corretta dell'opzione di richiesta:, curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); disabilitando l'invio di questa risposta di intestazione. Per quanto riguarda 302ciò, ciò non dovrebbe accadere, poiché l'intestazione 302 è il reindirizzamento, non si aspetta un corpo, per quanto ne so, a volte i server inviano un corpo con una 302risposta, ma sarà comunque ignorato dai browser, finora, perché curl dovrebbe gestirlo? )
msangel

5
CURLOPT_VERBOSEè destinato a fornire informazioni di processo a STDERR(potrebbe disturbare nella CLI) e per il problema discusso è inutile.
hejdav,

205

Molte delle altre soluzioni offerte da questo thread non lo stanno facendo correttamente.

  • Splitting su \r\n\r\nnon è affidabile quando CURLOPT_FOLLOWLOCATIONè acceso o quando le risponde server con un codice 100.
  • Non tutti i server sono conformi agli standard e trasmettono solo \nper le nuove linee.
  • Anche il rilevamento delle dimensioni delle intestazioni tramite CURLINFO_HEADER_SIZEnon è sempre affidabile, specialmente quando vengono utilizzati i proxy o in alcuni degli stessi scenari di reindirizzamento.

Il metodo più corretto sta usando CURLOPT_HEADERFUNCTION.

Ecco un metodo molto pulito per eseguire ciò utilizzando le chiusure PHP. Converte anche tutte le intestazioni in lettere minuscole per una gestione coerente tra server e versioni HTTP.

Questa versione manterrà le intestazioni duplicate

Questo è conforme a RFC822 e RFC2616, non suggerire modifiche per utilizzare le mb_funzioni di stringa, non è corretto!

$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 questa è la risposta migliore in questo thread e risolve i problemi con i reindirizzamenti che si sono verificati con altre risposte. Meglio leggere la documentazione di CURLOPT_HEADERFUNCTION per capire come funziona e i potenziali problemi. Ho anche apportato alcuni miglioramenti alla risposta per aiutare gli altri.
Simon East,

Bene, ho aggiornato la risposta per soddisfare le intestazioni duplicate. In futuro non riformattare il codice come dovrebbe essere. Questo è scritto in modo da chiarire dove sono i confini della funzione di chiusura.
Geoffrey,

@Geoffrey È $headers = [];php valido?
thealexbaron,

6
@thealexbaron Sì, è a partire da PHP 5.4, vedi: php.net/manual/en/migration54.new-features.php
Geoffrey,

4
Questa risposta è altamente sottovalutata per un approccio così pulito e conforme a RFC. Questa dovrebbe essere una risposta appiccicosa e spostata in alto. Vorrei solo che ci fosse un approccio più veloce per ottenere il valore di un'intestazione desiderata invece di analizzare prima tutte le intestazioni.
Fr0zenFir

114

Curl ha un'opzione integrata per questo, chiamata CURLOPT_HEADERFUNCTION. Il valore di questa opzione deve essere il nome di una funzione di callback. Curl passerà l'intestazione (e solo l'intestazione!) A questa funzione di richiamata, riga per riga (quindi la funzione verrà chiamata per ogni riga di intestazione, a partire dalla parte superiore della sezione dell'intestazione). La tua funzione di callback può quindi fare qualsiasi cosa con essa (e deve restituire il numero di byte di una determinata riga). Ecco un codice di lavoro testato:

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

Quanto sopra funziona con tutto, protocolli e proxy diversi, e non devi preoccuparti della dimensione dell'intestazione o impostare molte opzioni di arricciatura diverse.

PS: per gestire le righe di intestazione con un metodo oggetto, procedere come segue:

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

Come nota, la funzione di callback viene chiamata per ogni intestazione e sembra che non siano tagliati. È possibile utilizzare una variabile globale per contenere tutte le intestazioni oppure è possibile utilizzare una funzione anonima per il callback e utilizzare una variabile locale (locale per l'ambito padre, non la funzione anonima).
MV.

2
@MV Grazie, sì, per "riga per riga" intendevo "ogni intestazione". Ho modificato la mia risposta per chiarezza. Per ottenere l'intera sezione dell'intestazione (ovvero tutte le intestazioni), puoi anche utilizzare un metodo oggetto per il callback in modo che una proprietà dell'oggetto possa contenere tutte.
Skacc,

8
Questa è la migliore risposta IMO. Non causa problemi con più "\ r \ n \ r \ n" quando si usa CURLOPT_FOLLOWLOCATION e immagino che non sarà influenzato da ulteriori header dai proxy.
Rafał G.

Ha lavorato molto bene per me, puoi anche stackoverflow.com/questions/6482068/... in caso di problemi
RHH

1
Sì, questo è l'approccio migliore, tuttavia la risposta di @ Geoffrey rende questo più pulito utilizzando una funzione anonima senza la necessità di variabili globali e simili.
Simon East,

39

è questo che stai cercando?

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

8
Funziona normalmente tranne quando c'è un HTTP / 1.1 100 Continue seguito da una pausa, quindi HTTP / 1.1 200 OK. Vorrei andare con l'altro metodo.
ghostfly

1
Dai un'occhiata alla risposta selezionata di stackoverflow.com/questions/14459704/… prima di implementare qualcosa del genere. 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


Questo metodo fallisce anche sui reindirizzamenti 302 quando l'arricciatura è impostata per seguire l'intestazione della posizione.
Simon East,

10

Basta impostare le opzioni:

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

e usa curl_getinfo con CURLINFO_HTTP_CODE (o nessun parametro opt e avrai un array associativo con tutte le informazioni che vuoi)

Maggiori informazioni su: http://php.net/manual/fr/function.curl-getinfo.php


5
Questo non sembra restituirti le intestazioni di risposta. O almeno non c'è modo di recuperarli usando curl_getinfo().
Simon East,

8

Se si desidera specificamente l' Content-Typeopzione, è disponibile un'opzione speciale cURL per recuperarla:

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

L'OP ha chiesto se esiste un modo per recuperare le intestazioni, non un'intestazione specifica, questo non risponde alla domanda dell'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);

Funziona con HTTP/1.1 100 Continueprima di altre intestazioni.

Se hai bisogno di lavorare con server buggy che inviano solo LF invece di CRLF come interruzioni di riga, puoi utilizzare preg_splitcome segue:

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

Non dovrebbe $parts = explode("\r\n\r\nHTTP/", $response);avere il 3o parametro per esplodere come 2?
user4271704,

@ user4271704 No. Permette di trovare l'ultimo messaggio HTTP. HTTP/1.1 100 Continuepuò apparire più volte.
Enyby,

Ma dice qualcos'altro: stackoverflow.com/questions/9183178/… chi di voi ha ragione?
user4271704

HTTP/1.1 100 Continuepuò apparire più volte. Osserva il caso se appare solo una volta, ma nel caso comune è sbagliato. Ad esempio, il 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...suo codice non funziona correttamente
Enyby

1
La divisione su \ r \ n non è affidabile, alcuni server non sono conformi alle specifiche HTTP e invieranno solo un \ n. Lo standard RFC afferma che le applicazioni dovrebbero ignorare e dividere su \ n per la massima affidabilità.
Geoffrey,

1

La mia strada è

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

Se necessario, applicare un ciclo for e rimuovere il limite di esplosione.


1

Ecco il mio contributo al dibattito ... Questo restituisce un singolo array con i dati separati e le intestazioni elencate. Questo funziona sulla base del fatto che CURL restituirà i dati di un pezzo di intestazione [riga vuota]

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

Il problema con molte risposte qui è che "\r\n\r\n"può apparire legittimamente nel corpo dell'html, quindi non puoi essere sicuro di dividere correttamente le intestazioni.

Sembra che l'unico modo per memorizzare le intestazioni separatamente con una chiamata curl_execsia utilizzare una richiamata come suggerito sopra in https://stackoverflow.com/a/25118032/3326494

Quindi, per ottenere (in modo affidabile) solo il corpo della richiesta, è necessario passare il valore Content-Lengthdell'intestazione substr()come valore iniziale negativo.


1
Può apparire legittimamente, ma la tua risposta è errata. Content-Length non deve essere presente in una risposta HTTP. Il metodo corretto per analizzare manualmente le intestazioni è cercare la prima istanza di \ r \ n (o \ n \ n). Questo potrebbe essere fatto semplicemente limitando l'esplosione per restituire solo due elementi, ovvero: list($head, $body) = explode("\r\n\r\n", $response, 2);tuttavia CURL lo fa già per te se lo usicurl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
Geoffrey,

-1

Nel caso in cui non sia possibile / non utilizzare CURLOPT_HEADERFUNCTIONo altre soluzioni;

$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

Restituisci le intestazioni di risposta con un parametro di riferimento:

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

Sei sicuro che $rtn=explode("\r\n\r\nHTTP/", $rtn, 2);sia corretto? Il terzo parametro di esplodere non dovrebbe essere rimosso?
user4271704,

@ user4271704, il terzo parametro deve trattare l'intestazione "HTTP / 1.1 100 Continua \ r \ n \ r \ nHTTP / 1.1 200 OK ... \ r \ n \ r \ n ..."
diyism

Ma ha detto qualcos'altro: stackoverflow.com/questions/9183178/… chi di voi ha ragione?
user4271704

@ user4271704 anche il link a cui ti riferisci usa: explode("\r\n\r\n", $parts, 2); quindi entrambi hanno ragione.
Cyborg,

-5

Se non hai davvero bisogno di usare l'arricciatura;

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

Quali uscite

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

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


16
uhm, non hai nemmeno davvero bisogno di PHP, ma sembra che la domanda sia ...
Hans Z.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.