PHP cURL tek bir istekte yanıt üstbilgilerini VE gövdesini alabilir mi?


314

PHP kullanarak bir cURL isteği için üstbilgi ve gövde almak için herhangi bir yolu var mı? Bu seçeneği buldum:

curl_setopt($ch, CURLOPT_HEADER, true);

vücudu ve başlıkları döndürecek , ama sonra vücudu almak için ayrıştırmam gerekiyor. Her ikisini de daha kullanışlı (ve güvenli) bir şekilde almanın bir yolu var mı?

"Tekli istek" için GET / POST öncesinde bir HEAD isteği vermekten kaçınmak istediğimi unutmayın.


3
Bunun için yerleşik bir çözüm var, şu cevaba bakın: stackoverflow.com/a/25118032/1334485 (bu yorumu ekledi 'çünkü bu yazı hala birçok görüntüleme alıyor)
Skacc



Sorumun bu sorunun bir kopyası olduğu söylendi. Eğer bir kopya değilse, lütfen tekrar açabilir misiniz? stackoverflow.com/questions/43770246/… Benim sorum, bir dize değil, üstbilgi ve gövde ayrı bir nesne döndüren bir yöntem kullanmak için somut bir gereksinim var.
1,21 gigawatts

Yanıtlar:


466

Bunun bir çözümü PHP dokümantasyon yorumlarında yayınlanmıştır: http://www.php.net/manual/en/function.curl-exec.php#80442

Kod örneği:

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

Uyarı: Aşağıdaki yorumlarda belirtildiği gibi, proxy sunucularla kullanıldığında veya belirli yönlendirme türlerini işlerken bu güvenilir olmayabilir. @ Geoffrey'in cevabı bunları daha güvenilir bir şekilde ele alabilir.


22
Ayrıca list($header, $body) = explode("\r\n\r\n", $response, 2), istek boyutunuza bağlı olarak bu işlem biraz daha uzun sürebilir.
iblue

43
Bu kötü bir çözüm olduğu için proxy sunucusu ve proxy sunucusu (örneğin kemancı) yanıta kendi başlıklarını eklemek kullanırsanız - bu başlıkları tüm uzaklıklar kırdı ve kullanmak gerekir list($header, $body) = explode("\r\n\r\n", $response, 2)tek çalışan varyantı olarak
msangel

5
@msangel Yanıtınızda, sunucunun 302 yönlendirmesi yaptığı gibi birden çok başlık olduğunda çözümünüz çalışmaz. Herhangi bir öneri?
Nate

4
@Nate, evet, bunu biliyorum. AFAIK, ancak yalnızca bir olası ek başlık var - kodla 100(Devam). Bu üstbilgi için istek üstbilgisi doğru bir şekilde tanımlanabilir: curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); bu üstbilgi yanıtını göndermeyi devre dışı bırakabilirsiniz . Gelince 302, bu olmamalı, çünkü 302 üstbilgi yönlendiriyor, vücut beklemiyor, ancak biliyorum, bazen sunucular 302yanıtla bazı vücut gönderir , ancak yine de tarayıcılar tarafından göz ardı edilir, şimdiye kadar neden kıvırmak bunu ele almalı? )
msangel

5
CURLOPT_VERBOSEişlem bilgilerinin STDERR(CLI'de rahatsız edici olabilir) çıktısı için tasarlanmıştır ve tartışılan sorun işe yaramaz.
hejdav

205

Konuyu sunulan diğer çözümlerin birçoğu edilir değil doğru bunu.

  • Açıkken veya sunucu 100 koduyla yanıt verdiğinde bölme işlemi \r\n\r\ngüvenilir değildir CURLOPT_FOLLOWLOCATION.
  • Tüm sunucular standartlarla uyumlu değildir ve sadece \nyeni hatlar için bir iletim yapar .
  • Başlıkların büyüklüğünün algılanması CURLINFO_HEADER_SIZEda, özellikle proxy'ler kullanıldığında veya aynı yeniden yönlendirme senaryolarının bazılarında her zaman güvenilir değildir.

En doğru yöntem kullanmaktır CURLOPT_HEADERFUNCTION.

İşte PHP kapaklarını kullanarak bunu gerçekleştirmek için çok temiz bir yöntem. Ayrıca sunucular ve HTTP sürümleri arasında tutarlı işlem için tüm başlıkları küçük harfe dönüştürür.

Bu sürüm yinelenen başlıkları koruyacak

Bu RFC822 ve RFC2616 ile uyumludur, lütfen mb_dize işlevlerini kullanmak için düzenlemeler önermeyin , yanlış!

$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 bu konudaki en iyi cevaptır ve diğer cevaplarda meydana gelen yönlendirmelerle ilgili sorunları giderir. Nasıl çalıştığını ve potansiyel yakalamaları anlamak için CURLOPT_HEADERFUNCTION belgelerini okumak en iyisidir. Ayrıca başkalarına yardım etmek için cevapta bazı iyileştirmeler yaptım.
Simon East

Harika, yinelenen üstbilgileri karşılamak için cevap güncelledik. Gelecekte kodu olması gerektiğine inandığınız şekilde yeniden biçimlendirmeyin. Bu, kapatma işlevi sınırlarının nerede olduğunu netleştirecek şekilde yazılmıştır.
Geoffrey

@Geoffrey $headers = [];Geçerli bir php mi?
thealexbaron

6
@ thealexbaron Evet PHP 5.4'ten itibaren, bakınız: php.net/manual/en/migration54.new-features.php
Geoffrey

4
Bu cevabın, böylesine düzgün ve RFC uyumlu bir yaklaşım için çok önemsiz olduğu anlaşılmaktadır. Bu yapışkan bir cevap yapılmalı ve en üste taşınmalıdır. Keşke, önce tüm başlıkları ayrıştırmak yerine istenen bir başlığın değerini elde etmek için daha hızlı bir yaklaşım olsaydı.
09

114

Curl, bunun için CURLOPT_HEADERFUNCTION adlı yerleşik bir seçeneğe sahiptir. Bu seçeneğin değeri, geri arama işlevinin adı olmalıdır. Curl, üstbilgiyi (ve yalnızca üstbilgi!) Bu geri arama işlevine satır satır geçirir (böylece üstbilgi bölümünün üstünden başlayarak her üstbilgi satırı için işlev çağrılır). Geri arama işleviniz daha sonra onunla her şeyi yapabilir (ve verilen satırın bayt sayısını döndürmelidir). İşte test edilmiş bir çalışma kodu:

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

Yukarıdakiler her şeyle, farklı protokollerle ve proxy'lerle çalışır ve başlık boyutu hakkında endişelenmenize veya birçok farklı kıvırma seçeneği belirlemenize gerek yoktur.

Not: Başlık satırlarını bir nesne yöntemiyle işlemek için şunu yapın:

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

Not olarak, her başlık için geri arama işlevi çağrılır ve kırpılmadığı anlaşılır. Tüm üstbilgileri tutmak için genel bir değişken kullanabilir veya geri arama için anonim bir işlev kullanabilir ve yerel bir değişken kullanabilirsiniz (anonim işlev için değil, ana kapsam için yerel).
MV.

2
@ MV Teşekkürler, evet, "satır satır" ile "Her başlık" demek istedim. Cevabımı netlik için düzenledim. Üstbilgi bölümünün tamamını almak için (diğer bir deyişle tüm üstbilgiler), bir nesne özelliğinin tümünü tutabilmesi için geri arama için bir nesne yöntemi de kullanabilirsiniz.
Skacc

8
Bu en iyi cevap IMO. CURLOPT_FOLLOWLOCATION kullanırken birden fazla "\ r \ n \ r \ n" ile ilgili sorunlara neden olmaz ve sanırım proxy'lerden gelen ek başlıklardan etkilenmeyecektir.
Rafał G.

Benim için çok iyi çalıştı, ayrıca sorunlar durumunda stackoverflow.com/questions/6482068/…
RHH

1
Evet, bu en iyi yaklaşımdır ancak @ Geoffrey'nin cevabı, küresel değişkenlere ve benzerlerine ihtiyaç duymadan anonim bir işlev kullanarak bunu daha temiz hale getirir.
Simon East

39

Aradığın şey bu mu?

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

8
HTTP / 1.1 100 Devam ve ardından bir mola ve ardından HTTP / 1.1 200 Tamam dışında normal çalışır. Diğer yöntemle giderdim.
ghostfly

1
Bunun gibi bir şey uygulamadan önce stackoverflow.com/questions/14459704/… seçilen cevabına bir göz atın . 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


Bu yöntem, curl konum başlığını izleyecek şekilde ayarlandığında 302 yönlendirmelerinde de başarısız olur.
Simon East

10

Sadece seçenekleri ayarlayın:

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

ve curl_getinfo'yu CURLINFO_HTTP_CODE ile kullanın (veya opt param yok ve istediğiniz tüm bilgileri içeren ilişkilendirilebilir bir diziniz olacak)

Daha fazla bilgi için: http://php.net/manual/fr/function.curl-getinfo.php


5
Bu, yanıt başlıklarını size hiç döndürmüyor gibi görünüyor. Ya da en azından bunları kullanarak almanın bir yolu yok curl_getinfo().
Simon East

8

Özellikle istiyorsanız, Content-Typealmak için özel bir cURL seçeneği vardır:

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

OP, başlıkları almanın bir yolu olup olmadığını sordu, belirli bir başlık değil, bu OP'nin sorusuna cevap vermiyor.
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);

HTTP/1.1 100 ContinueDiğer başlıklardan önce ile çalışır .

Satır kesmeleri olarak CRLF yerine yalnızca LF gönderen buggy sunucularıyla çalışmanız gerekiyorsa preg_splitaşağıdaki gibi kullanabilirsiniz :

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

Patlama $parts = explode("\r\n\r\nHTTP/", $response);için 3. parametrenin 2 olması gerekmez mi?
user4271704

@ user4271704 Hayır. Son HTTP iletisinin bulunmasına izin verir. HTTP/1.1 100 Continuebirçok kez görünebilir.
Enyby

Ama başka bir şey söylüyor: stackoverflow.com/questions/9183178/… hanginiz doğru?
user4271704

HTTP/1.1 100 Continuebirçok kez görünebilir. Davayı sadece bir kez ortaya çıkarsa görüyor, ancak ortak durumda yanlış. Örneğin 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...kodu için düzgün çalışmıyor
Enyby

1
\ R \ n üzerinde bölme işlemi güvenilir değildir, bazı sunucular HTTP spesifikasyonlarına uymaz ve sadece bir \ n gönderir. RFC standardı, uygulamaların en yüksek güvenilirlik için yok sayılması ve ayrılması \ n gerektiğini belirtir.
Geoffrey

1

Benim yolum

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

Gerekirse bir for döngüsü uygulayın ve patlama sınırını kaldırın.


1

İşte tartışmaya katkım ... Bu, veri ayrılmış ve başlıklar listelenmiş tek bir dizi döndürür. Bu, CURL'nin bir başlık yığın [boş satır] verisi döndürmesi temelinde çalışır

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

Burada birçok cevapla ilgili sorun "\r\n\r\n", yasal olarak html'nin gövdesinde görünebilmesidir, bu nedenle üstbilgileri doğru bir şekilde böldüğünüzden emin olamazsınız.

Başlıkları tek bir çağrı ile ayrı olarak depolamanın tek yolu, curl_execyukarıda https://stackoverflow.com/a/25118032/3326494 içinde önerildiği gibi bir geri arama kullanmak gibi görünüyor.

Ve daha sonra (güvenilir bir şekilde) isteğin gövdesini almak için, Content-Lengthüstbilginin substr()değerini negatif bir başlangıç ​​değeri olarak geçirmeniz gerekir .


1
Yasal olarak görünebilir, ancak cevabınız yanlış. İçerik uzunluğunun bir HTTP yanıtında bulunması gerekmez. Üstbilgileri el ile ayrıştırmak için doğru yöntem ilk \ r \ n (veya \ n \ n) örneğini aramaktır. Bu sadece patlamayı sadece iki öğe döndürmekle sınırlayarak yapılabilir, yani: list($head, $body) = explode("\r\n\r\n", $response, 2);CURL bunu kullanırsanız zaten sizin için yaparcurl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
Geoffrey

-1

CURLOPT_HEADERFUNCTIONBaşka çözümler kullanamamanız / kullanmamanız durumunda ;

$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

Referans parametresi ile yanıt yanıt başlıkları:

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

$rtn=explode("\r\n\r\nHTTP/", $rtn, 2);Doğru olduğundan emin misin ? Patlamanın 3. parametresi kaldırılmamalı mı?
user4271704

@ user4271704, 3 param "HTTP / 1.1 100 \ r Devam \ n \ r \ nhttp / 1.1 200 OK ... \ r \ n \ r \ n ..." başlığı ile uğraşmak olduğunu
diyism

Ama başka bir şey söyledi: stackoverflow.com/questions/9183178/… hanginiz doğru?
user4271704

@ user4271704 atıfta bulunduğunuz bağlantıyı da kullanın: explode("\r\n\r\n", $parts, 2); yani her ikisi de doğru.
Cyborg

-5

Gerçekten curl kullanmanız gerekmiyorsa;

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

Hangi çıktılar

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

Bkz. Http://php.net/manual/en/reserved.variables.httpresponseheader.php


16
uhm, gerçekten PHP'ye de ihtiyacınız yok, ama sorunun nedeni bu ...
Hans Z.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.