O PHP cURL pode recuperar cabeçalhos de resposta E corpo em uma única solicitação?

314

Existe alguma maneira de obter cabeçalhos e corpo para uma solicitação cURL usando PHP? Eu achei que esta opção:

curl_setopt($ch, CURLOPT_HEADER, true);

vai retornar o corpo mais os cabeçalhos , mas preciso analisá-lo para obter o corpo. Existe alguma maneira de obter os dois de uma maneira mais utilizável (e segura)?

Observe que, para "solicitação única", evito emitir uma solicitação HEAD antes do GET / POST.

gremo
fonte
3
Há um construído em solução para isso, veja esta resposta: stackoverflow.com/a/25118032/1334485 (adicionado este comentário 'coz este post ainda recebe muitas visitas)
Skacc
Veja este belo comentário: secure.php.net/manual/en/book.curl.php#117138
user956584
Disseram-me que minha pergunta era uma duplicata para esta pergunta. Se não for uma duplicata, alguém pode reabri-la? stackoverflow.com/questions/43770246/… Na minha pergunta, tenho um requisito concreto de usar um método que retorne um objeto com cabeçalhos e corpo separados e não uma string.
1,21 gigawatts

Respostas:

466

Uma solução para isso foi publicada nos comentários da documentação do PHP: http://www.php.net/manual/en/function.curl-exec.php#80442

Exemplo de código:

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

Aviso: Conforme observado nos comentários abaixo, isso pode não ser confiável quando usado com servidores proxy ou ao lidar com certos tipos de redirecionamentos. A resposta de @ Geoffrey pode lidar com isso de maneira mais confiável.

iblue
fonte
22
Você também pode list($header, $body) = explode("\r\n\r\n", $response, 2), mas isso pode demorar um pouco mais, dependendo do tamanho da sua solicitação.
iBlue
43
isso é ruim solução, porque se você usar o servidor proxy e o servidor proxy (violinista por exemplo) adicionar próprios cabeçalhos de resposta - isto cabeçalhos quebrou todos os deslocamentos e você deve usar list($header, $body) = explode("\r\n\r\n", $response, 2)como única variante trabalhando
msangel
5
@msangel Sua solução não funciona quando há vários cabeçalhos na resposta, como quando o servidor faz um redirecionamento 302. Alguma sugestão?
Nate
4
@ Nate, sim, eu sei disso. AFAIK, mas existe apenas um cabeçalho adicional possível - com o código 100(Continuar). Para este cabeçalho, você pode definir a opção de solicitação corretamente: curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); desativando o envio desta resposta de cabeçalho. Quanto a 302isso, isso não deve acontecer, porque o cabeçalho 302 é redirecionado, ele não espera um corpo, por mais que eu saiba, às vezes os servidores enviam algum corpo com 302resposta, mas ele será ignorado pelos navegadores, até agora, por que o curl deve lidar com isso? )
msangel
5
CURLOPT_VERBOSEdestina-se a enviar informações de processo para STDERR(pode incomodar na CLI) e, para o problema discutido, é inútil.
hejdav
205

Muitas das outras soluções oferecidas nesse segmento não estão fazendo isso corretamente.

  • A divisão \r\n\r\nnão é confiável quando CURLOPT_FOLLOWLOCATIONestá ativada ou quando o servidor responde com um código 100.
  • Nem todos os servidores são compatíveis com os padrões e transmitem apenas um \npara novas linhas.
  • A detecção do tamanho dos cabeçalhos CURLINFO_HEADER_SIZEtambém nem sempre é confiável, especialmente quando proxies são usados ​​ou em alguns dos mesmos cenários de redirecionamento.

O método mais correto está usando CURLOPT_HEADERFUNCTION.

Aqui está um método muito limpo de executar isso usando fechamentos PHP. Ele também converte todos os cabeçalhos em minúsculas para manipulação consistente entre servidores e versões HTTP.

Esta versão manterá cabeçalhos duplicados

Está em conformidade com RFC822 e RFC2616, por favor não sugerir edições para fazer uso das mb_funções de string, pois está incorreto!

$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
fonte
12
IMO, esta é a melhor resposta neste segmento e corrige problemas com redirecionamentos que ocorreram com outras respostas. É melhor ler a documentação do CURLOPT_HEADERFUNCTION para entender como funciona e possíveis dicas. Também fiz algumas melhorias na resposta para ajudar outras pessoas.
Simon Médio
Ótimo, atualizei a resposta para atender a cabeçalhos duplicados. No futuro, não re-formate o código para o que você acredita que deveria ser. Isso é escrito de maneira a deixar claro onde estão os limites da função de fechamento.
Geoffrey
@ Geoffrey O $headers = [];php é válido?
thealexbaron
6
@thealexbaron Sim, é a partir do PHP 5.4, consulte: php.net/manual/en/migration54.new-features.php
Geoffrey
4
Esta resposta é altamente subestimada para uma abordagem tão elegante e compatível com RFC. Isso deve ser uma resposta pegajosa e movido para o topo. Eu só queria que houvesse uma abordagem mais rápida para obter o valor de um cabeçalho desejado em vez de analisar todos os cabeçalhos primeiro.
Fr0zenFyr
114

O Curl possui uma opção integrada para isso, chamada CURLOPT_HEADERFUNCTION. O valor dessa opção deve ser o nome de uma função de retorno de chamada. A ondulação passará o cabeçalho (e apenas o cabeçalho!) Para essa função de retorno de chamada, linha por linha (para que a função seja chamada para cada linha de cabeçalho, começando na parte superior da seção do cabeçalho). Sua função de retorno de chamada pode fazer qualquer coisa com ela (e deve retornar o número de bytes da linha especificada). Aqui está um código de trabalho testado:

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

O acima funciona com tudo, protocolos e proxies diferentes também, e você não precisa se preocupar com o tamanho do cabeçalho ou definir muitas opções de ondulação diferentes.

PS: Para manipular as linhas de cabeçalho com um método de objeto, faça o seguinte:

curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$object, 'methodName'))
Skacc
fonte
Como observação, a função de retorno de chamada é chamada para cada cabeçalho e parece que eles não são cortados. Você pode usar uma variável global para manter todos os cabeçalhos ou usar uma função anônima para o retorno de chamada e usar uma variável local (local para o escopo pai, não a função anônima).
MV.
2
@MV Obrigado, sim, por "linha por linha" eu quis dizer "cada cabeçalho". Eu editei minha resposta para maior clareza. Para obter a seção inteira do cabeçalho (também conhecida como todos os cabeçalhos), você também pode usar um método de objeto para o retorno de chamada, para que uma propriedade do objeto possa conter todos eles.
Skacc
8
Esta é a melhor resposta IMO. Não causa problemas com vários "\ r \ n \ r \ n" ao usar CURLOPT_FOLLOWLOCATION e acho que não será afetado por cabeçalhos adicionais de proxies.
Rafał G.
Funcionou muito bem para mim, ver também stackoverflow.com/questions/6482068/... em caso de problemas
RHH
1
Sim, esta é a melhor abordagem, no entanto, a resposta de @ Geoffrey torna isso mais limpo usando uma função anônima sem a necessidade de variáveis ​​globais e tal.
Simon East
39

é isso que você está procurando?

curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch); 
list($header, $body) = explode("\r\n\r\n", $response, 2);
user1031143
fonte
8
Isso funciona normalmente, exceto quando há um HTTP / 1.1 100 Continue seguido de uma pausa e, em seguida, HTTP / 1.1 200 OK. Eu iria com o outro método.
Ghostfly 23/05
1
Dê uma olhada na resposta selecionada de stackoverflow.com/questions/14459704/… antes de implementar algo como isto. 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
Esse método também falha nos redirecionamentos 302 quando a curvatura é configurada para seguir o cabeçalho do local.
Simon East
10

Basta definir opções:

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

e use curl_getinfo com CURLINFO_HTTP_CODE (ou nenhum parâmetro de opção e você terá um array associativo com todas as informações que desejar)

Mais em: http://php.net/manual/fr/function.curl-getinfo.php

Cyril H.
fonte
5
Isso não parece retornar os cabeçalhos de resposta para você. Ou pelo menos não há como recuperá-los usando curl_getinfo().
Simon Médio
8

Se você deseja especificamente Content-Type, há uma opção especial de cURL para recuperá-lo:

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
pr1001
fonte
O OP perguntou se existe uma maneira de recuperar os cabeçalhos, e não um cabeçalho específico, isso não responde à pergunta do OP.
91319 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);

Funciona com HTTP/1.1 100 Continueantes de outros cabeçalhos.

Se você precisar trabalhar com servidores com bugs, que enviam apenas LF em vez de CRLF como quebras de linha, você pode usar preg_splito seguinte:

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
fonte
Não deveria $parts = explode("\r\n\r\nHTTP/", $response);ter o terceiro parâmetro para explodir como 2?
user4271704
@ user4271704 Não. Permite localizar a última mensagem HTTP. HTTP/1.1 100 Continuepode aparecer muitas vezes.
usar o seguinte comando
Mas ele diz outra coisa: stackoverflow.com/questions/9183178/… qual de vocês está correto?
User4271704
HTTP/1.1 100 Continuepode aparecer muitas vezes. Ele vê o caso se aparecer apenas uma vez, mas está errado no caso comum. Por exemplo, para 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...o seu código não funcionam corretamente
Enyby
1
A divisão em \ r \ n não é confiável, alguns servidores não estão em conformidade com as especificações HTTP e apenas enviarão um \ n. O padrão RFC afirma que os aplicativos devem ignorar \ re dividir \ n para obter maior confiabilidade.
Geoffrey
1

Meu jeito é

$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 necessário, aplique um loop for e remova o limite de explosão.

Roy
fonte
1

Aqui está minha contribuição para o debate ... Isso retorna uma única matriz com os dados separados e os cabeçalhos listados. Isso funciona com base no fato de que CURL retornará dados de um pedaço de cabeçalho [linha em branco]

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
fonte
0

O problema com muitas respostas aqui é que "\r\n\r\n"elas podem aparecer legitimamente no corpo do html; portanto, você não pode ter certeza de que está dividindo os cabeçalhos corretamente.

Parece que a única maneira de armazenar cabeçalhos separadamente com uma chamada curl_execé usar um retorno de chamada, conforme sugerido acima em https://stackoverflow.com/a/25118032/3326494

E, para (de maneira confiável) obter apenas o corpo da solicitação, você precisará passar o valor do Content-Lengthcabeçalho para substr()um valor inicial negativo.

mal
fonte
1
Pode aparecer legitimamente, mas sua resposta está incorreta. O comprimento do conteúdo não precisa estar presente em uma resposta HTTP. O método correto para analisar manualmente os cabeçalhos é procurar a primeira instância de \ r \ n (ou \ n \ n). Isso pode ser feito simplesmente por limitar explodir para retornar apenas dois elementos, ou seja: list($head, $body) = explode("\r\n\r\n", $response, 2);, no entanto CURL já faz isso para você se você usarcurl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
Geoffrey
-1

Caso você não possa / não use CURLOPT_HEADERFUNCTIONou outras soluções;

$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
fonte
-2

Retorne os cabeçalhos de resposta com um parâmetro de referência:

<?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;
         }
?>
diyism
fonte
Tem certeza de que $rtn=explode("\r\n\r\nHTTP/", $rtn, 2);está correto? O terceiro parâmetro de explosão não deve ser removido?
User4271704
@ user4271704, o terceiro parâmetro é lidar com o cabeçalho "HTTP / 1.1 100 Continue \ r \ n \ r \ nHTTP / 1.1 200 OK ... \ r \ n \ r \ n ..."
diyism
Mas ele disse outra coisa: stackoverflow.com/questions/9183178/… qual de vocês está correto?
User4271704
@ user4271704 o link ao qual você está se referindo também usa: explode("\r\n\r\n", $parts, 2); portanto, ambos estão certos.
Cyborg
-5

Se você realmente não precisa usar curl;

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

Quais saídas

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

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

Bevan
fonte
16
uhm, você também não precisa realmente de PHP, mas isso é exatamente o que a pergunta é ... #
9111 Hans Z.