Bisakah PHP cURL mengambil tajuk respons DAN badan dalam satu permintaan?

314

Apakah ada cara untuk mendapatkan header dan isi untuk permintaan CURL menggunakan PHP? Saya menemukan bahwa opsi ini:

curl_setopt($ch, CURLOPT_HEADER, true);

akan mengembalikan tubuh plus header , tapi kemudian saya perlu menguraikannya untuk mendapatkan tubuh. Apakah ada cara untuk mendapatkan keduanya dengan cara yang lebih bermanfaat (dan aman)?

Perhatikan bahwa untuk "permintaan tunggal" yang saya maksud menghindari mengeluarkan permintaan HEAD sebelum GET / POST.

Gremo
sumber
3
Ada solusi
bawaan
Lihatlah komentar yang bagus ini: secure.php.net/manual/en/book.curl.php#117138
user956584
Saya diberi tahu bahwa pertanyaan saya adalah duplikat dari pertanyaan ini. Jika ini bukan duplikat, bisakah seseorang membuka kembali? stackoverflow.com/questions/43770246/... Dalam pertanyaan saya, saya memiliki persyaratan konkret untuk menggunakan metode yang mengembalikan objek dengan header dan tubuh terpisah dan bukan satu string.
1,21 gigawatt

Jawaban:

466

Salah satu solusi untuk ini diposting dalam komentar dokumentasi PHP: http://www.php.net/manual/en/function.curl-exec.php#80442

Contoh kode:

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

Peringatan: Seperti disebutkan dalam komentar di bawah, ini mungkin tidak dapat diandalkan saat digunakan dengan server proxy atau saat menangani jenis pengalihan tertentu. Jawaban @ Geoffrey dapat menangani ini dengan lebih andal.

iblue
sumber
22
Anda juga bisa list($header, $body) = explode("\r\n\r\n", $response, 2), tetapi ini mungkin butuh sedikit lebih lama, tergantung pada ukuran permintaan Anda.
iblue
43
ini adalah solusi buruk karena jika Anda menggunakan server proxy dan server proxy Anda (fiddler misalnya) tambahkan header sendiri ke respons - header ini mematahkan semua offset dan Anda harus menggunakan list($header, $body) = explode("\r\n\r\n", $response, 2)hanya varian yang berfungsi
msangel
5
@msangel Solusi Anda tidak berfungsi ketika ada beberapa header dalam respons, seperti ketika server melakukan redirect 302. Ada saran?
Nate
4
@Nate, ya, saya tahu ini. AFAIK, tetapi hanya ada satu kemungkinan tajuk tambahan - dengan kode 100(Lanjutkan). Untuk tajuk ini, Anda dapat berkeliling dengan menentukan opsi permintaan yang benar:, curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); menonaktifkan pengiriman respons tajuk ini. Adapun 302, ini seharusnya tidak terjadi, karena 302 header adalah redirect, itu tidak mengharapkan tubuh, namun saya tahu, kadang-kadang server mengirim beberapa tubuh dengan 302respons, tetapi tetap akan diabaikan oleh browser, sejauh ini, mengapa curl harus menangani ini? )
msangel
5
CURLOPT_VERBOSEdimaksudkan untuk menampilkan informasi proses ke STDERR(dapat mengganggu di CLI) dan untuk masalah yang dibahas tidak berguna.
hejdav
205

Banyak solusi lain yang ditawarkan utas ini tidak melakukan ini dengan benar.

  • Memecah \r\n\r\ntidak dapat diandalkan saat CURLOPT_FOLLOWLOCATIONdinyalakan atau ketika server merespons dengan kode 100.
  • Tidak semua server memenuhi standar dan mengirimkan hanya \nuntuk saluran baru.
  • Mendeteksi ukuran header melalui CURLINFO_HEADER_SIZEjuga tidak selalu dapat diandalkan, terutama ketika proksi digunakan atau dalam beberapa skenario pengalihan yang sama.

Metode yang paling benar adalah menggunakan CURLOPT_HEADERFUNCTION.

Berikut adalah metode yang sangat bersih untuk melakukan ini menggunakan penutupan PHP. Ini juga mengubah semua header menjadi huruf kecil untuk penanganan yang konsisten di seluruh server dan versi HTTP.

Versi ini akan mempertahankan header yang digandakan

Ini sesuai dengan RFC822 dan RFC2616, tolong jangan menyarankan pengeditan untuk menggunakan mb_fungsi string, itu salah!

$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
sumber
12
IMO ini adalah jawaban terbaik di utas ini dan memperbaiki masalah pengalihan yang terjadi dengan jawaban lain. Terbaik untuk membaca dokumentasi untuk CURLOPT_HEADERFUNCTION untuk memahami cara kerjanya dan potensi gotcha. Saya juga membuat beberapa perbaikan pada jawaban untuk membantu orang lain.
Simon East
Hebat, saya telah memperbarui jawaban untuk memenuhi tajuk ganda. Di masa depan jangan memformat ulang kode sesuai dengan yang Anda yakini. Ini ditulis dengan cara untuk memperjelas di mana batas fungsi penutupan berada.
Geoffrey
@ Geoffrey Apakah $headers = [];php valid?
thealexbar
6
@thealexbaron Ya pada PHP 5.4, lihat: php.net/manual/en/migration54.new-features.php
Geoffrey
4
Jawaban ini sangat diremehkan untuk pendekatan yang rapi dan sesuai RFC. Ini harus dijadikan jawaban yang lengket dan dipindahkan ke atas. Saya hanya berharap ada pendekatan yang lebih cepat untuk mendapatkan nilai dari header yang diinginkan daripada mem-parsing semua header terlebih dahulu.
Fr0zenFyr
114

Curl memiliki opsi bawaan untuk ini, disebut CURLOPT_HEADERFUNCTION. Nilai opsi ini haruslah nama fungsi panggilan balik. Curl akan meneruskan tajuk (dan hanya tajuk!) Ke fungsi panggilan balik ini, baris demi baris (sehingga fungsi akan dipanggil untuk setiap baris tajuk, mulai dari atas bagian tajuk). Fungsi panggilan balik Anda kemudian dapat melakukan apa saja dengan itu (dan harus mengembalikan jumlah byte dari baris yang diberikan). Berikut adalah kode kerja yang diuji:

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

Di atas berfungsi dengan segalanya, protokol dan proksi yang berbeda juga, dan Anda tidak perlu khawatir tentang ukuran header, atau mengatur banyak opsi ikal yang berbeda.

PS: Untuk menangani garis header dengan metode objek, lakukan ini:

curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$object, 'methodName'))
Skacc
sumber
Sebagai catatan, fungsi callback dipanggil untuk setiap header dan sepertinya tidak dipangkas. Anda dapat menggunakan variabel global untuk menampung semua tajuk atau Anda dapat menggunakan fungsi anonim untuk panggilan balik dan menggunakan variabel lokal (lokal untuk lingkup induk, bukan fungsi anonim).
MV.
2
@ MV Terima kasih, ya, dengan "baris demi baris" yang saya maksudkan "setiap header". Saya mengedit jawaban saya untuk kejelasan. Untuk mendapatkan seluruh bagian tajuk (alias. Semua tajuk), Anda juga dapat menggunakan metode objek untuk callback sehingga properti objek dapat menampung semuanya.
Skacc
8
Ini jawaban terbaik IMO. Itu tidak menyebabkan masalah dengan banyak "\ r \ n \ r \ n" saat menggunakan CURLOPT_FOLLOWLOCATION dan saya kira itu tidak akan terpengaruh oleh tajuk tambahan dari proksi.
Rafał G.
Bekerja dengan sangat baik bagi saya, juga lihat stackoverflow.com/questions/6482068/… jika terjadi masalah
RHH
1
Ya, ini adalah pendekatan terbaik namun jawaban @ Geoffrey membuat pembersih ini dengan menggunakan fungsi anonim tanpa perlu variabel global dan semacamnya.
Simon East
39

apakah ini yang Anda cari?

curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch); 
list($header, $body) = explode("\r\n\r\n", $response, 2);
pengguna1031143
sumber
8
Ini berfungsi normal kecuali ketika ada HTTP / 1.1 100 Lanjutkan diikuti dengan istirahat lalu HTTP / 1.1 200 OK. Saya akan menggunakan metode lain.
ghostfly
1
Lihatlah jawaban yang dipilih dari stackoverflow.com/questions/14459704/… sebelum mengimplementasikan sesuatu seperti ini. 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
Metode ini juga gagal pada 302 pengalihan ketika curl diatur untuk mengikuti header lokasi.
Simon East
10

Cukup atur opsi:

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

dan gunakan curl_getinfo dengan CURLINFO_HTTP_CODE (atau tanpa opt param dan Anda akan memiliki array asosiatif dengan semua informasi yang Anda inginkan)

Lebih lanjut di: http://php.net/manual/fr/function.curl-getinfo.php

Cyril H.
sumber
5
Ini sepertinya tidak mengembalikan header respons kepada Anda sama sekali. Atau setidaknya tidak ada cara untuk mengambilnya dengan menggunakan curl_getinfo().
Simon East
8

Jika Anda secara khusus menginginkannya Content-Type, ada opsi CURL khusus untuk mengambilnya:

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
pr1001
sumber
OP bertanya apakah ada cara untuk mengambil header, bukan satu header tertentu, ini tidak menjawab pertanyaan 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);

Bekerja dengan HTTP/1.1 100 Continuesebelum header lainnya.

Jika Anda perlu bekerja dengan server buggy yang hanya mengirim LF alih-alih CRLF sebagai jeda baris, Anda dapat menggunakan preg_splitsebagai berikut:

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
sumber
Tidakkah seharusnya $parts = explode("\r\n\r\nHTTP/", $response);memiliki parameter ke-3 untuk meledak sebagai 2?
user4271704
@ user4271704 Tidak. Ini memungkinkan menemukan pesan HTTP terakhir. HTTP/1.1 100 Continuedapat muncul berkali-kali.
Enyby
Tapi dia mengatakan sesuatu yang lain: stackoverflow.com/questions/9183178/… siapa di antara kalian yang benar?
user4271704
HTTP/1.1 100 Continuedapat muncul berkali-kali. Ia melihat case jika hanya muncul satu kali saja, tetapi itu salah dalam kasus yang umum. Misalnya untuk 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...kode nya tidak berfungsi dengan baik
Enyby
1
Membagi pada \ r \ n tidak dapat diandalkan, beberapa server tidak sesuai dengan spesifikasi HTTP dan hanya akan mengirim \ n. Standar RFC menyatakan bahwa aplikasi harus mengabaikan dan membagi \ n untuk keandalan terbaik.
Geoffrey
1

Cara saya adalah

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

Jika perlu terapkan for loop dan hapus batas meledak.

Roy
sumber
1

Inilah kontribusi saya untuk debat ... Ini mengembalikan satu array dengan data yang dipisahkan dan header terdaftar. Ini bekerja atas dasar bahwa CURL akan mengembalikan data [baris kosong] header

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

Masalah dengan banyak jawaban di sini adalah bahwa "\r\n\r\n"dapat muncul secara sah di badan html, sehingga Anda tidak dapat memastikan bahwa Anda membelah header dengan benar.

Tampaknya satu-satunya cara untuk menyimpan header secara terpisah dengan satu panggilan curl_exec adalah dengan menggunakan panggilan balik seperti yang disarankan di atas di https://stackoverflow.com/a/25118032/3326494

Dan kemudian untuk (andal) mendapatkan hanya tubuh permintaan, Anda harus meneruskan nilai Content-Lengthheader substr()sebagai nilai awal negatif.

mal
sumber
1
Itu dapat muncul secara sah, tetapi jawaban Anda salah. Content-Length tidak harus ada dalam respons HTTP. Metode yang benar untuk mengurai header secara manual adalah dengan mencari instance pertama dari \ r \ n (atau \ n \ n). Ini bisa dilakukan hanya dengan membatasi meledak untuk mengembalikan hanya dua elemen, yaitu list($head, $body) = explode("\r\n\r\n", $response, 2);:, namun CURL sudah melakukan ini untuk Anda jika Anda menggunakancurl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
Geoffrey
-1

Kalau-kalau Anda tidak bisa / tidak menggunakan CURLOPT_HEADERFUNCTIONatau solusi lain;

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

Kembalikan tajuk respons dengan parameter referensi:

<?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
sumber
Apakah Anda yakin $rtn=explode("\r\n\r\nHTTP/", $rtn, 2);itu benar? Bukankah seharusnya parameter ledakan ke-3 dihapus?
user4271704
@ user4271704, param ke-3 adalah untuk berurusan dengan "HTTP / 1.1 100 Lanjutkan \ r \ n \ r \ nHTTP / 1.1 200 OK ... header \ r \ n \ r \ n ..."
diyism
Tapi dia mengatakan sesuatu yang lain: stackoverflow.com/questions/9183178/… siapa di antara kalian yang benar?
user4271704
@ user4271704 tautan yang Anda rujuk juga gunakan: explode("\r\n\r\n", $parts, 2); jadi keduanya benar.
Cyborg
-5

Jika Anda tidak benar-benar perlu menggunakan ikal;

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

Output yang mana

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

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

Bevan
sumber
16
uhm, Anda juga tidak benar-benar membutuhkan PHP, tetapi itu adalah pertanyaannya ...
Hans Z.