Может ли PHP cURL получить заголовки ответов и тело в одном запросе?
Есть ли способ получить оба заголовка и тело для запроса cURL с использованием PHP? Я обнаружил, что этот параметр:
curl_setopt($ch, CURLOPT_HEADER, true);
собирается вернуть теги плюс заголовки, но затем мне нужно разобрать его, чтобы получить тело. Есть ли способ получить как более удобный (и безопасный) способ?
Обратите внимание, что для "одиночного запроса" я имею в виду избегать выдачи запроса HEAD перед GET/POST.
Ответы
Ответ 1
Одно из решений этого было опубликовано в комментариях документации 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 может справиться с этим более надежно.
Ответ 2
Многие другие решения, предлагаемые этой веткой, делают это неправильно.
- Разделение на
\r\n\r\n
не является надежным, когда CURLOPT_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);
Ответ 3
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'))
Ответ 4
это то, что вы ищете?
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch);
list($header, $body) = explode("\r\n\r\n", $response, 2);
Ответ 5
Просто установите параметры:
и используйте curl_getinfo с CURLINFO_HTTP_CODE (или нет opt param, и у вас будет ассоциативный массив со всей необходимой информацией)
Подробнее: http://php.net/manual/fr/function.curl-getinfo.php
Ответ 6
Если вы специально хотите 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);
Ответ 7
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?\[email protected]", $parts, 2);
Ответ 8
Мой способ
$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 и удалите ограничение на разрыв.
Ответ 9
Вот мой вклад в дебаты... Это возвращает один массив с разделенными данными и заголовками в списке. Это работает на основе того, что 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;
}
}
Ответ 10
Проблема с множеством ответов здесь заключается в том, что "\r\n\r\n"
может законно отображаться в теле html, поэтому вы не можете быть уверены, что правильно разделяете заголовки.
Кажется, что единственный способ сохранить заголовки отдельно с одним вызовом curl_exec
- использовать обратный вызов, как предлагается выше в fooobar.com/questions/29489/...
И затем, чтобы (надежно) получить только тело запроса, вам нужно передать значение заголовка Content-Length
в substr()
как отрицательное начальное значение.
Ответ 11
Будьте осторожны, когда вам понадобятся последние материалы, возвращаемые с сервера. Этот код может прервать ваше ожидание, ожидая реальных (последних) заголовков и тела: 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;
Ответ 12
Вернуть ответные заголовки с опорным параметром:
<?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;
}
?>
Ответ 13
Если вам действительно не нужно использовать 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