php curl multi error handler
Asked Answered
T

2

5

i want to capture curl errors and warnings in my error handler so that they do not get echoed to the user. to prove that all errors have been caught i prepend the $err_start string to the error. currently here is a working (but simplified) snippet of my code (run it in a browser, not cli):

<?php
set_error_handler('handle_errors');
test_curl();
function handle_errors($error_num, $error_str, $error_file, $error_line)
{           
    $err_start = 'caught error'; //to prove that the error has been properly caught
    die("$err_start $error_num, $error_str, $error_file, $error_line<br>");
}           
function test_curl()
{   
    $curl_multi_handle = curl_multi_init();
    $curl_handle1 = curl_init('iamdooooooooooown.com');
    curl_setopt($curl_handle1, CURLOPT_RETURNTRANSFER, true);
    curl_multi_add_handle($curl_multi_handle, $curl_handle1);
    $still_running = 1;
    while($still_running > 0) $multi_errors = curl_multi_exec($curl_multi_handle, $still_running);
    if($multi_errors != CURLM_OK) trigger_error("curl error [$multi_errors]: ".curl_error($curl_multi_handle), E_USER_ERROR);
    if(strlen(curl_error($curl_handle1))) trigger_error("curl error: [".curl_error($curl_handle1)."]", E_USER_ERROR);
    $curl_info = curl_getinfo($curl_handle1); //info for individual requests
    $content = curl_multi_getcontent($curl_handle1);
    curl_multi_remove_handle($curl_multi_handle, $curl_handle1);
    curl_close($curl_handle1);
    curl_multi_close($curl_multi_handle);
}
?>

note that my full code has multiple requests in parallel, however the issue still manifests with a single request as shown here. note also that the error handler shown in this code snippet is very basic - my actual error handler will not die on warnings or notices, so no need to school me on this.

now if i try and curl a host which is currently down then i successfully capture the curl error and my script dies with:

caught error 256, curl error: [Couldn't resolve host 'iamdooooooooooown.com'], /var/www/proj/test_curl.php, 18

however the following warning is not caught by my error handler function, and is being echoed to the page:

Warning: (null)(): 3 is not a valid cURL handle resource in Unknown on line 0

i would like to capture this warning in my error handler so that i can log it for later inspection.

one thing i have noticed is that the warning only manifests when the curl code is inside a function - it does not happen when the code is at the highest scope level. is it possible that one of the curl globals (eg CURLM_OK) is not accessible within the scope of the test_curl() function?

i am using PHP Version 5.3.2-1ubuntu4.19

edits

  • updated the code snippet to fully demonstrate the error
  • the uncaptured warning only manifests when inside a function or class method
Teratogenic answered 14/4, 2013 at 22:57 Comment(7)
Do you have Xdebug with the xdebug.scream setting enabled?Marrissa
i don't think so. does this capture more errors than the set_error_handler()?Teratogenic
Take a look at this answer. It may help.Indistinguishable
@Jack thanks for testing. i have updated the code snippet - to my surprise the warning only manifests when the code is within a function or method!Teratogenic
Now it shows only caught error 256, curl error: [Couldn't resolve host 'iamdooooooooooown.com'], /home/xxx/curl.php, 18<br> and no other errors.Indistinguishable
are you using apache? i don't get the warning in cli, but i definitely get it with apache.Teratogenic
I used cli. If this only happens under apache it must be something else than your code and might be a bug in either libcurl, libphp or apache itself.Indistinguishable
C
9

I don't think i agree with the with the way you are capturing the error ... you can try

$nodes = array(
        "http://google.com",
        "http://iamdooooooooooown.com",
        "https://gokillyourself.com"
);

echo "<pre>";
print_r(multiplePost($nodes));

Output

Array
(
    [google.com] => #HTTP-OK 48.52 kb returned
    [iamdooooooooooown.com] => #HTTP-ERROR 0  for : http://iamdooooooooooown.com
    [gokillyourself.com] => #HTTP-ERROR 0  for : https://gokillyourself.com
)

Function Used

function multiplePost($nodes) {
    $mh = curl_multi_init();
    $curl_array = array();
    foreach ( $nodes as $i => $url ) {
        $url = trim($url);
        $curl_array[$i] = curl_init($url);
        curl_setopt($curl_array[$i], CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl_array[$i], CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)');
        curl_setopt($curl_array[$i], CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($curl_array[$i], CURLOPT_TIMEOUT, 15);
        curl_setopt($curl_array[$i], CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($curl_array[$i], CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($curl_array[$i], CURLOPT_SSL_VERIFYPEER, 0);
        curl_multi_add_handle($mh, $curl_array[$i]);
    }
    $running = NULL;
    do {
        usleep(10000);
        curl_multi_exec($mh, $running);
    } while ( $running > 0 );
    $res = array();

    foreach ( $nodes as $i => $url ) {
        $domain = parse_url($url, PHP_URL_HOST);
        $curlErrorCode = curl_errno($curl_array[$i]);
        if ($curlErrorCode === 0) {
            $info = curl_getinfo($curl_array[$i]);
            $info['url'] = trim($info['url']);
            if ($info['http_code'] == 200) {
                $content = curl_multi_getcontent($curl_array[$i]);
                $res[$domain] = sprintf("#HTTP-OK %0.2f kb returned", strlen($content) / 1024);
            } else {
                $res[$domain] = "#HTTP-ERROR {$info['http_code'] }  for : {$info['url']}";
            }
        } else {
            $res[$domain] = sprintf("#CURL-ERROR %d: %s ", $curlErrorCode, curl_error($curl_array[$i]));
        }
        curl_multi_remove_handle($mh, $curl_array[$i]);
        curl_close($curl_array[$i]);
        flush();
        ob_flush();
    }
    curl_multi_close($mh);
    return $res;
}
Countermine answered 17/4, 2013 at 17:7 Comment(1)
thanks! i clearly was expecting a down host to return a curl error, but as your code shows, there is a http error, but no curl error. as per your code, curl_error() should only be called if curl_errno() does not return 0. otherwise curl_error() seems to print out warnings which cannot be suppressed. it is strange that curl_errno() does not return 52 (CURLE_GOT_NOTHING) when the host is down though. cheers!Teratogenic
T
2

it is possible that this is a bug with php-curl. when the following line is removed, then everything behaves ok:

if(strlen(curl_error($curl_handle1))) trigger_error("curl error: [".curl_error($curl_handle1)."]", E_USER_ERROR);

as far as i can tell, curling a host that is down is corrupting $curl_handle1 in some way that the curl_error() function is not prepared for. to get around this problem (until a bug fix is made) just test if the http_code returned by curl_getinfo() is 0. if it is 0 then do not use the curl_error function:

if($multi_errors != CURLM_OK) trigger_error("curl error [$multi_errors]: ".curl_error($curl_multi_handle), E_USER_ERROR);
$curl_info = curl_getinfo($curl_handle1); //info for individual requests
$is_up = ($curl_info['http_code'] == 0) ? 0 : 1;
if($is_up && strlen(curl_error($curl_handle1))) trigger_error("curl error: [".curl_error($curl_handle1)."]", E_USER_ERROR);

its not a very elegant solution, but it may have to do for now.

Teratogenic answered 17/4, 2013 at 10:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.