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

时间:2012-02-07 20:13:03

标签: php http curl

有没有办法使用PHP获取cURL请求的标题和正文?我发现这个选项:

curl_setopt($ch, CURLOPT_HEADER, true);

将返回正文加标题,但之后我需要解析它以获取正文。有没有办法以更有用(和更安全)的方式获得两者?

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

15 个答案:

答案 0 :(得分:431)

对此的一个解决方案发布在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的答案可能会更可靠地处理这些问题。

答案 1 :(得分:134)

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

  • \r\n\r\n打开或服务器以100代码响应时,CURLOPT_FOLLOWLOCATION上的拆分不可靠。
  • 并非所有服务器都符合标准,并且仅为新线路传输\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;

    $name = strtolower(trim($header[0]));
    if (!array_key_exists($name, $headers))
      $headers[$name] = [trim($header[1])];
    else
      $headers[$name][] = trim($header[1]);

    return $len;
  }
);

$data = curl_exec($ch);
print_r($headers);

答案 2 :(得分:112)

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

以上内容适用于所有内容,不同的协议和代理,您不必担心标题大小,或设置许多不同的卷曲选项。

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

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

答案 3 :(得分:40)

这是你想要的吗?

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

答案 4 :(得分:10)

只需设置选项:

  • CURLOPT_HEADER,0

  • CURLOPT_RETURNTRANSFER,1

并使用curl_getinfo和CURLINFO_HTTP_CODE(或者没有选择参数,你将拥有一个包含你想要的所有信息的关联数组)

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

答案 5 :(得分: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);

答案 6 :(得分: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);

答案 7 :(得分: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循环并删除爆炸限制。

答案 8 :(得分: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;
    }
}

答案 9 :(得分:0)

这里有许多答案的问题是"\r\n\r\n"可以合法地出现在html的正文中,因此您无法确定是否正确地拆分了标题。

似乎只有一次调用curl_exec来单独存储标题的唯一方法是使用上面https://stackoverflow.com/a/25118032/3326494中建议的回调

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

答案 10 :(得分:0)

如果您使用 GET,请尝试此操作:

$curl = curl_init($url);

curl_setopt_array($curl, array(
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_HTTPHEADER => array(
        "Cache-Control: no-cache"
    ),
));

$response = curl_exec($curl);
curl_close($curl);

答案 11 :(得分:0)

更好的方法是使用详细的 CURL 响应,它可以通过管道传输到临时流。然后您可以在响应中搜索标头名称。这可能需要一些调整,但对我有用:

class genericCURL {
    /**
     * NB this is designed for getting data, or for posting JSON data
     */
    public function request($url, $method = 'GET', $data = array()) {
        $ch = curl_init();
        
        if($method == 'POST') {
            
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
            curl_setopt($ch, CURLOPT_POSTFIELDS, $string = json_encode($data));
            
        }

        
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_VERBOSE, true);
        
        //open a temporary stream to output the curl log, which would normally got to STDERR
        $err = fopen("php://temp", "w+");
        curl_setopt($ch, CURLOPT_STDERR, $err);
        

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $server_output = curl_exec ($ch);
        
        //rewind the temp stream and put it into a string   
        rewind($err);
        $this->curl_log = stream_get_contents($err);
        
        curl_close($ch);
        fclose($err);

    
        return $server_output;
        
    }
    
    /**
     * use the curl log to get a header value
     */
    public function getReturnHeaderValue($header) {
        $log = explode("\n", str_replace("\r\n", "\n", $this->curl_log));
        foreach($log as $line) {
            //is the requested header there
            if(stripos($line, '< ' . $header . ':') !== false) {
                $value = trim(substr($line, strlen($header) + 3));
                return $value;
            }
        }
        //still here implies not found so return false
        return false;
        
    }
}

答案 12 :(得分:-1)

当您需要从服务器返回的最后一些内容时要小心。在等待真实(最后)标题和正文时,此代码可能会破坏您的期望:list($headers, $body) = explode("\r\n\r\n", $result, 2);

这是获取最终标题的简单方法。身体部位;

$result = explode("\r\n\r\n", $result);

// drop redirect etc. headers
while (count($result) > 2) {
    array_shift($result);
}

// split headers / body parts
@ list($headers, $body) = $result;

答案 13 :(得分:-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;
         }
?>

答案 14 :(得分:-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