397

Is there any way to get both headers and body for a cURL request using PHP? I found that this option:

curl_setopt($ch, CURLOPT_HEADER, true);

is going to return the body plus headers, but then I need to parse it to get the body. Is there any way to get both in a more usable (and secure) way?

Note that for "single request" I mean avoiding issuing a HEAD request prior of GET/POST.

halfer
  • 19,824
  • 17
  • 99
  • 186
gremo
  • 47,186
  • 75
  • 257
  • 421
  • 4
    There is a built in solution for this, see this answer: http://stackoverflow.com/a/25118032/1334485 (added this comment 'coz this post still gets many views) – Skacc Apr 16 '15 at 21:59
  • Look at this nice comment: https://secure.php.net/manual/en/book.curl.php#117138 – user956584 Aug 24 '15 at 10:35
  • [a-curl_multi-working-example---that-actually-works](https://github.com/eladkarako/a-curl_multi-working-example---that-actually-works) –  Dec 02 '15 at 01:52
  • I was told my question was a duplicate to this question. If it is not a duplicate can someone please reopen it? http://stackoverflow.com/questions/43770246/can-php-curl-return-an-object-with-body-and-headers-in-a-single-request In my question I have a concrete requirement to use a method that returns an object with headers and body separate and not one string. – 1.21 gigawatts May 04 '17 at 04:00

16 Answers16

562

One solution to this was posted in the PHP documentation comments: http://www.php.net/manual/en/function.curl-exec.php#80442

Code example:

$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 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);

Warning: As noted in the comments below, this may not be reliable when used with proxy servers or when handling certain types of redirects. @Geoffrey's answer may handle these more reliably.

laurent
  • 88,262
  • 77
  • 290
  • 428
iblue
  • 29,609
  • 19
  • 89
  • 128
  • 1
    Is this reliable? I mean using header size. Thanks. – gremo Feb 07 '12 at 21:31
  • 25
    You can also `list($header, $body) = explode("\r\n\r\n", $response, 2)`, but this might take a bit longer, depending on your request size. – iblue Feb 07 '12 at 21:33
  • Thanks, answer accepted. So bad headers are in raw format btw. Looking for a good parsing function. – gremo Feb 07 '12 at 23:26
  • 48
    this is bad solution because if you use proxy server and your proxy server(fiddler for example) add own headers to response - this headers broke all offsets and you should use `list($header, $body) = explode("\r\n\r\n", $response, 2)` as only working variant – msangel Feb 25 '13 at 02:17
  • 1
    This is not my fault. It's a bug in PHP. See https://bugs.php.net/bug.php?id=63894. If you send a code sample to them, they will probably fix it. – iblue Feb 25 '13 at 18:23
  • 6
    @msangel Your solution doesn't work when there are multiple headers in the response, such as when the server does a 302 redirect. Any suggestions? – Nate Apr 08 '14 at 00:32
  • 4
    @Nate, yes, i know this. AFAIK, but there is only one possible additional header - with code `100` (Continue). For this header you can go around with correctly defining request option: `curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); `, disabling sending this header response. As for `302`, this should not be happened, because 302 header is redirect, it not expecting body, however i know, sometimes servers send some body with `302` response, but it will be anyway ignored by browsers, so far, why curl should handle this?) – msangel Apr 08 '14 at 14:37
  • 1
    @Nate, In your case, most possible, that you have `CURLOPT_FOLLOWLOCATION` setted to `true`, so far, you have two http queries pre one curl query. – msangel Apr 08 '14 at 14:42
  • 6
    `CURLOPT_VERBOSE` is intended to output process information to `STDERR` (may bother in CLI) and for discussed problem is useless. – hejdav Apr 08 '15 at 15:46
  • 4
    msangel's poroblem with iblue's solution has been fixed in lib_curl 7.30.0 https://github.com/curl/curl/pull/60 -- curl now correctly includes any proxy headers in the CURLINFO_HEADER_SIZE calculation. – Eric L. Sep 08 '16 at 18:00
  • Note also that proxies could add a second http response code with something like "Connection established" so that's all you'll get in header, and the body will contain the actual header then the body if you use the explode trick above (bit me before). – Jon Marnock Jan 30 '18 at 03:35
387

Many of the other solutions offered this thread are not doing this correctly.

  • Splitting on \r\n\r\n is not reliable when CURLOPT_FOLLOWLOCATION is on or when the server responds with a 100 code RFC-7231, MDN.
  • Not all servers are standards compliant and transmit just a \n for new lines (and a recipient may discard the \r in the line terminator) Q&A.
  • Detecting the size of the headers via CURLINFO_HEADER_SIZE is also not always reliable, especially when proxies are used Curl-1204 or in some of the same redirection scenarios.

The most correct method is using CURLOPT_HEADERFUNCTION.

Here is a very clean method of performing this using PHP closures. It also converts all headers to lowercase for consistent handling across servers and HTTP versions.

This version will retain duplicated headers

This complies with RFC822 and RFC2616, please do not make use of the mb_ (and similar) string functions, it is a not only incorrect but even a security issue RFC-7230!

$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);
hakre
  • 193,403
  • 52
  • 435
  • 836
Geoffrey
  • 10,843
  • 3
  • 33
  • 46
  • 20
    IMO this is the best answer in this thread and fixes problems with redirects that occurred with other answers. Best to read the documentation for [CURLOPT_HEADERFUNCTION](https://curl.haxx.se/libcurl/c/CURLOPT_HEADERFUNCTION.html) to understand how it works and potential gotchas. I've also made some improvements to the answer to help others out. – Simon East Jun 09 '17 at 07:06
  • 1
    Great, I have updated the answer to cater for duplicated headers. In future do not re-format the code to what you believe it should be. This is written in a way to make it clear where the closure function boundaries are. – Geoffrey Jun 09 '17 at 07:20
  • @Geoffrey Is `$headers = [];` valid php? – thealexbaron Jul 17 '17 at 22:00
  • 7
    @thealexbaron Yes it is as of PHP 5.4, see: http://php.net/manual/en/migration54.new-features.php – Geoffrey Jul 18 '17 at 00:39
  • 6
    This answer is highly underrated for such a neat and RFC compliant approach. This should be made sticky answer and moved to the top. I just wish there were a faster approach to get value of a desired header instead of parsing all headers first. – Fr0zenFyr May 17 '18 at 09:01
  • @Fr0zenFyr thanks for the comments, unfortunately I do not think there is a way to stop curl calling the function once you find the header you're interested in, but you could always throw an `if (array_key_exists('my-header', $headers)) return $len` into it to ignore the rest of them once you find your target. – Geoffrey May 17 '18 at 09:24
  • @Geoffrey Thanks for responding. I agree with you and also understand that it will hardly make any difference if there are only a few headers (which usually is the case unless you communicate with quite shitty URL :P). Was just hoping that there was an inbuilt way of catching a response header for code legibility. :) – Fr0zenFyr May 17 '18 at 09:34
  • @Geoffrey Is the following code is proper why to fetch body content? `$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $body = substr($response, $headerSize);` – Mahesh.D Jun 26 '19 at 07:49
  • 1
    @Mahesh.D no, `$data = curl_exec($ch);` returns the content when `CURLOPT_RETURNTRANSFER` is set as per the provided example. – Geoffrey Jun 26 '19 at 11:26
  • `$a['foo'] = ['bar'];` is literally the same thing as `$a['foo'][] = 'bar';`. I've simplified your code somewhat. – Mike Jul 25 '19 at 23:15
  • @Mike: It most certainly is not. `$a['foo'] = ['bar']` will assign a new array with only one element, where as `$a['foo'][] = 'bar'; will append an element. Also the check for an existing key is required on PHP 5.7 and above to avoid spamming the log with PHP warnings. You're edits are incorrect. – Geoffrey Jul 26 '19 at 05:27
  • @Geoffrey I'm not sure what you're talking about with PHP warnings and checking that keys exist before assigning them, but [you're wrong](https://3v4l.org/Q3VF6). In your case they are exactly the same thing because you have already determined that `array_key_exists` is `false`. – Mike Jul 26 '19 at 21:47
  • Also, PHP 5.7 doesn't exist. It goes from 5.6 to 7.0. – Mike Jul 26 '19 at 21:56
  • @mike sorry, yes, 5.6 was when the notices were introduced attempting to use an unassigned array index as an array, it plagued our servers while our clients updated the code, however as of testing now it seems that this behaviour is no longer the case. So yes, I am sorry, you are completely correct here. – Geoffrey Jul 28 '19 at 01:17
  • @Geoffrey That's very interesting. You're sure it was PHP and not another server-side language producing the error (like CGI scripts)? I wonder if there is anything in the config that you can change to enable this behavior in PHP. If there is, I haven't heard of it. Did you compile PHP yourselves, or install from binaries? – Mike Jul 29 '19 at 16:57
  • @Mike I am sorry, this was many years ago now when 5.6 was still new, they were `E_NOTICE` warnings. Beyond ensuring our staff were aware of the issue and how to resolve it, I do not remember. – Geoffrey Jul 30 '19 at 00:16
  • @Geoffrey Maybe the error was about trying to access an unset array key, not setting one. However that should have thrown an E_NOTICE before 5.6, but your config could have changed during the upgrade as well. – Mike Jul 30 '19 at 06:23
  • Using above code, we are getting our reqeust's headers instead of the response headers. Any clue to get that. – Naveed Ramzan Dec 16 '20 at 06:51
  • @NaveedRamzan post a SO question about it with a code example, this is not the right place to ask for support. This works for literally hundreds of people without issue indicating something else is wrong with your code. – Geoffrey Jan 17 '21 at 15:39
  • @Geoffrey Can you help me with https://stackoverflow.com/q/68171432/6792962 I used your code to parse the Header but having error. – Alpha Jun 29 '21 at 01:09
  • @Geoffrey how do you work with CURLOPT_HEADERFUNCTION when doing curl multi init? – Andrew Mar 29 '22 at 08:37
  • @Andrew exactly the same way, just use a different variable for each `CURLOPT_HEADERFUNCTION` instead of `$headers`. How you do this is up to you. – Geoffrey Mar 30 '22 at 01:37
  • @Geoffrey ... Take my money! – codekandis May 13 '22 at 14:54
  • Very useful answer! In the line, "function($curl, $header) use (&$headers)", what is $curl? I don’t see $curl used anywhere else. Should that be $ch? I think the "static size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata)" [from https://curl.se/libcurl/c/CURLOPT_HEADERFUNCTION.html] requires it, but does it do anything? Is it just a dummy variable? – Tristan Jun 22 '22 at 22:46
  • 1
    @Tristan it's correct as it stands. The php curl callback function expects two arguments, the first is the curl handle itself (same value as $ch), the second value is the header. In the above example it's simply unused but must be present or the callback prototype wont match. It could however be used for a generic handler across your entire code base where access to the handle is required per request for extra curl processing inside the callback. – Geoffrey Jun 24 '22 at 01:43
  • @Geoffrey @Mike `$a['foo'] = ['bar'];` is not the same as `$a['foo'][] = 'bar';` First stores an Array containing one item 'bar' under key 'foo'. Second stores string 'bar' in a NEW item under key 'foo'. The purpose of creating another array dimension here, is to keep duplicated headers. Former statement would overwrite value under 'foo' key. In your test, duplicate your two lines. (to imitate two headers with the same name). Current edit is OK. Originally I came to post a note explaining why [] is there. As it seemed confusing to me, before seeing it's for duplicates. – papo Jul 04 '23 at 22:46
  • @papo, Yes. You're right. I wrote that comment 4 years ago and my edit was rolled back shortly after. – Mike Jul 05 '23 at 16:47
  • I checked the history, I see now there was a conditional block with both of them and you were talking about the first run, where it was indeed weird and unnecessary to not use "add to array" even on an empty array. Before I was just reading the comments and from those came to a conclusion you mean those two are identical in general. That's why I commented, to not keep others confused. @Mike suggestion was OK and the rollback was rollbacked so that's what we see now. – papo Jul 05 '23 at 18:11
143

Curl has a built in option for this, called CURLOPT_HEADERFUNCTION. The value of this option must be the name of a callback function. Curl will pass the header (and the header only!) to this callback function, line-by-line (so the function will be called for each header line, starting from the top of the header section). Your callback function then can do anything with it (and must return the number of bytes of the given line). Here is a tested working code:

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

The above works with everything, different protocols and proxies too, and you dont need to worry about the header size, or set lots of different curl options.

P.S.: To handle the header lines with an object method, do this:

curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($object, 'methodName'))
hakre
  • 193,403
  • 52
  • 435
  • 836
Skacc
  • 1,726
  • 2
  • 12
  • 16
  • As a note, the callback function is called for each header and it seems they are not trimmed. You can use a global variable to hold all headers or you can use an anonymous function for the callback and use a local variable (local for the parent scope, not the anonymous function). – MV. Apr 25 '15 at 23:55
  • 2
    @MV Thanks, yes, by "line-by-line" I meant "each header". I edited my answer for clarity. To get the entire header section (aka. all headers), you can also use an object method for the callback so an object property can hold all of them. – Skacc Apr 26 '15 at 12:06
  • 11
    This is the best answer IMO. It doesn't cause problems with multiple "\r\n\r\n" when using CURLOPT_FOLLOWLOCATION and I guess it won't be affected by additional headers from proxies. – Rafał G. Jul 02 '15 at 11:43
  • 1
    Worked very well for me, also see http://stackoverflow.com/questions/6482068/curl-setoptch-curlopt-headerfunction-arraythis-readheader-not-working in case of issues – RHH Apr 04 '16 at 15:12
  • 3
    Yes, this is the best approach however @Geoffrey's answer makes this cleaner by using an anonymous function with no need for global variables and such. – Simon East Jun 09 '17 at 06:50
  • This was the first answer to suggest using CURLOPT_HEADERFUNCTION, so I think it should be the highest rated. – ADJenks Apr 08 '21 at 21:14
  • 1
    @ADJenks first doesn't mean best... this website is about finding and providing the highest quality answer. It's not a contest where the first answer wins. – Geoffrey Jun 24 '22 at 01:45
38

is this what are you looking to?

curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch); 
list($header, $body) = explode("\r\n\r\n", $response, 2);
papsy
  • 433
  • 4
  • 5
user1031143
  • 828
  • 1
  • 10
  • 16
  • 8
    This works normally except when there's a HTTP/1.1 100 Continue followed by a break then HTTP/1.1 200 OK. I'd go with the other method. – ghostfly May 23 '13 at 19:38
  • 1
    Take a look to the selected answer of http://stackoverflow.com/questions/14459704/does-empty-expect-header-mean-anything before implementing something like this. http://www.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.` – Alrik Dec 13 '13 at 10:48
  • @ghostfly, [i have the solution for `100` in comment under "correct" answer](http://stackoverflow.com/questions/9183178/php-curl-retrieving-response-headers-and-body-in-a-single-request/9183272?noredirect=1#comment35020725_9183272). – msangel Apr 08 '14 at 14:47
  • This method also fails on 302 redirects when curl is set to follow the location header. – Simon East Jun 08 '17 at 22:36
  • This doesn't work on some POST requests too. – divix Jul 06 '22 at 12:41
16

If you specifically want the Content-Type, there's a special cURL option to retrieve it:

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
pr1001
  • 21,727
  • 17
  • 79
  • 125
  • 4
    The OP asked if there is a way to retrieve the headers, not one specific header, this doesn't answer the OP's question. – Geoffrey Jun 06 '19 at 02:38
  • 3
    @Geoffrey no, it can be useful for others who needs to get only Content-Type – Acuna Jun 05 '22 at 20:19
  • @Acuna it doesn't matter how useful it is, it does not answer the OPs question! – Geoffrey Jun 06 '22 at 21:04
  • 2
    @Geoffrey yes, but all answers are useful for other users too, don't forget about it, and OP has found an answer too, so everybody is satisfied – Acuna Jun 06 '22 at 21:10
13

Just set options :

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

and use curl_getinfo with CURLINFO_HTTP_CODE (or no opt param and you will have an associative array with all the informations you want)

More at : http://php.net/manual/fr/function.curl-getinfo.php

Cyril H.
  • 155
  • 1
  • 2
  • 7
    This does not seem to return the response headers to you at all. Or at least there's no way to retrieve them using `curl_getinfo()`. – Simon East Jun 08 '17 at 22:39
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);

Works with HTTP/1.1 100 Continue before other headers.

If you need work with buggy servers which sends only LF instead of CRLF as line breaks you can use preg_split as follows:

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);
Enyby
  • 4,162
  • 2
  • 33
  • 42
  • Shouldn't `$parts = explode("\r\n\r\nHTTP/", $response);` have 3rd parameter for explode as 2? – user4271704 Aug 21 '15 at 19:16
  • @user4271704 No. It allow find last HTTP message. `HTTP/1.1 100 Continue` can appear many times. – Enyby Aug 22 '15 at 09:25
  • But he says something else: http://stackoverflow.com/questions/9183178/php-curl-retrieving-response-headers-and-body-in-a-single-request/26885947?noredirect=1#comment52233964_26885947 which one of you are correct? – user4271704 Aug 24 '15 at 09:20
  • `HTTP/1.1 100 Continue` can appear many times. He view case if it appear only one time, but it wrong in common case. For example for `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...` his code don't work properly – Enyby Aug 24 '15 at 09:35
  • 1
    Splitting on \r\n is not reliable, some servers do not conform to HTTP specifications and will only send a \n. The RFC standard states that applications should ignore \r and split on \n for greatest reliability. – Geoffrey Jun 09 '17 at 07:24
  • @Geoffrey This is only recommendation in RFC 2616. But you can use `preg_split` if you want it is slower but work for your case. – Enyby Jun 11 '17 at 03:35
  • @Enyby the better solution is to split on `\n` and then trim the output, this is both fast and 100% compatible. – Geoffrey Jun 06 '19 at 02:39
  • I'd suggest not using `preg_split` for such a simple operation. Split by `\n` and then remove the `\r`, much faster. – Nikolay Dimitrov Dec 06 '21 at 08:34
1

My way is

$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];
}

If needed apply a for loop and remove the explode limit.

tony gil
  • 9,424
  • 6
  • 76
  • 100
Roy
  • 322
  • 3
  • 6
1

Here is my contribution to the debate ... This returns a single array with the data separated and the headers listed. This works on the basis that CURL will return a headers chunk [ blank line ] data

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;
    }
}
Antony
  • 3,875
  • 30
  • 32
0

The problem with many answers here is that "\r\n\r\n" can legitimately appear in the body of the html, so you can't be sure that you're splitting headers correctly.

It seems that the only way to store headers separately with one call to curl_exec is to use a callback as is suggested above in https://stackoverflow.com/a/25118032/3326494

And then to (reliably) get just the body of the request, you would need to pass the value of the Content-Length header to substr() as a negative start value.

Community
  • 1
  • 1
mal
  • 62
  • 4
  • 2
    It can appear legitimately, but your answer is incorrect. Content-Length does not have to be present in a HTTP response. The correct method to manually parse the headers is to look for the first instance of \r\n (or \n\n). This could be done simply by limiting explode to return only two elements, ie: `list($head, $body) = explode("\r\n\r\n", $response, 2);`, however CURL already does this for you if you use `curl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction);` – Geoffrey Jun 09 '17 at 07:26
0

Just in case you can't / don't use CURLOPT_HEADERFUNCTION or other solutions;

$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));
}
Kerem
  • 11,377
  • 5
  • 59
  • 58
0

A better way is to use the verbose CURL response which can be piped to a temporary stream. Then you can search the response for the header name. This could probably use a few tweaks but it works for me:

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;
        
    }
}
james-geldart
  • 709
  • 7
  • 9
0

Improvement of Geoffreys answer:

I couldn't get the right length for header with $headerSize = curl_getinfo($this->curlHandler, CURLINFO_HEADER_SIZE);- i had to calculate header size on my own.

In addition some improvements for better readability.

       $headerSize = 0;
        $headers['status'] = '';
        curl_setopt_array($this->curlHandler, [
            CURLOPT_URL => $yourURL,
            CURLOPT_POST => 0,
            CURLOPT_HEADER => 1,
            // this function is called by curl for each header received
            // source: https://stackoverflow.com/a/41135574/8398149 and improved
            CURLOPT_HEADERFUNCTION =>
                function ($curl, $header) use (&$headers, &$headerSize) {
                    $lenghtCurrentLine = strlen($header);
                    $headerSize += $lenghtCurrentLine;
                    $header = explode(':', $header, 2);
                    if (count($header) > 1) { // store only valid headers
                        $headers[strtolower(trim($header[0]))] = trim($header[1]);
                    } elseif (substr($header[0], 0, 8) === 'HTTP/1.1') {
                        // get status code
                        $headers['status'] = intval(substr($header[0], 9, 3));
                    }
                    return $lenghtCurrentLine;
                },
        ]);

        $fullResult = curl_exec($this->curlHandler);
sneaky
  • 439
  • 1
  • 6
  • 18
  • 1
    Don't you need to set CURLOPT_HEADER for it to return the headers when using CURLOPT_RETURNTRANSFER? If your'e using CURLOPT_HEADERFUNCTION without CURLOPT_HEADER, you shouldn't need to skip the headers with substr(). And then counting the aggregated length won't be necessary either. Have you even tried your code example? – MattBianco Mar 10 '23 at 13:11
  • You're right CURLOPT_HEADER was missing. I'm using this code here: https://github.com/sneakyx/wordpressApiClient/blob/master/src/WordpressApiClient.php – sneaky Mar 13 '23 at 06:57
-2

Return response headers with a reference parameter:

<?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;
         }
?>
diyism
  • 12,477
  • 5
  • 46
  • 46
  • Are you sure `$rtn=explode("\r\n\r\nHTTP/", $rtn, 2);` is correct? Shouldn't 3rd parameter of explode be removed? – user4271704 Aug 21 '15 at 18:11
  • @user4271704, the 3rd param is to deal with "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK...\r\n\r\n..." header – diyism Aug 24 '15 at 03:00
  • But he said something else: http://stackoverflow.com/questions/9183178/php-curl-retrieving-response-headers-and-body-in-a-single-request/17971689?noredirect=1#comment52197279_17971689 which one of you are correct? – user4271704 Aug 24 '15 at 09:19
  • @user4271704 the link you are referring to also use: `explode("\r\n\r\n", $parts, 2); ` so both are right. – Cyborg Sep 17 '19 at 01:25
-2

Try this if you are using 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);
tosturaw
  • 7
  • 3
-4

If you don't really need to use curl;

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

Which outputs

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>...

See http://php.net/manual/en/reserved.variables.httpresponseheader.php

Bevan
  • 483
  • 4
  • 5
  • 20
    uhm, you don't really need PHP either, but that just happens to be what the question is about... – Hans Z. Feb 24 '15 at 20:55