PHP cURL可以在单个请求中检索响应标头和正文吗?


314

有什么方法可以使用PHP获取cURL请求的标头和正文?我发现这个选项:

curl_setopt($ch, CURLOPT_HEADER, true);

将返回正文和标头,但随后我需要对其进行解析以获取正文。有什么方法可以使两者更加实用(和安全)?

请注意,对于“单个请求”,我的意思是避免在GET / POST之前发出HEAD请求。


3
为此有一个内置的解决方案,请参见以下答案:stackoverflow.com/a/25118032/1334485(添加此评论“因为此帖子仍然获得很多意见)
Skacc 2015年



有人告诉我我的问题是该问题的重复。如果不是重复项,有人可以重新打开吗?stackoverflow.com/questions/43770246/…在我的问题中,我有一个具体的要求,即使用一种方法来返回标头和主体分开而不是一个字符串的对象。
1.21吉瓦

Answers:


466

一种解决方案发布在PHP文档注释中:http : //www.php.net/manual/en/function.curl-exec.php#80442

代码示例:

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

警告:如以下注释中所述,当与代理服务器一起使用或处理某些类型的重定向时,这可能并不可靠。@Geoffrey的答案可能会更可靠地处理这些问题。


22
您也可以list($header, $body) = explode("\r\n\r\n", $response, 2),但这可能需要更长的时间,具体取决于您的请求大小。
iblue 2012年

43
这是一个糟糕的解决方案,因为如果您使用代理服务器,并且您的代理服务器(例如,提琴手)添加了自己的标list($header, $body) = explode("\r\n\r\n", $response, 2)
头来

5
@msangel当响应中有多个标头时,例如服务器进行302重定向时,您的解决方案将不起作用。有什么建议么?
Nate 2014年

4
@Nate,是的,我知道这一点。AFAIK,但只有一个可能的附加标头-带有代码100(继续)。对于此标头,您可以正确定义请求选项:curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); ,禁用发送此标头响应。至于302,这不应该发生,因为302标头是重定向的,它不期望正文,但是我知道,有时服务器会发送带有302响应的正文,但是无论如何它会被浏览器忽略,到目前为止,为什么curl应该处理这个问题? )
msangel 2014年

5
CURLOPT_VERBOSE旨在将过程信息输出到STDERR(可能会在CLI中打扰),并且对于所讨论的问题没有用。
hejdav 2015年

205

此线程提供的许多其他解决方案均未正确执行此操作。

  • 开启\r\n\r\nCURLOPT_FOLLOWLOCATION或服务器响应100代码时,分割开启不可靠。
  • 并非所有服务器都符合标准,并且仅\n针对新产品进行传输。
  • 通过头检测标头的大小CURLINFO_HEADER_SIZE也不总是可靠的,尤其是当使用代理或在某些相同的重定向方案中时。

最正确的方法是使用CURLOPT_HEADERFUNCTION

这是使用PHP闭包执行此操作的一种非常干净的方法。它还将所有标头转换为小写,以便在服务器和HTTP版本之间进行一致的处理。

此版本将保留重复的标题

这符合RFC822和RFC2616,请不要建议使用mb_字符串函数进行编辑,这是不正确的!

$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,这是该线程中的最佳答案,并解决了其他答案发生的重定向问题。最好阅读有关CURLOPT_HEADERFUNCTION的文档,以了解其工作原理和潜在问题。我也对答案做了一些改进,以帮助他人。
西蒙东

太好了,我已经更新了答案,以满足重复的标题。将来不要将代码重新格式化为您认为应该的格式。编写此代码的目的是为了明确闭合函数边界在哪里。
Geoffrey

@Geoffrey是$headers = [];有效的php吗?
thealexbaron'7

6
@thealexbaron是的,自PHP 5.4起,请参见:php.net/manual/en/migration54.new-features.php
Geoffrey,

4
对于这种整洁且符合RFC的方法,此答案被大大低估了。应该将其设置为粘滞答案并移至顶部。我只希望有一种更快的方法来获取所需标头的值,而不是先解析所有标头。
Fr0zenFyr

114

Curl为此有一个内置选项,称为CURLOPT_HEADERFUNCTION。此选项的值必须是回调函数的名称。Curl会逐行将标头(仅标头!)传递给此回调函数(因此将从标头部分的顶部开始为每个标头行调用该函数)。然后,回调函数可以对其执行任何操作(并且必须返回给定行的字节数)。这是一个经过测试的工作代码:

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

上面的方法适用于所有内容,不同的协议和代理,并且您不必担心标题大小,也不必设置很多不同的curl选项。

PS:要使用对象方法处理标题行,请执行以下操作:

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

需要注意的是,每个标头都调用了回调函数,似乎它们没有被修剪。您可以使用全局变量来保存所有标头,也可以将匿名函数用于回调并使用局部变量(对于父作用域是局部变量,而不是匿名函数)。
MV。

2
@MV谢谢,是的,“逐行”是指“每个标题”。为了清楚起见,我编辑了答案。要获取整个标头部分(也就是所有标头),您还可以将对象方法用于回调,以便对象属性可以容纳所有标头。
Skacc

8
这是IMO的最佳答案。使用CURLOPT_FOLLOWLOCATION时,它不会导致多个“ \ r \ n \ r \ n”出现问题,并且我认为它不会受到代理的其他标头的影响。
拉斐尔G.15年

工作对我非常好,也看到stackoverflow.com/questions/6482068/...在出现问题时
RHH

1
是的,这是最好的方法,但是@Geoffrey的答案通过使用不需要全局变量等的匿名函数使此方法更清洁。
西蒙东

39

这是你在找什么?

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 Continue,然后先中断然后HTTP / 1.1 200 OK。我会选择其他方法。
ghostfly

1
在执行类似这样的操作之前,请先查看一下stackoverflow.com/questions/14459704/…的选定答案。 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


当将curl设置为跟随位置标头时,此方法在302重定向上也会失败。
西蒙东

10

只需设置选项:

  • CURLOPT_HEADER,0

  • CURLOPT_RETURNTRANSFER,1

并将curl_getinfo与CURLINFO_HTTP_CODE一起使用(或不使用opt参数,并且您将拥有一个与所需信息关联的数组)

更多信息:http : //php.net/manual/fr/function.curl-getinfo.php


5
这似乎根本没有将响应头返回给您。或至少没有办法使用来检索它们curl_getinfo()
西蒙东

8

如果您特别想要Content-Type,则有一个特殊的cURL选项可以检索它:

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

OP询问是否有一种方法可以检索标头,而不是一个特定的标头,但这不能回答OP的问题。
杰弗里

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 Continue其他头文件一起使用。

如果您需要使用越野车服务器,因为它们仅在换行时发送LF而不发送CRLF,则可以preg_split按以下方式使用:

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

不应$parts = explode("\r\n\r\nHTTP/", $response);将第三个参数爆炸为2吗?
user4271704

@ user4271704否。它允许找到最后一个HTTP消息。HTTP/1.1 100 Continue可以出现很多次
Enyby

但是他说了别的话:stackoverflow.com/questions/9183178/…你们哪个是正确的?
user4271704

HTTP/1.1 100 Continue可以出现很多次 他查看案例是否仅出现一次,但在通常情况下是错误的。例如,对于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...他的代码不能正常工作
Enyby

1
在\ r \ n上进行拆分是不可靠的,某些服务器不符合HTTP规范,而只会发送\ n。RFC标准规定应用程序应忽略\ r并在\ n上拆分以实现最大的可靠性。
Geoffrey

1

我的方式是

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

如果需要,请使用for循环并删除爆炸极限。


1

这是我对辩论的贡献...这将返回一个单独的数组,其中数据分开,并且标头列出。这是基于CURL将返回标题块[空白行]数据的基础上进行的

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

这里有很多答案的问题是,它们"\r\n\r\n"可以合法地出现在html的正文中,因此您不能确定自己正确地拆分了标头。

似乎唯一通过一次调用单独存储标头的方法curl_exec是使用回调,如上面https://stackoverflow.com/a/25118032/3326494中建议的那样

然后(要可靠地)仅获取请求的正文,您需要将Content-Length标头的值传递substr()为负起始值。


1
它可以合法显示,但您的答案不正确。HTTP响应中不必包含Content-Length。手动解析标头的正确方法是查找\ r \ n(或\ n \ n)的第一个实例。只需将explode限制为仅返回两个元素即可完成此操作,即:list($head, $body) = explode("\r\n\r\n", $response, 2);,但是如果您使用CURL,则已经为您完成了此操作curl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
Geoffrey

-1

以防万一您不能/不使用CURLOPT_HEADERFUNCTION或其他解决方案;

$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

返回带有参考参数的响应头:

<?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);正确吗?不应该删除爆炸的第三个参数吗?
user4271704

@ user4271704,第三个参数是处理“ HTTP / 1.1 100 Continue \ r \ n \ r \ nHTTP / 1.1 200 OK ... \ r \ n \ r \ n ...”标头
diyism

但是他说了别的话:stackoverflow.com/questions/9183178/…你们哪个是正确的?
user4271704

@ user4271704您所引用的链接也使用:explode("\r\n\r\n", $parts, 2); 所以两者都是正确的。
电子人

-5

如果您真的不需要使用curl;

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

哪些输出

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

参见http://php.net/manual/en/reserved.variables.httpresponseheader.php


16
嗯,您也不是真的需要PHP,但这恰好是问题所在……
Hans Z.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.