使用cURL发出的SSL请求在进程分叉后失败


SSL Requests made with cURL fail after process fork

我在卷曲中遇到了一些非常奇怪的行为

  1. 如果我在父进程中使用curl发出SSL请求,然后分叉进程并尝试在子进程中发出另一个SSL请求处理尝试失败,错误编号为35(SSL连接错误)
  2. 如果我没有在父进程中发出SSL请求,则子进程中的请求会成功
  3. 我可以使父级中任意数量的非SSL请求和子级中的SSL请求成功

看来这是一个与libcurl相关的问题中的错误,回答者对此有解决办法

我的问题是:

  1. curl_global_cleanup是否由PHP API中的其他名称公开
  2. 如果没有,周围还有其他工作吗

$ch = curl_init('https://www.google.ca/');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$success = curl_exec($ch);
var_dump($success !== false); // true
curl_close($ch); 
$pid = pcntl_fork();
if ($pid === 0) {
    $ch = curl_init('http://www.google.ca/');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $success = curl_exec($ch);
    var_dump($success !== false); // true
    curl_close($ch);
    $ch = curl_init('https://www.google.ca/');
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $success = curl_exec($ch);
    var_dump($success !== false); // false
    $errno = curl_errno($ch); // 35
    $error = curl_error($ch); // SSL connect error
    curl_close($ch);
} else if ($pid > 0) {
    // wait for child process
    pcntl_wait($status);
} else {
    // handel fork error
}

如果这不是libcurl的错误,而是我做错了什么,请告诉我。

我刚刚通过使用pcntl_exec函数解决了这个问题。因此,您基本上会删除您的子代码,并将其放在不同的php文件中,然后在子进程中执行(并实际替换)。

$ch = curl_init('https://www.google.ca/');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$success = curl_exec($ch);
var_dump($success !== false); // true
curl_close($ch); 
$pid = pcntl_fork();
if ($pid === 0) {
  pcntl_exec('child_curl.php');
} else if ($pid > 0) {
  // wait for child process
  pcntl_wait($status);
} else {
  // handel fork error
}

child_curl.php的内容:

$ch = curl_init('http://www.google.ca/');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$success = curl_exec($ch);
var_dump($success !== false); // true
curl_close($ch);
$ch = curl_init('https://www.google.ca/');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$success = curl_exec($ch);
var_dump($success !== false); // false
$errno = curl_errno($ch); // 35
$error = curl_error($ch); // SSL connect error
curl_close($ch);

在执行子项之后,您将不会看到错误。

这些其他问题帮助了我:

  • fork()后出现libCurl SSL错误
  • 卷曲和pcntl_fork()

另一个解决方法是使用套接字。

function fetch_page($url) {
    $socketPair = array();
    if (socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $socketPair) === false) {
        return false;
    }
    $pid = pcntl_fork();
    if ($pid === 0) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $content = curl_exec($ch);
        curl_close($ch);
        socket_close($socketPair[1]);
        socket_set_nonblock($socketPair[0]);
        $exitStatus = $content ? 0 : 1;
        if ($content) {
            while ((strlen($content) > 0) && ($wrote = socket_write($socketPair[0], $content))) {
                $content = substr($content, $wrote);
            }
        }
        socket_close($socketPair[0]);
        exit($exitStatus);
    }
    else if ($pid > 0) {
        pcntl_wait($status);
        socket_close($socketPair[0]);
        $content = false;
        if (pcntl_wexitstatus($status) === 0) {
            $content = '';
            while ($line = socket_read($socketPair[1], 4096)) {
                $len = strlen($content);
                $content .= $line;
            }
        }
        socket_close($socketPair[1]);
        return $content;
    }
    socket_close($socketPair[0]);
    socket_close($socketPair[1]);
    return false;
} 

我还没有找到直接处理问题的方法。然而,我已经制定了一些变通办法。两者都用(猜测是什么)另一个分叉来解决流程分叉问题

请注意:这不是为了提高性能。这只是问题中突出显示的问题的一种变通方法。

function fetch_page($page) {
    $tmp_file = tempnam(sys_get_temp_dir(), 'curl_tmp_file');
    $pid = pcntl_fork();
    if ($pid === 0) {
        $ch = curl_init($page);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $content = curl_exec($ch);
        curl_close($ch);
        if ($content) {
            file_put_contents($tmp_file, $content);
            exit(0);
        }
        else {
            exit(1);
        }
    }
    else if ($pid > 0) {
        pcntl_wait($status);
        $content = false;
        if (pcntl_wexitstatus($status) === 0) {
            $content = file_get_contents($tmp_file);
        }
        unlink($tmp_file);
        return $content;
    }
    else {
        unlink($tmp_file);
        return false;
    }
}

上的信件我可以在子进程中建立https连接。

fetch_page('https://www.google.ca/');
$pid = pcntl_fork();
if ($pid === 0) {   
    $ch = curl_init('https://www.google.ca/');
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $success = curl_exec($ch);
    var_dump($success !== false); // true
} else if ($pid > 0) {
    // wait for child process
    pcntl_wait($status);
} else {
    // handel fork error
}