Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Have to call curl_close() twice before handle is closed and cookie jar can be read. Is this a bug?

I've been banging my head against the wall for hours trying to understand why cURL's cookie jar file was empty when I tried reading it. I just discovered that my code works if I call curl_close() twice instead of once, however, and I'm wondering if this is a bug with cURL.

Here's an example:

curl_close($chInfo['handle']);
var_dump(is_resource($chInfo['handle']));

That outputs boolean true. So, in other words, the handle isn't closed, despite the fact that I called curl_close().

My next thought was that maybe it takes some time for the handle to be closed, so I tried using sleep() for a few seconds after the curl_close() call, but there wasn't any difference.

Out of desperation, I tried copying the curl_close() line, like this:

curl_close($chInfo['handle']);
curl_close($chInfo['handle']);
var_dump(is_resource($chInfo['handle']));

That outputs boolean false, meaning the handle is closed, and I am able to read from the cookie jar file (cURL writes the cookies to the file when the handle is closed).

So what's going on here? This seems an awful lot like a bug!

EDIT: I can't post my full code (you wouldn't want to read it anyway!), but here is a simplified example (note that only one url is fetched in this example, whereas in my real code curl_multi is utilized to fetch many URLs simultaneously):

$curlOptions = array(
    CURLOPT_USERAGENT      => 'Mozilla/5.001 (windows; U; NT4.0; en-US; rv:1.0) Gecko/25250101',
    CURLOPT_CONNECTTIMEOUT => 5, // the number of seconds to wait while trying to connect.
    CURLOPT_TIMEOUT        => 5, // the maximum number of seconds to allow cURL functions to execute.
    CURLOPT_RETURNTRANSFER => 1, // TRUE to return the transfer as a string of the return value of curl_exec() instead of outputting it out directly.
    CURLOPT_FOLLOWLOCATION => 1,
    CURLOPT_MAXREDIRS      => 10,
    CURLOPT_AUTOREFERER    => 1,
    CURLOPT_REFERER        => null,
    CURLOPT_POST           => 0,  // GET request by default
    CURLOPT_POSTFIELDS     => '', // no POST data by default
    CURLINFO_HEADER_OUT    => 1, // allows the request header to be retrieved
    CURLOPT_HEADER         => 1, // returns the response header along with the page body
    CURLOPT_URL            => 'http://www.example.com/',
    CURLOPT_COOKIEJAR      => __DIR__ . '/cookie.txt',
    CURLOPT_COOKIEFILE     => __DIR__ . '/cookie.txt'
);


$ch = curl_init();
curl_setopt_array($ch, $curlOptions); // set the options for this handle

$mh = curl_multi_init();
$responses = array();
curl_multi_add_handle($mh, $ch); // add the handle to the curl_multi object

do
{
    $result   = curl_multi_exec($mh, $running);
    $activity = curl_multi_select($mh);    // blocks until there's activity on the curl_multi connection (in which case it returns a number > 0), or until 1 sec has passed

    while($chInfo = curl_multi_info_read($mh))
    {
        $chStatus = curl_getinfo($chInfo['handle']);

        if($chStatus['http_code'] == 200) // if the page was retrieved successfully
        {
            $response = curl_multi_getcontent($chInfo['handle']); // get the response

            curl_multi_remove_handle($mh, $chInfo['handle']); // remove the curl handle that was just completed
            curl_close($chInfo['handle']);                    // close the curl handle that was just completed (cookies are saved when the handle is closed?)
            curl_close($chInfo['handle']);

            var_dump(is_resource($chInfo['handle']));
        }
        else // request failed
        {
            echo 'Error: Request failed with http_code: ' . $chStatus['http_code'] . ', curl error: ' . curl_error($chInfo['handle']). PHP_EOL;
        }
    }
} while ($running > 0);

curl_multi_close($mh);

If you run the above code, the output will be

boolean false

Indicating that the handle is closed. However, if you remove the second call to curl_close(), then the output changes to

boolean true

Indicating the handle is not closed.

like image 403
Nate Avatar asked Mar 31 '14 00:03

Nate


1 Answers

This is not realy a bug, but just the way it works. If you look at the source code you can see what is happening.

At first you open the handle with $ch = curl_init(); and looking at the source in ext\curl\interface.c you can see that internally it sets ch->uses = 0;

Then you call curl_multi_add_handle($mh, $ch); and looking at ext\curl\multi.c this method does ch->uses++;. At this point ch->uses==1

Now the last part, looking at curl_close($chInfo['handle']);, again in ext\curl\interface.c it has the following code:

if (ch->uses) {
    ch->uses--;
} else {
    zend_list_delete(Z_LVAL_P(zid));
}

So the first attempt to close it will decrease ch->uses and the second attempt it will actually close it.

This internal pointer only increases when using curl_multi_add_handle or when using curl_copy_handle. So I guess the idea was for curl_multi_add_handle to use a copy of the handle and not the actual handle.

like image 129
Hugo Delsing Avatar answered Sep 20 '22 06:09

Hugo Delsing