使用 PHP Curl 的 FTPS 获取部分下载
FTPS with PHP Curl getting partial download
我在使用 php curl over ftps 和隐式 ssl 检索文件时遇到问题(如此处讨论:ftp_ssl_connect with implicit ftp over tls)。问题是 有时 - 大概有 5% 的时间,我以部分下载结束。
我的class或多或少改编自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;
}
}
我是这样使用的:
$ftps = new ftps('example.com','john_doe','123456');
$ftps->download('remote_filename','local_filename');
正如我上面提到的,这几乎完美地工作 除了 大约 5% 的结果是部分下载的文件。然后我检查远程服务器并能够验证文件确实完整存在 - 再次尝试脚本,它总是在第二次尝试时获取整个文件。
像这样使用 curl 会导致间歇性问题的原因是什么?我的下一步行动是实施某种校验和并继续下载尝试,直到所有内容都散列,但这感觉更像是一个草率的解决方法,而不是真正的解决方案,很高兴知道问题的真正根源。
curl 可能会注意到,并且 curl_error() 可能会报告它(作为 CURLE_PARTIAL_FILE 错误),但是您的代码完全忽略了该错误。而不是
if (curl_error($this->curlhandle)) {
return false;
} else {
尝试
if (curl_errno($this->curlhandle)) {
throw new \RuntimeException('curl error: '.curl_errno($this->curlhandle).': '.curl_error($this->curlhandle));
} else {
现在,如果 curl 下载失败,您应该会收到正确的错误消息。然而,为了给你调试的东西,我还建议添加一个 protected $curldebugfileh;
到 class 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']));
并添加到 __destruct:fclose($this->curldebugfileh);
现在您应该会在异常中获得关于损坏下载发生的情况的详细日志,这可能会揭示下载损坏的原因。
编辑:仔细阅读后,我发现您没有 __destruct,并且从不关闭 curl 句柄,因此正在泄漏内存。你也应该解决这个问题。添加 function __destruct(){curl_close($this->curlhandle);fclose($this->curldebugfileh);}
将防止 memory/resource 泄漏。
编辑 2:我看到你在做 $fp = fopen($local, 'w')
- 不要使用 w
,这会破坏你下载的所有东西,在某些操作系统上,比如微软 windows (虽然它不会对 linux 造成任何伤害..),使用 wb
,并且您的代码对于 windows 上的 运行 是安全的(以及其他操作系统,包括预OSX Mac、DOS、CP/M、OS/2、Symbian,可能还有其他)
编辑 3:您还泄漏了资源,因为您从不 fclose($fp); ,你或许也应该解决这个问题。
我在使用 php curl over ftps 和隐式 ssl 检索文件时遇到问题(如此处讨论:ftp_ssl_connect with implicit ftp over tls)。问题是 有时 - 大概有 5% 的时间,我以部分下载结束。
我的class或多或少改编自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;
}
}
我是这样使用的:
$ftps = new ftps('example.com','john_doe','123456');
$ftps->download('remote_filename','local_filename');
正如我上面提到的,这几乎完美地工作 除了 大约 5% 的结果是部分下载的文件。然后我检查远程服务器并能够验证文件确实完整存在 - 再次尝试脚本,它总是在第二次尝试时获取整个文件。
像这样使用 curl 会导致间歇性问题的原因是什么?我的下一步行动是实施某种校验和并继续下载尝试,直到所有内容都散列,但这感觉更像是一个草率的解决方法,而不是真正的解决方案,很高兴知道问题的真正根源。
curl 可能会注意到,并且 curl_error() 可能会报告它(作为 CURLE_PARTIAL_FILE 错误),但是您的代码完全忽略了该错误。而不是
if (curl_error($this->curlhandle)) {
return false;
} else {
尝试
if (curl_errno($this->curlhandle)) {
throw new \RuntimeException('curl error: '.curl_errno($this->curlhandle).': '.curl_error($this->curlhandle));
} else {
现在,如果 curl 下载失败,您应该会收到正确的错误消息。然而,为了给你调试的东西,我还建议添加一个 protected $curldebugfileh;
到 class 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']));
并添加到 __destruct:fclose($this->curldebugfileh);
现在您应该会在异常中获得关于损坏下载发生的情况的详细日志,这可能会揭示下载损坏的原因。
编辑:仔细阅读后,我发现您没有 __destruct,并且从不关闭 curl 句柄,因此正在泄漏内存。你也应该解决这个问题。添加 function __destruct(){curl_close($this->curlhandle);fclose($this->curldebugfileh);}
将防止 memory/resource 泄漏。
编辑 2:我看到你在做 $fp = fopen($local, 'w')
- 不要使用 w
,这会破坏你下载的所有东西,在某些操作系统上,比如微软 windows (虽然它不会对 linux 造成任何伤害..),使用 wb
,并且您的代码对于 windows 上的 运行 是安全的(以及其他操作系统,包括预OSX Mac、DOS、CP/M、OS/2、Symbian,可能还有其他)
编辑 3:您还泄漏了资源,因为您从不 fclose($fp); ,你或许也应该解决这个问题。