Может ли PHP cURL получить заголовки ответа и тело в одном запросе?
Есть ли способ получить заголовки и тело для запроса cURL с использованием PHP? Я обнаружил, что этот вариант:
curl_setopt($ch, CURLOPT_HEADER, true);
собирается вернуть тело плюс заголовки, но затем мне нужно разобрать его, чтобы получить тело. Есть ли способ получить как более удобный (и безопасный) способ?
Обратите внимание, что для "одного запроса" я имею в виду избегать выдачи запроса HEAD до GET/POST.
16 ответов
Одно из решений было опубликовано в комментариях к документации PHP: http://www.php.net/manual/en/function.curl-exec.php
Пример кода:
$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);
Предупреждение: как отмечено в комментариях ниже, это может быть ненадежно при использовании с прокси-серверами или при обработке определенных типов перенаправлений. Ответ @ Джеффри может обработать их более надежно.
Многие другие решения, предлагаемые этой веткой, делают это неправильно.
- Расщепление на
\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;
$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 есть встроенная опция для этого, которая называется 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'))
Это то, что вы ищете?
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch);
list($header, $body) = explode("\r\n\r\n", $response, 2);
Если вы специально хотите 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);
Просто установите параметры:
CURLOPT_HEADER, 0
CURLOPT_RETURNTRANSFER, 1
и используйте curl_getinfo с CURLINFO_HTTP_CODE (или без параметра opt, и у вас будет ассоциативный массив со всей необходимой информацией)
Больше на: http://php.net/manual/fr/function.curl-getinfo.php
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);
Мой путь
$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 и удалите предел разнесения.
Вот мой вклад в дебаты... Это возвращает один массив с разделенными данными и заголовками в списке. Это работает на основе того, что 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;
}
}
Лучше использовать подробный ответ 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;
}
}
Проблема со многими ответами здесь заключается в том, что "\r\n\r\n"
может на законных основаниях появляться в теле html, поэтому вы не можете быть уверены, что правильно разделяете заголовки.
Кажется, что единственный способ хранить заголовки отдельно с одним вызовом curl_exec
использовать обратный вызов, как указано выше в /questions/45862158/mozhet-li-php-curl-poluchit-zagolovki-otveta-i-telo-v-odnom-zaprose/45862185#45862185
А затем, чтобы (надежно) получить только тело запроса, вам нужно будет передать значение Content-Length
заголовок к substr()
как отрицательное начальное значение.
Улучшение ответа Джеффри:
Я не смог получить нужную длину заголовка с помощью
$headerSize = curl_getinfo($this->curlHandler, CURLINFO_HEADER_SIZE);
- Мне пришлось самостоятельно рассчитывать размер заголовка.
Вдобавок некоторые улучшения для лучшей читаемости.
$headerSize = 0;
curl_setopt_array($this->curlHandler, [
CURLOPT_URL => $yourUrl,
CURLOPT_POST => 0,
CURLOPT_RETURNTRANSFER => 1,
// this function is called by curl for each header received
CURLOPT_HEADERFUNCTION =>
function ($curl, $header) use (&$headers, &$headerSize) {
$lenghtCurrentLine = strlen($header);
$headerSize += $lenghtCurrentLine;
$header = explode(':', $header, 2);
if (count($header) > 1) { // store only vadid headers
$headers[strtolower(trim($header[0]))][] = trim($header[1]);
}
return $lenghtCurrentLine;
},
]);
$fullResult = curl_exec($this->curlHandler);
$result = substr($fullResult, $headerSize);
Попробуйте это, если вы используете 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);
Будьте осторожны, когда вам нужны последние данные, которые вернулись с сервера. Этот код может нарушить ваши ожидания при ожидании реальных (последних) заголовков и тела: 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;
Вернуть заголовки ответа со ссылочным параметром:
<?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;
}
?>
Если вам не нужно использовать 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