如何通过 PHP 将非常大的文件从 URL 复制到服务器?
How to copy very large files from URL to server via PHP?
我使用以下代码将文件从外部服务器(通过 URL 的任何服务器)copy/download 传送到我的托管 Web 服务器(默认设置为 Dreamhost 共享托管)。
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<form method="post" action="copy.php">
<input type="submit" value="click" name="submit">
</form>
</body>
</html>
<!-- copy.php file contents -->
<?php
function chunked_copy() {
# 1 meg at a time, adjustable.
$buffer_size = 1048576;
$ret = 0;
$fin = fopen("http://www.example.com/file.zip", "rb");
$fout = fopen("file.zip", "w");
while(!feof($fin)) {
$ret += fwrite($fout, fread($fin, $buffer_size));
}
fclose($fin);
fclose($fout);
return $ret; # return number of bytes written
}
if(isset($_POST['submit']))
{
chunked_copy();
}
?>
但是,该函数会在大约 2.5GB(有时是 2.3GB,有时是 2.7GB,等等)的文件下载完成后停止 运行。每次我执行此功能时都会发生这种情况。较小的文件 (<2GB) 很少会出现此问题。我相信源没有问题,因为我单独将文件完美地下载到我的家用电脑上。
有人可以解决这个问题并向我解释一下吗?我对编程很陌生。
此外,
file_put_contents("Tmpfile.zip", fopen("http://example.com/file.zip", 'r'));
也表现出类似的症状。
也许你可以试试 curl 下载文件。
function downloadUrlToFile($url, $outFileName)
{
//file_put_contents($xmlFileName, fopen($link, 'r'));
//copy($link, $xmlFileName); // download xml file
if(is_file($url)) {
copy($url, $outFileName); // download xml file
} else {
$options = array(
CURLOPT_FILE => fopen($outFileName, 'w'),
CURLOPT_TIMEOUT => 28800, // set this to 8 hours so we dont timeout on big files
CURLOPT_URL => $url
);
$ch = curl_init();
curl_setopt_array($ch, $options);
curl_exec($ch);
curl_close($ch);
}
}
解释:也许吧。 Remidy:应该不会。
可能是PHP的限制造成的:the manual on the filesize function在return值的部分提到:
Note: Because PHP's integer type is signed and many platforms use 32bit integers, some filesystem functions may return unexpected results for files which are larger than 2GB.
似乎 fopen
功能可能会导致此问题,因为已添加(1, 2)关于该主题的两条评论(尽管已修改)。
您似乎需要从源代码编译 PHP(使用 CFLAGS="-D_FILE_OFFSET_BITS=64"
标志)以启用大文件 (>2GB),但它可能会破坏一些其他功能。
由于您使用的是共享历史记录:我猜您运气不好。
对不起...
由于问题发生在(目前)未知和未定义的文件大小,也许最好尝试一种变通方法。如果您只是关闭并在一些字节后重新打开输出文件怎么办?
function chunked_copy() {
# 1 meg at a time, adjustable.
$buffer_size = 1048576;
# 1 GB write-chuncks
$write_chuncks = 1073741824;
$ret = 0;
$fin = fopen("http://www.example.com/file.zip", "rb");
$fout = fopen("file.zip", "w");
$bytes_written = 0;
while(!feof($fin)) {
$bytes = fwrite($fout, fread($fin, $buffer_size));
$ret += $bytes;
$bytes_written += $bytes;
if ($bytes_written >= $write_chunks) {
// (another) chunck of 1GB has been written, close and reopen the stream
fclose($fout);
$fout = fopen("file.zip", "a"); // "a" for "append"
$bytes_written = 0; // re-start counting
}
}
fclose($fin);
fclose($fout);
return $ret; # return number of bytes written
}
重新打开应该是附加模式,它将写指针(没有读指针)放在文件的末尾,而不是覆盖之前写入的字节。
这不会解决任何操作系统级别或文件系统级别的问题,但它可以解决写入文件时 PHP 内部的任何计数问题。
也许这个技巧也可以(或应该)应用于阅读端,但我不确定你是否可以对下载进行搜索操作...
请注意,任何整数溢出(如果您使用的是 32 位,则超出 2147483647)都应该通过强制转换为浮点数来透明地解决,因此这应该不是问题。
编辑:计算实际写入的字节数,而不是块大小
我认为问题可能出在许多服务器 运行 PHP 脚本上的 30 秒超时。
PHP 脚本 运行 通过 cron 或 shell 不会有这个问题所以也许你可以找到一种方法来做到这一点。
或者,您可以将 set_time_limit([所需时间]) 添加到代码的开头。
您在 30 秒后超时,可能是由于 PHP(默认 max_execution_time
= 30 秒)造成的。您可以尝试将其设置为更长的时间:
ini_set('max_execution_time', '300');
但是,有一些注意事项:
如果脚本在安全模式下是运行,你不能用ini_set
设置max_execution_time
(我找不到Dreamhost是否开启或关闭了安全模式在共享主机中,你需要问他们,或者试试这个)。
Web 服务器也可能有执行限制。 Apache 的默认值为 300s(IIS 也是如此,但鉴于 Dreamhost 提供 'full unix shell',Apache 比 IIS 更有可能)。但是对于 5GB 的文件大小,这应该可以帮助您。
我使用以下代码将文件从外部服务器(通过 URL 的任何服务器)copy/download 传送到我的托管 Web 服务器(默认设置为 Dreamhost 共享托管)。
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<form method="post" action="copy.php">
<input type="submit" value="click" name="submit">
</form>
</body>
</html>
<!-- copy.php file contents -->
<?php
function chunked_copy() {
# 1 meg at a time, adjustable.
$buffer_size = 1048576;
$ret = 0;
$fin = fopen("http://www.example.com/file.zip", "rb");
$fout = fopen("file.zip", "w");
while(!feof($fin)) {
$ret += fwrite($fout, fread($fin, $buffer_size));
}
fclose($fin);
fclose($fout);
return $ret; # return number of bytes written
}
if(isset($_POST['submit']))
{
chunked_copy();
}
?>
但是,该函数会在大约 2.5GB(有时是 2.3GB,有时是 2.7GB,等等)的文件下载完成后停止 运行。每次我执行此功能时都会发生这种情况。较小的文件 (<2GB) 很少会出现此问题。我相信源没有问题,因为我单独将文件完美地下载到我的家用电脑上。
有人可以解决这个问题并向我解释一下吗?我对编程很陌生。
此外,
file_put_contents("Tmpfile.zip", fopen("http://example.com/file.zip", 'r'));
也表现出类似的症状。
也许你可以试试 curl 下载文件。
function downloadUrlToFile($url, $outFileName)
{
//file_put_contents($xmlFileName, fopen($link, 'r'));
//copy($link, $xmlFileName); // download xml file
if(is_file($url)) {
copy($url, $outFileName); // download xml file
} else {
$options = array(
CURLOPT_FILE => fopen($outFileName, 'w'),
CURLOPT_TIMEOUT => 28800, // set this to 8 hours so we dont timeout on big files
CURLOPT_URL => $url
);
$ch = curl_init();
curl_setopt_array($ch, $options);
curl_exec($ch);
curl_close($ch);
}
}
解释:也许吧。 Remidy:应该不会。
可能是PHP的限制造成的:the manual on the filesize function在return值的部分提到:
Note: Because PHP's integer type is signed and many platforms use 32bit integers, some filesystem functions may return unexpected results for files which are larger than 2GB.
似乎 fopen
功能可能会导致此问题,因为已添加(1, 2)关于该主题的两条评论(尽管已修改)。
您似乎需要从源代码编译 PHP(使用 CFLAGS="-D_FILE_OFFSET_BITS=64"
标志)以启用大文件 (>2GB),但它可能会破坏一些其他功能。
由于您使用的是共享历史记录:我猜您运气不好。
对不起...
由于问题发生在(目前)未知和未定义的文件大小,也许最好尝试一种变通方法。如果您只是关闭并在一些字节后重新打开输出文件怎么办?
function chunked_copy() {
# 1 meg at a time, adjustable.
$buffer_size = 1048576;
# 1 GB write-chuncks
$write_chuncks = 1073741824;
$ret = 0;
$fin = fopen("http://www.example.com/file.zip", "rb");
$fout = fopen("file.zip", "w");
$bytes_written = 0;
while(!feof($fin)) {
$bytes = fwrite($fout, fread($fin, $buffer_size));
$ret += $bytes;
$bytes_written += $bytes;
if ($bytes_written >= $write_chunks) {
// (another) chunck of 1GB has been written, close and reopen the stream
fclose($fout);
$fout = fopen("file.zip", "a"); // "a" for "append"
$bytes_written = 0; // re-start counting
}
}
fclose($fin);
fclose($fout);
return $ret; # return number of bytes written
}
重新打开应该是附加模式,它将写指针(没有读指针)放在文件的末尾,而不是覆盖之前写入的字节。
这不会解决任何操作系统级别或文件系统级别的问题,但它可以解决写入文件时 PHP 内部的任何计数问题。
也许这个技巧也可以(或应该)应用于阅读端,但我不确定你是否可以对下载进行搜索操作...
请注意,任何整数溢出(如果您使用的是 32 位,则超出 2147483647)都应该通过强制转换为浮点数来透明地解决,因此这应该不是问题。
编辑:计算实际写入的字节数,而不是块大小
我认为问题可能出在许多服务器 运行 PHP 脚本上的 30 秒超时。
PHP 脚本 运行 通过 cron 或 shell 不会有这个问题所以也许你可以找到一种方法来做到这一点。
或者,您可以将 set_time_limit([所需时间]) 添加到代码的开头。
您在 30 秒后超时,可能是由于 PHP(默认 max_execution_time
= 30 秒)造成的。您可以尝试将其设置为更长的时间:
ini_set('max_execution_time', '300');
但是,有一些注意事项:
如果脚本在安全模式下是运行,你不能用
ini_set
设置max_execution_time
(我找不到Dreamhost是否开启或关闭了安全模式在共享主机中,你需要问他们,或者试试这个)。Web 服务器也可能有执行限制。 Apache 的默认值为 300s(IIS 也是如此,但鉴于 Dreamhost 提供 'full unix shell',Apache 比 IIS 更有可能)。但是对于 5GB 的文件大小,这应该可以帮助您。