关于http:PHP cURL可以在单个请求中检索响应头和主体吗?

Can PHP cURL retrieve response headers AND body in a single request?

是否有任何方法可以使用PHP同时获取curl请求的头和体?我发现这个选项:

1
curl_setopt($ch, CURLOPT_HEADER, true);

将返回body加上headers,但是我需要解析它来获取body。有没有什么方法可以更有效(更安全)地实现这两个目标?

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


其中一个解决方案发布在php文档注释中:http://www.php.net/manual/en/function.curl exec.php_80442

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
$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);

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


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

  • CURLOPT_FOLLOWLOCATION打开或服务器用100代码响应时,在

    上拆分是不可靠的。

  • 并不是所有的服务器都符合标准,只传输一个用于新线路的
  • 通过CURLINFO_HEADER_SIZE检测头的大小也不总是可靠的,特别是在使用代理或在某些相同的重定向场景中。

最正确的方法是使用CURLOPT_HEADERFUNCTION

下面是一个非常干净的方法,可以使用PHP闭包来执行此操作。它还将所有头转换为小写,以便跨服务器和HTTP版本进行一致的处理。

此版本将保留重复的邮件头

这符合rfc822和rfc2616,请不要建议编辑以使用mb_字符串函数,这是不正确的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$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);


curl有一个内置选项,称为curlot_headerfunction。此选项的值必须是回调函数的名称。curl将通过收割台(仅限于收割台!)对于这个回调函数,一行一行地调用(这样函数将从头部分的顶部开始为每个头行调用)。然后回调函数可以对其执行任何操作(并且必须返回给定行的字节数)。下面是一个经过测试的工作代码:

1
2
3
4
5
6
7
8
9
10
11
function HandleHeaderLine( $curl, $header_line ) {
    echo"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选项。

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

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


这就是你想要的吗?

1
2
3
4
5
6
7
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch);
list($header, $body) = explode("



"
, $response, 2);


设置选项:

  • Curlopt_标题,0

  • 卷取返回传输,1

使用curlinfo和curlinfo代码(或者不使用opt-param,您将拥有一个包含所有所需信息的关联数组)

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


如果您特别想要Content-Type,有一个特殊的curl选项来检索它:

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);

$parts = explode("



HTTP/"
, $response);
$parts = (count($parts) > 1 ? 'HTTP/' : '').array_pop($parts);
list($headers, $body) = explode("



"
, $parts, 2);

在其他头文件之前使用HTTP/1.1 100 Continue

如果您需要使用只发送LF而不是CRLF作为换行符的buggy服务器,可以使用preg_split如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);

$parts = preg_split("@
?

?
HTTP/@u"
, $response);
$parts = (count($parts) > 1 ? 'HTTP/' : '').array_pop($parts);
list($headers, $body) = preg_split("@
?

?
@u"
, $parts, 2);


这是我对辩论的贡献…这将返回一个单独的数组,其中数据被分隔,头被列出。这是基于curl将返回头块[空行]数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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("
"
,$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;
    }
}


我的方法是

1
2
3
4
5
6
7
8
9
10
11
12
13
$response = curl_exec($ch);
$x = explode("



"
, $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循环并删除爆炸限制。


这里有许多答案的问题是,"

"可以合法地出现在HTML的主体中,因此您不能确定是否正确地拆分了头。

似乎只有一个调用curl_exec来单独存储头的方法是使用回调,如上面的https://stackoverflow.com/a/25118032/3326494所示。

然后为了(可靠地)得到请求的主体,您需要将Content-Length头的值作为负的起始值传递给substr()


当您需要从服务器返回的最后一个内容时,请小心。此代码可能会在等待实际(最后)头和正文时打破您的期望:list($headers, $body) = explode("

", $result, 2);。

下面是获取最终头部和身体部位的简单方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
$result = explode("



"
, $result);

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

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


返回带有引用参数的响应头:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?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("



HTTP/"
, $rtn, 2);    //to deal with"HTTP/1.1 100 Continue



HTTP/1.1 200 OK...



..." header
          $rtn=(count($rtn)>1 ? 'HTTP/' : '').array_pop($rtn);
          list($str_resp_headers, $rtn)=explode("




", $rtn, 2);

          $str_resp_headers=explode("


", $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;
         }
?>


如果你真的不需要使用卷发;

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

哪些输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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>
    Example Domain...

请参阅http://php.net/manual/en/reserved.variables.httpResponseHeader.php