使用PHP Curl的FTPS获得部分下载

bil*_*oah 3 php ssl curl ftps

我有一个问题,使用php curl在ftps上使用隐式ssl检索文件(如下所述:ftp_ssl_connect,隐式ftp over tls).问题是有时候 - 大概有5%的时间,我最终会部分下载.

我的课程或多或少地改编自改编自Nico Westerdale的答案,以下是相关方法:

class ftps {

    private $server;
    private $username;
    private $password;
    private $curlhandle;
    public $dir = '/';

    public function __construct($server, $username, $password) {
        $this->server = $server;
        $this->username = $username;
        $this->password = $password;
        $this->curlhandle = curl_init();
    }

    private function common($remote) {
        curl_reset($this->curlhandle);
        curl_setopt($this->curlhandle, CURLOPT_URL, 'ftps://' . $this->server . '/' . $remote);
        curl_setopt($this->curlhandle, CURLOPT_USERPWD, $this->username . ':' . $this->password);
        curl_setopt($this->curlhandle, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($this->curlhandle, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($this->curlhandle, CURLOPT_FTP_SSL, CURLFTPSSL_TRY);
        curl_setopt($this->curlhandle, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_TLS);
        return $this->curlhandle;
    }

    public function download($filepath, $local = false) {
        $filename = basename($filepath);
        $remote   = dirname($filepath);
        if ($remote == '.') {
            $remote = $this->dir;
        }
        if ($local === false) {
            $local = $filename;
        }

        if ($fp = fopen($local, 'w')) {
            $this->curlhandle = self::common($remote . $filename);
            curl_setopt($this->curlhandle, CURLOPT_UPLOAD, 0);
            curl_setopt($this->curlhandle, CURLOPT_FILE, $fp);
            curl_exec($this->curlhandle);
            if (curl_error($this->curlhandle)) {
                return false;
            } else {
                return $local;
            }
        }
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

我这样使用它:

$ftps = new ftps('example.com','john_doe','123456');
$ftps->download('remote_filename','local_filename');
Run Code Online (Sandbox Code Playgroud)

正如我上面提到的,除了大约5%的时间结果是部分下载的文件之外,这几乎完美无缺.然后我检查远程服务器,并且能够验证文件确实存在于其中 - 再次尝试脚本,它总是在第二次尝试时获取整个文件.

什么会导致像这样使用卷曲的间歇性问题?我的下一步行动是实现某种校验和并继续下载尝试,直到所有的哈希值,但这感觉更像是一个草率的解决方案而不是真正的解决方案,并且知道问题的实际根源会很好.

han*_*rik 7

curl可能会注意到,curl_error()可能会报告它(作为CURLE_PARTIAL_FILE错误),但是你的代码完全忽略了这个错误.代替

        if (curl_error($this->curlhandle)) {
            return false;
        } else {
Run Code Online (Sandbox Code Playgroud)

尝试

        if (curl_errno($this->curlhandle)) {
            throw new \RuntimeException('curl error: '.curl_errno($this->curlhandle).': '.curl_error($this->curlhandle));
    } else {
Run Code Online (Sandbox Code Playgroud)

现在如果卷曲下载失败,你应该得到一个正确的错误.但是,为了给你一些调试的东西,我还建议你添加一个protected $curldebugfileh; 类ftps并在__construct中执行:curl_setopt_array($this->curlhandle,array(CURLOPT_VERBOSE=>true,CURLOPT_STDERR=>($this->curldebugfileh=tmpfile())));

然后将异常更改为:

            throw new \RuntimeException('curl error: '.curl_errno($this->curlhandle).': '.curl_error($this->curlhandle).' curl verbose log: '.file_get_contents(stream_get_meta_data($this->curldebugfileh)['uri']));
Run Code Online (Sandbox Code Playgroud)

并添加到__destruct: fclose($this->curldebugfileh);

现在你应该在关于损坏的下载发生的事件的异常中得到一个详细的日志,这可能会揭示下载被破坏的原因.

编辑:仔细阅读后,我发现你没有__destruct,并且从不关闭卷曲手柄,因此泄漏了内存.你也应该解决这个问题.添加function __destruct(){curl_close($this->curlhandle);fclose($this->curldebugfileh);} 会阻止内存/资源泄漏.

编辑2:我看到你正在做$fp = fopen($local, 'w')- 不要使用w,这将破坏你下载的所有内容,在某些操作系统上,如微软windows(它不会对linux造成任何伤害,但是..),使用wb和你的代码在Windows(和其他操作系统,包括OSX前Mac,DOS,CP/M,OS/2,Symbian,以及其他可能的其他操作系统)上运行是安全的

编辑3:你也在泄漏资源,因为你从未fclose($ fp); 你也应该解决这个问题.