PHP cURL이 단일 요청으로 응답 헤더와 본문을 검색 할 수 있습니까?


314

PHP를 사용하여 cURL 요청을 위해 헤더와 본문을 모두 얻는 방법이 있습니까? 나는이 옵션을 발견했다 :

curl_setopt($ch, CURLOPT_HEADER, true);

본문과 헤더 를 반환 하지만 본문을 얻으려면 구문 분석해야합니다. 보다 유용하고 안전한 방법으로 둘 다 얻을 수있는 방법이 있습니까?

"단일 요청"의 경우 GET / POST 이전에 HEAD 요청을 발행하지 않는 것을 의미합니다.


3
이에 대한 해결책이 내장되어 있습니다.이 답변을 참조하십시오 : stackoverflow.com/a/25118032/1334485 (이 게시물에 'coz이 게시물은 여전히 ​​많은 조회를 얻습니다)
Skacc

이 멋진 의견을보십시오 : secure.php.net/manual/en/book.curl.php#117138
user956584


나는 내 질문이이 질문과 중복된다고 들었다. 복제본이 아닌 경우 누군가 다시 열 수 있습니까? stackoverflow.com/questions/43770246/… 내 질문에 헤더와 본문이 분리되어 있고 하나의 문자열이 아닌 객체를 반환하는 메서드를 사용해야하는 구체적인 요구 사항이 있습니다.
1.21 기가 와트

답변:


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

43
이 나쁜 솔루션입니다 때문에 당신은 프록시 서버와 프록시 서버 (예 : 피들러) 응답에 자신의 헤더를 추가 사용하는 경우 -이 헤더는 모든 오프셋을 돌파하고 사용해야 list($header, $body) = explode("\r\n\r\n", $response, 2)만 작동 변형으로
msangel

5
@msangel 서버가 302 리디렉션을 수행하는 경우와 같이 응답에 여러 헤더가 있으면 솔루션이 작동하지 않습니다. 어떤 제안?
Nate

4
@Nate, 예, 알고 있습니다. AFAIK이지만 가능한 추가 헤더는 코드와 함께 만 있습니다 100(계속). 이 헤더의 경우 request 옵션을 올바르게 정의 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); 하여이 헤더 응답 전송을 비활성화 할 수 있습니다 . 에 관해서는 302,이 그러나 난 서버가 일부 신체를 보내 때때로 알고, 302 헤더 리디렉션을하기 때문에, 그것은 몸을 기대하지 않고, 일이되어서는 안 302응답 있지만 어쨌든 브라우저에 의해 무시됩니다, 지금까지 컬이 왜 처리해야합니까? )
msangel

5
CURLOPT_VERBOSE프로세스 정보를 STDERR(CLI에서 귀찮게 할 수 있음) 출력하기위한 것이며 논의 된 문제는 쓸모가 없습니다.
hejdav

205

이 스레드를 제공하는 다른 많은 솔루션 이 올바르게 수행 하지 않습니다 .

  • 서버가 켜져 있거나 서버가 100 코드로 응답 할 \r\n\r\n때는 스 플리 팅을 신뢰할 수 없습니다 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;

    $headers[strtolower(trim($header[0]))][] = trim($header[1]);

    return $len;
  }
);

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

12
IMO 이것은이 스레드에서 가장 좋은 답변이며 다른 답변에서 발생한 리디렉션 문제를 해결합니다. CURLOPT_HEADERFUNCTION 의 설명서를 읽고 작동 방식 및 잠재적 문제점을 이해하는 것이 가장 좋습니다. 또한 다른 사람들을 돕기 위해 답변을 약간 개선했습니다.
Simon East

복제본 헤더에 대한 답변을 업데이트했습니다. 앞으로 코드를 원하는대로 다시 포맷하지 마십시오. 이것은 클로저 함수 경계가 어디에 있는지 명확하게하기 위해 작성되었습니다.
Geoffrey

@Geoffrey $headers = [];유효한 PHP입니까?
thealexbaron

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_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$object, 'methodName'))

참고로 콜백 함수는 각 헤더마다 호출되며 잘리지 않는 것 같습니다. 전역 변수를 사용하여 모든 헤더를 보유하거나 콜백에 익명 함수를 사용하고 로컬 변수 (익명 함수가 아닌 상위 범위에 대한 로컬)를 사용할 수 있습니다.
MV.

2
@MV 감사합니다. "line-by-line"으로 "각 헤더"를 의미했습니다. 명확성을 위해 답변을 편집했습니다. 전체 헤더 섹션 (일명 모든 헤더)을 가져 오려면 콜백에 객체 메소드를 사용하여 객체 속성이 모든 헤더를 보유 할 수 있습니다.
Skacc

8
이것이 최고의 답변 IMO입니다. CURLOPT_FOLLOWLOCATION을 사용할 때 여러 "\ r \ n \ r \ n"에 문제가 발생하지 않으며 프록시의 추가 헤더에 영향을받지 않습니다.
Rafał G.

나를 위해 아주 잘 일했다. stackoverflow.com/questions/6482068/… 문제가있는 경우
RHH

1
예, 이것이 최선의 접근 방법이지만 @Geoffrey의 대답은 전역 변수 등이 필요없는 익명 함수를 사용하여이를 깨끗하게 만듭니다.
Simon East

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가 있고 break가 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.
알릭


curl이 위치 헤더를 따르도록 설정된 경우이 방법은 302 리디렉션에서도 실패합니다.
Simon East

10

옵션을 설정하십시오.

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

CURLINFO_HTTP_CODE와 함께 curl_getinfo를 사용하십시오 (또는 opt 매개 변수가 없으면 원하는 모든 정보가 포함 된 연관 배열이 있습니다)

자세한 내용은 http://php.net/manual/fr/function.curl-getinfo.php


5
이것은 응답 헤더를 전혀 반환하지 않는 것 같습니다. 또는 적어도을 사용하여 검색 할 방법이 없습니다 curl_getinfo().
Simon East

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의 질문에 대답하지 않습니다.
Geoffrey

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다른 헤더 앞에 작동합니다 .

CRLF 대신 LF 만 줄 바꿈으로 보내는 버그가있는 서버로 작업해야하는 경우 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로 폭발 3 번째 매개 변수가?
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이 헤더 청크 [blank line] 데이터를 반환한다는 사실에 근거하여 작동합니다.

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
합법적으로 나타날 수 있지만 답변이 잘못되었습니다. Content-Length는 HTTP 응답에 없어도됩니다. 헤더를 수동으로 구문 분석하는 올바른 방법은 \ r \ n (또는 \ n \ n)의 첫 번째 인스턴스를 찾는 것입니다. :이 두 요소, 즉 돌아 폭발 제한하여 간단하게 할 수있는 list($head, $body) = explode("\r\n\r\n", $response, 2);당신이 사용하는 경우, 그러나 CURL 이미 당신을 위해이 작업을 수행curl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);
제프리

-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);맞습니까? explode의 3 번째 매개 변수를 제거하지 않아야합니까?
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); 그래서 둘 다 맞습니다.
Cyborg

-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.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.