这是一篇由 Simon Speich 撰写的客座文章。Simon 是一位 Web 开发人员,相信 Web 标准,并且从 Mozilla 0.8 开始就热爱 Mozilla(!)。
今天,Simon 正在试验 File API 和 Firefox 4 中引入的新 **Slice()** 方法。以下是他如何在文件上传器中实现 **恢复上传** 功能。
文件上传是使用 XHR Level2 对象 完成的。它提供了不同的方法和事件来处理请求(例如,发送数据和监视其进度)以及处理响应(例如,检查上传是否成功或发生错误)。有关更多信息,请阅读 如何开发 HTML5 图片上传器。
不幸的是,XHR 对象没有提供暂停和恢复上传的方法。但是,可以通过将新的 File API 的 slice() 方法与 XHR 的 abort() 方法结合使用来实现该功能。让我们看看如何实现。
实时演示
您可以查看 实时文件上传器演示 或 从 github.com 下载 JavaScript 和 PHP 代码。
暂停和恢复上传
这个想法是为用户提供一个按钮来暂停正在进行的上传,并在稍后恢复它。暂停请求很简单。只需使用 abort() 方法中止请求。确保您的用户界面不会将其报告为错误。
更难的部分是恢复上传,因为请求已中止且连接已关闭。我们不重新发送整个文件,而是使用 blob 的 mozSlice() 方法首先创建一个包含文件剩余部分的块。然后,我们创建新的请求,发送块,并将其附加到请求中止之前已保存在服务器上的部分。
创建块
可以这样创建块:
var chunk = file.mozSlice(start, end);
我们只需要知道从哪里开始切片,也就是已经上传的字节数。最简单的方法是在我们中止请求之前保存 ProgressEvent 的 loaded
属性。但是,此数字不一定与服务器上写入的字节数完全相同。最可靠的方法是在我们再次上传之前,发送一个额外的请求以从服务器获取部分写入的文件的大小。然后,可以使用此信息对文件进行切片并创建块。
总结上述事件链
(假设上传已在进行中)
- 用户暂停上传
- UI 状态设置为暂停
- 上传被中止
- 服务器停止将文件写入磁盘
- 用户恢复上传
- UI 状态设置为恢复中
- 从服务器获取部分写入的文件大小
- 将文件切片成剩余部分(块)
- 上传块
- UI 状态设置为上传中
- 服务器附加数据
JavaScript 代码
// Assuming that the request to fetch the already written bytes has just
// taken place and xhr.result contains the response from the server.
var start = xhr.result.numWrittenBytes;
var chunk = file.mozSlice(start, file.size);
var req = new XMLHttpRequest();
req.open('post', 'fnc.php?fnc=resume', true);
req.setRequestHeader("Cache-Control", "no-cache");
req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
req.setRequestHeader("X-File-Name", file.name);
req.setRequestHeader("X-File-Size", file.size);
req.send(chunk);
PHP 代码
服务器端处理正常上传和恢复上传之间的唯一区别在于,在后一种情况下,您需要附加到文件而不是创建文件。
$headers = getallheaders();
$protocol = $_SERVER[‘SERVER_PROTOCOL’];
$fnc = isset($_GET['fnc']) ? $_GET['fnc'] : null;
$file = new stdClass();
$file->name = basename($headers['X-File-Name']));
$file->size = $headers['X-File-Size']);
// php://input bypasses the php.ini settings, so we have to limit the file size ourselves:
$maxUpload = getBytes(ini_get('upload_max_filesize'));
$maxPost = getBytes(ini_get('post_max_size'));
$memoryLimit = getBytes(ini_get('memory_limit'));
$limit = min($maxUpload, $maxPost, $memoryLimit);
if ($headers['Content-Length'] > $limit) {
header($protocol.' 403 Forbidden');
exit('File size to big. Limit is '.$limit. ' bytes.');
}
$file->content = file_get_contents(’php://input’);
$flag = ($fnc == ‘resume’ ? FILE_APPEND : 0);
file_put_contents($file->name, $file->content, $flag);
function getBytes($val) {
$val = trim($val);
$last = strtolower($val[strlen($val) - 1]);
switch ($last) {
case 'g': $val *= 1024;
case 'm': $val *= 1024;
case 'k': $val *= 1024;
}
return $val;
}
注意!
上面的 PHP 代码示例没有进行任何安全检查。用户可以发送和写入任何类型的文件到您的磁盘,或者附加到或甚至覆盖您的任何文件。因此,在启用网站上传功能时,请务必采取适当的安全措施。
错误后恢复上传
暂停和恢复的事件序列也可用于在网络错误后继续上传。与其尝试重新上传整个文件,不如从服务器获取已写入的文件大小,并将文件首先切片成一个新的块。
关于恢复暂停或中断的文件上传的说明
将块附加到文件可能会创建损坏的文件,因为您无法控制服务器在请求中止后写入的内容——如果它确实写入任何内容。
浏览器崩溃后恢复上传
您可以将暂停和恢复功能更进一步。至少在理论上,即使在浏览器意外关闭或崩溃后,也可以恢复上传。问题在于,浏览器关闭后,读取到内存的文件对象会丢失。用户必须首先重新选择或拖动文件,然后才能对文件进行切片以恢复上传。
相反,您可以使用新的 IndexedDB API 并在进行任何上传之前存储文件。然后,在浏览器崩溃后,从数据库加载文件,切片成剩余的块并恢复上传。
关于 Simon Speich
Simon Speich 是一位 Web 开发人员,相信 Web 标准,并且从 Mozilla 0.8 开始就热爱 Mozilla。他还热衷于 摄影。您可以在他的网站 www.speich.net 上了解更多关于他的信息。
关于 Paul Rouget
Paul 是一位 Firefox 开发人员。
18 条评论