其他人已经指出,在任何正确配置的生产 PHP 服务器上都会遇到一些限制。启动的内存、帖子和文件最大值。另外,httpd 服务通常也会限制这些。
上传如此大的文件的答案是将文件切成块,将每个块发送到不同的 put 或 post 中(取决于浏览器)。
已经存在一个能够进行块文件上传的库,因此我将使用它作为示例。为了支持分块上传,上传处理程序使用 Content-Range 标头,该标头由插件为每个块传输。
UploadHandler 类中的handle_file_upload 函数是一个很好的示例,说明如何使用 PHP 在服务器端处理分块文件上传。 --https://github.com/blueimp/jQuery-File-Upload/blob/master/server/php/UploadHandler.php
function handle_file_upload($uploaded_file, $name, $size, $type, $error,
$index = null, $content_range = null)
该函数接受参数$content_range = null
它在 HTTP 标头中传递到服务器,并从$_SERVER['HTTP_CONTENT_RANGE'];
稍后我们需要确定是否会将文件上传附加到已存在的文件中,因此我们设置一个变量。如果 HTTP 请求报告的文件大小大于服务器上的实际文件大小,则$content_range
变量不为 NULL 并且文件存在,我们需要将此上传附加到现有文件。
$append_file = $content_range && is_file($file_path) &&
$file->size > $this->get_file_size($file_path);
伟大的!怎么办?
所以现在我们需要知道我们如何接收数据。旧版本的 Firefox 无法使用 multipart/formdata (POST) 进行分块文件上传。客户端和服务器端都需要以不同的方式处理这些请求。
if ($uploaded_file && is_uploaded_file($uploaded_file)) {
// multipart/formdata uploads (POST method uploads)
if ($append_file) {
// append to the existing file
file_put_contents(
$file_path,
fopen($uploaded_file, 'r'),
FILE_APPEND
);
} else {
// this is a new chunked upload OR a completed single part upload,
// so move the file from the temp directory to the uploads directory.
move_uploaded_file($uploaded_file, $file_path);
}
}
根据文档:仅支持 XHR 文件上传和 Blob API 的浏览器支持分块文件上传,其中包括 Google Chrome 和 Mozilla Firefox 4+ -https://github.com/blueimp/jQuery-File-Upload/wiki/Chunked-file-uploads
为了使分块上传在 Mozilla Firefox 4-6(Firefox 7 之前支持 XHR 上传的 Firefox 版本)中工作,multipart 选项也必须设置为 false。这是在服务器端处理这些情况的代码。
else {
// Non-multipart uploads (PUT method support)
file_put_contents(
$file_path,
fopen('php://input', 'r'),
$append_file ? FILE_APPEND : 0
);
}
最后我们可以验证下载是否完成,或者放弃取消的上传。
$file_size = $this->get_file_size($file_path, $append_file);
if ($file_size === $file->size) {
$file->url = $this->get_download_url($file->name);
if ($this->is_valid_image_file($file_path)) {
$this->handle_image_file($file_path, $file);
}
} else {
$file->size = $file_size;
if (!$content_range && $this->options['discard_aborted_uploads']) {
unlink($file_path);
$file->error = $this->get_error_message('abort');
}
}
在客户端,您需要跟踪块。每个部分发布后,我们发送下一部分,直到没有更多的块剩下。示例库是 jQuery 的插件,这使得它非常简单。像您一样使用裸 XHR 对象将需要更多代码。它可能看起来像这样:
var chunksize = 1000000 // 1MB
var chunks = math.ceil(chunksize / fileToUpload.fileSize);
function uploadChunk(fileToUpload, chunk = 0) {
var xhr = new XMLHttpRequest();
var uploadStatus = xhr.upload;
uploadStatus.addEventListener("progress", function (ev) {
if (ev.lengthComputable) {
$("#uploadPercentage").html((ev.loaded / ev.total) * 100 + "%");
}
}, false);
uploadStatus.addEventListener("error", function (ev) {$("#error").html(ev)}, false);
uploadStatus.addEventListener("load", function (ev) {$("#error").html("APPOSTO!")}, false);
var start = chunksize*chunk;
var end = start+(chunksize-1)
if (end >= fileToUpload.fileSize) {
end = fileToUpload.fileSize-1;
}
xhr.open(
"POST",
"serverUpload.php",
true
);
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("Content-Type", "multipart/form-data");
xhr.setRequestHeader("X-File-Name", fileToUpload.fileName);
xhr.setRequestHeader("X-File-Size", fileToUpload.fileSize);
xhr.setRequestHeader("X-File-Type", fileToUpload.type);
xhr.setRequestHeader("Content-Range", start+"-"+end+"/"+fileToUpload.fileSize);
xhr.send(fileToUpload);
}
for(c = 0; c < chunks; c++) {
uploadChunk(fileToUpload, c);
}
循环遍历块,依次上传每个块范围。请注意,Content-Range 标头值的格式为起点/终点/大小。范围从 0 开始,所以"end"最多只能小于 1"size"。您可以使用范围"start-"指示范围从以下位置延伸到文件末尾"start".
EDIT:
只是认为这可以在服务器上实现进度条,否则单个文件上传是不可能的。由于您知道每个块的大小以及每个请求的状态,因此您可以在每次循环运行时相应地更新状态栏。
另外值得注意的是某些浏览器的限制。 Chrome 和 Firefox 应该能够处理 4GB 的文件,但低于 9 的 IE 版本有一个错误,无法处理大于 2GB 的文件。