PHP cURL có thể truy xuất các tiêu đề phản hồi VÀ cơ thể trong một yêu cầu không?


314

Có cách nào để có được cả tiêu đề và nội dung cho yêu cầu cURL bằng PHP không? Tôi thấy rằng tùy chọn này:

curl_setopt($ch, CURLOPT_HEADER, true);

sẽ trả lại cơ thể cộng với các tiêu đề , nhưng sau đó tôi cần phân tích nó để có được cơ thể. Có cách nào để có được cả hai theo cách dễ sử dụng hơn (và an toàn) không?

Lưu ý rằng đối với "một yêu cầu", tôi có nghĩa là tránh đưa ra một yêu cầu CHÍNH trước khi NHẬN / POST.


3
Có một giải pháp tích hợp cho việc này, hãy xem câu trả lời sau: stackoverflow.com/a/25118032/1334485 (đã thêm nhận xét này 'vì bài đăng này vẫn nhận được nhiều lượt xem)
Skacc

Nhìn vào bình luận tốt đẹp này: safe.php.net/manual/en/book.curl.php#117138
user956584


Tôi đã nói rằng câu hỏi của tôi là một bản sao cho câu hỏi này. Nếu nó không phải là một bản sao, ai đó có thể vui lòng mở lại không? stackoverflow.com/questions/43770246/ Khắc Trong câu hỏi của tôi, tôi có một yêu cầu cụ thể là sử dụng một phương thức trả về một đối tượng với các tiêu đề và phần thân riêng biệt và không phải là một chuỗi.
1,21 gigawatt

Câu trả lời:


466

Một giải pháp cho vấn đề này đã được đăng trong các bình luận tài liệu PHP: http://www.php.net/manual/en/feft.curl-exec.php#80442

Mã ví dụ:

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

Cảnh báo: Như đã lưu ý trong các nhận xét bên dưới, điều này có thể không đáng tin cậy khi được sử dụng với máy chủ proxy hoặc khi xử lý một số loại chuyển hướng nhất định. Câu trả lời của @ Geoffrey có thể xử lý những điều này đáng tin cậy hơn.


22
Bạn cũng có thể list($header, $body) = explode("\r\n\r\n", $response, 2), nhưng việc này có thể lâu hơn một chút, tùy thuộc vào kích thước yêu cầu của bạn.
iblue

43
Đây là giải pháp tồi vì nếu bạn sử dụng máy chủ proxy và máy chủ proxy của mình (ví dụ: fiddler) thêm tiêu đề riêng để phản hồi - tiêu đề này đã phá vỡ tất cả các offset và bạn chỉ nên sử dụng list($header, $body) = explode("\r\n\r\n", $response, 2)như một biến thể hoạt động
mseach

5
@msangel Giải pháp của bạn không hoạt động khi có nhiều tiêu đề trong phản hồi, chẳng hạn như khi máy chủ thực hiện chuyển hướng 302. Bất kỳ đề xuất?
Nate

4
@Nate, vâng, tôi biết điều này. AFAIK, nhưng chỉ có một tiêu đề bổ sung có thể có - với mã 100(Tiếp tục). Đối với tiêu đề này, bạn có thể đi xung quanh với tùy chọn yêu cầu xác định chính xác : curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); , vô hiệu hóa việc gửi phản hồi tiêu đề này. Đối với 302, điều này không nên xảy ra, bởi vì tiêu đề 302 là chuyển hướng, nó không mong đợi cơ thể, tuy nhiên tôi biết, đôi khi các máy chủ gửi một số cơ thể có 302phản hồi, nhưng dù sao nó sẽ bị các trình duyệt bỏ qua, tại sao curl nên xử lý việc này? )
msangel

5
CURLOPT_VERBOSEđược dự định để xuất thông tin quá trình tới STDERR(có thể làm phiền trong CLI) và đối với vấn đề được thảo luận là vô ích.
hejdav

205

Nhiều giải pháp khác được cung cấp chủ đề này không làm điều này một cách chính xác.

  • Chia tách \r\n\r\nkhông đáng tin cậy khi CURLOPT_FOLLOWLOCATIONbật hoặc khi máy chủ phản hồi với mã 100.
  • Không phải tất cả các máy chủ đều tuân thủ tiêu chuẩn và chỉ truyền tải một \ndòng mới.
  • Việc phát hiện kích thước của các tiêu đề thông qua CURLINFO_HEADER_SIZEcũng không phải lúc nào cũng đáng tin cậy, đặc biệt là khi các proxy được sử dụng hoặc trong một số tình huống chuyển hướng tương tự.

Phương pháp đúng nhất là sử dụng CURLOPT_HEADERFUNCTION.

Đây là một phương pháp rất rõ ràng để thực hiện điều này bằng cách sử dụng các bao đóng PHP. Nó cũng chuyển đổi tất cả các tiêu đề thành chữ thường để xử lý nhất quán trên các máy chủ và phiên bản HTTP.

Phiên bản này sẽ giữ lại các tiêu đề trùng lặp

Điều này tuân thủ RFC822 và RFC2616, vui lòng không đề xuất các chỉnh sửa để sử dụng các mb_hàm chuỗi, không chính xác!

$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 đây là câu trả lời tốt nhất trong chủ đề này và khắc phục các sự cố với chuyển hướng xảy ra với các câu trả lời khác. Tốt nhất nên đọc tài liệu về CURLOPT_HEADERFUNCTION để hiểu cách thức hoạt động và các vấn đề tiềm năng. Tôi cũng đã thực hiện một số cải tiến cho câu trả lời để giúp đỡ những người khác.
Simon East

Tuyệt vời, tôi đã cập nhật câu trả lời để phục vụ cho các tiêu đề trùng lặp. Trong tương lai, không định dạng lại mã theo những gì bạn tin là như vậy. Điều này được viết theo cách để làm rõ ranh giới của hàm đóng.
Geoffrey

@Geoffrey $headers = [];php có hợp lệ không?
thealexbaron

6
@thealexbaron Có, kể từ phiên bản PHP 5.4, hãy xem: php.net/manual/en/migration54.new-features.php
Geoffrey

4
Câu trả lời này được đánh giá rất cao cho cách tiếp cận gọn gàng và tuân thủ RFC. Điều này nên được thực hiện câu trả lời dính và di chuyển lên đầu. Tôi chỉ muốn có một cách tiếp cận nhanh hơn để có được giá trị của một tiêu đề mong muốn thay vì phân tích tất cả các tiêu đề trước.
Fr0zenFyr

114

Curl có một tùy chọn tích hợp sẵn cho việc này, được gọi là CURLOPT_HEADERFUNCTION. Giá trị của tùy chọn này phải là tên của hàm gọi lại. Curl sẽ chuyển tiêu đề (và chỉ tiêu đề!) Cho chức năng gọi lại này, từng dòng một (vì vậy chức năng sẽ được gọi cho mỗi dòng tiêu đề, bắt đầu từ đầu phần tiêu đề). Hàm gọi lại của bạn sau đó có thể làm bất cứ điều gì với nó (và phải trả về số byte của dòng đã cho). Đây là một mã làm việc được thử nghiệm:

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

Ở trên cũng hoạt động với mọi thứ, các giao thức và proxy khác nhau và bạn không cần phải lo lắng về kích thước tiêu đề hoặc đặt nhiều tùy chọn cuộn tròn khác nhau.

PS: Để xử lý các dòng tiêu đề bằng một phương thức đối tượng, hãy làm điều này:

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

Lưu ý, chức năng gọi lại được gọi cho mỗi tiêu đề và có vẻ như chúng không được cắt bớt. Bạn có thể sử dụng biến toàn cục để giữ tất cả các tiêu đề hoặc bạn có thể sử dụng hàm ẩn danh cho cuộc gọi lại và sử dụng biến cục bộ (cục bộ cho phạm vi cha, không phải hàm ẩn danh).
MV.

2
@MV Cảm ơn, vâng, bởi "từng dòng" Tôi có nghĩa là "mỗi tiêu đề". Tôi chỉnh sửa câu trả lời của tôi cho rõ ràng. Để có được toàn bộ phần tiêu đề (còn gọi là tất cả các tiêu đề), bạn cũng có thể sử dụng một phương thức đối tượng cho cuộc gọi lại để một thuộc tính đối tượng có thể chứa tất cả chúng.
Skacc

8
Đây là câu trả lời tốt nhất IMO. Nó không gây ra vấn đề với nhiều "\ r \ n \ r \ n" khi sử dụng CURLOPT_FOLLOWLOCATION và tôi đoán nó sẽ không bị ảnh hưởng bởi các tiêu đề bổ sung từ proxy.
Rafał G.

Làm việc rất tốt cho tôi, cũng xem stackoverflow.com/questions/6482068/ cấp trong trường hợp có vấn đề
RHH

1
Có, đây là cách tiếp cận tốt nhất tuy nhiên câu trả lời của @ Geoffrey làm cho điều này rõ ràng hơn bằng cách sử dụng một hàm ẩn danh mà không cần các biến toàn cục và như vậy.
Simon East

39

đây là những gì bạn đang tìm kiếm?

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

8
Điều này hoạt động bình thường trừ khi có HTTP / 1.1 100 Tiếp tục theo sau là ngắt rồi HTTP / 1.1 200 OK. Tôi sẽ đi với phương pháp khác.
ghostfly

1
Hãy xem câu trả lời được chọn của stackoverflow.com/questions/14459704/ trên trước khi thực hiện một cái gì đó như thế này. w3.org/Prot Protocol / 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


Phương pháp này cũng thất bại trên 302 chuyển hướng khi curl được đặt để theo tiêu đề vị trí.
Simon East

10

Chỉ cần đặt tùy chọn:

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

và sử dụng curl_getinfo với CURLINEFO_HTTP_CODE (hoặc không có tham số opt và bạn sẽ có một mảng kết hợp với tất cả các thông tin bạn muốn)

Xem thêm tại: http://php.net/manual/fr/feft.curl-getinfo.php


5
Điều này dường như không trả lại tiêu đề phản hồi cho bạn. Hoặc ít nhất là không có cách nào để lấy chúng bằng cách sử dụng curl_getinfo().
Simon East

8

Nếu bạn đặc biệt muốn Content-Type, có một tùy chọn cURL đặc biệt để truy xuất nó:

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

OP đã hỏi liệu có cách nào để lấy lại các tiêu đề, không phải một tiêu đề cụ thể không, điều này không trả lời câu hỏi của 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);

Làm việc với HTTP/1.1 100 Continuetrước các tiêu đề khác.

Nếu bạn cần làm việc với các máy chủ có lỗi, chỉ gửi LF thay vì CRLF khi ngắt dòng, bạn có thể sử dụng preg_splitnhư sau:

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

Không nên $parts = explode("\r\n\r\nHTTP/", $response);có tham số thứ 3 để phát nổ là 2?
dùng4271704

@ user4271704 Không. Nó cho phép tìm tin nhắn HTTP cuối cùng. HTTP/1.1 100 ContinueCó thể xuất hiện nhiều lần.
Enyby

Nhưng anh ấy nói một cái gì đó khác: stackoverflow.com/questions/9183178/ trên đó là một trong những bạn là chính xác?
dùng4271704

HTTP/1.1 100 ContinueCó thể xuất hiện nhiều lần. Anh ta xem trường hợp nếu nó chỉ xuất hiện một lần, nhưng nó sai trong trường hợp phổ biến. Ví dụ: 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...mã của anh ta không hoạt động đúng
Enyby

1
Chia tách trên \ r \ n không đáng tin cậy, một số máy chủ không tuân thủ thông số kỹ thuật HTTP và sẽ chỉ gửi \ n. Tiêu chuẩn RFC tuyên bố rằng các ứng dụng nên bỏ qua \ r và phân tách trên \ n để có độ tin cậy cao nhất.
Geoffrey

1

Cách của tôi là

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

Nếu cần, áp dụng một vòng lặp for và loại bỏ giới hạn phát nổ.


1

Đây là đóng góp của tôi cho cuộc tranh luận ... Điều này trả về một mảng duy nhất với dữ liệu được phân tách và các tiêu đề được liệt kê. Điều này hoạt động trên cơ sở rằng CURL sẽ trả về dữ liệu chunk [dòng trống]

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

Vấn đề với nhiều câu trả lời ở đây là "\r\n\r\n"có thể xuất hiện hợp pháp trong phần thân của html, vì vậy bạn không thể chắc chắn rằng mình đang phân chia tiêu đề chính xác.

Có vẻ như cách duy nhất để lưu trữ các tiêu đề riêng biệt với một cuộc gọi đến curl_execlà sử dụng một cuộc gọi lại như được đề xuất ở trên trong https://stackoverflow.com/a/25118032/3326494

Và sau đó, (đáng tin cậy) chỉ nhận phần thân của yêu cầu, bạn sẽ cần chuyển giá trị của Content-Lengthtiêu đề thành substr()giá trị bắt đầu âm.


1
Nó có thể xuất hiện hợp pháp, nhưng câu trả lời của bạn là không chính xác. Độ dài nội dung không phải xuất hiện trong phản hồi HTTP. Phương pháp đúng để phân tích thủ công các tiêu đề là tìm phiên bản đầu tiên của \ r \ n (hoặc \ n \ n). Điều này có thể được thực hiện đơn giản bằng cách hạn chế phát nổ chỉ trả lại hai yếu tố, nghĩa là: list($head, $body) = explode("\r\n\r\n", $response, 2);tuy nhiên, CURL đã làm điều này cho bạn nếu bạn sử dụngcurl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
Geoffrey

-1

Chỉ trong trường hợp bạn không thể / không sử dụng CURLOPT_HEADERFUNCTIONhoặc các giải pháp khác;

$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

Trả về các tiêu đề phản hồi với tham số tham chiếu:

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

Bạn có chắc $rtn=explode("\r\n\r\nHTTP/", $rtn, 2);là đúng? Không nên loại bỏ tham số thứ 3 của vụ nổ?
dùng4271704

@ user4271704, thông số thứ 3 là để xử lý tiêu đề "HTTP / 1.1 100 Tiếp tục \ r \ n \ r \ nHTTP / 1.1 200 OK ... \ r \ n \ r \ n ..."
nghĩa tự làm

Nhưng anh còn nói một điều khác: stackoverflow.com/questions/9183178/ trên đó là một trong những bạn đúng?
dùng4271704

@ user4271704 liên kết bạn đang đề cập cũng sử dụng: explode("\r\n\r\n", $parts, 2); vì vậy cả hai đều đúng.
Cyborg

-5

Nếu bạn không thực sự cần sử dụng curl;

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

Đầu ra nào

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

Xem http://php.net/manual/en/reserved.variabled.httpresponseheader.php


16
uhm, bạn cũng không thực sự cần PHP, nhưng đó chỉ là câu hỏi về ...
Hans Z.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.