Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

PUT方法文件分块上传(PHP+Apache实现)

  之前的文章中已经介绍过了使用PUT方法和Content-Range头来做文件分块上传的相关思路 ,只是当时没有给出具体的实现代码。现在使用PHP+Apache来实现这个思路。为了让代码更易读,前端使用了ES6的Promise特性,所以请使用现代浏览器测试。
  首先,要配置一下Apache的.htaccess,把对一个目录的PUT请求转发给一个程序来处理。
RewriteCond %{REQUEST_METHOD} ^PUT$
RewriteRule ^test/(\w{32})$ /test.php?md5=$1
  test是目录名称,匹配所有文件名为32个\w字符的文件,交给test.php处理。接着是程序部分,这里为了方便测试,把PHP代码和HTML代码写在同一个文件里了,以下就是 test.php <?
@apache_setenv('no-gzip',1);
@ini_set('zlib.output_compression','Off');
set_time_limit(0);
ignore_user_abort();
ob_end_clean();
if($_SERVER['REQUEST_METHOD']=='PUT'){
  //计算文件路径和临时文件路径
  $md5=$_GET['md5'];
  @mkdir('test');
  $temp=sys_get_temp_dir()."/$md5";
  $path="test/$md5";
  //如果文件已存在则返回304代码
  if(file_exists($path))
    header('HTTP/1.1 304 Not Modified') or die;
  //创建并根据Content-Range的位置写入文件
  touch($temp);
  $range=$_SERVER['HTTP_CONTENT_RANGE'];
  preg_match('/(\d+)-(\d+)/',$range,$range);
  $file=fopen($temp,'r+');
  fseek($file,$range[1]);
  $data=file_get_contents('php://input');
  fwrite($file,$data,$range[2]-$range[1]);
  fclose($file);
  //判断MD5是否正确
  //如果正确则将临时文件移动到正式文件路径上
  //并返回201代码
  if($md5==md5_file($temp)){
    header('HTTP/1.1 201 Created');
    copy($temp,$path);
    unlink($temp);
    die;
  };
  //其它情况返回202代码,并断开客户端连接
  header('HTTP/1.1 202 Accepted');
  header('Content-Length: 0');
  header('Connection: Close');
  flush();
  //30秒后判断整个上传是否成功,如果失败则删除临时文件
  sleep(30);
  file_exists($path) or unlink($temp);
  die;
};
?>
<script src="http://www.web-tinker.com/share/md5.js"></script>
<input type="file" />
<script>
document.querySelector("input").addEventListener("change",function(e){
  new Promise(function(resolve){ //加载文件
    var fr=new FileReader;
    fr.addEventListener("load",resolve);
    fr.readAsArrayBuffer(e.target.files[0]);
  }).then(function(e){ //计算MD5,获取服务器文件状态
    var data=new Uint8Array(e.target.result);
    var hex=Array.prototype.map.call(md5(data),function(e){
      return (e<16?"0":"")+e.toString(16);
    }).join("");
    var url="/test/"+hex;
    return new Promise(function(resolve){
      var xhr=new XMLHttpRequest;
      xhr.data=data;
      xhr.url=url;
      xhr.addEventListener("load",resolve);
      xhr.open("HEAD",url,true);
      xhr.addEventListener("error",function(){});
      xhr.send();
    });
  }).then(function(e){ //文件分块上传
    var data=e.target.data;
    var url=e.target.url;
    //如果服务器上已存在该文件则不重复上传
    if(e.target.status!=404)return Promise.resolve([
      {target:{status:201,url:url}}
    ]);
    var requests=[];
    var BLOCKSIZE=10240;
    for(var i=0;i<data.length;i+=BLOCKSIZE){
      requests.push(new Promise(function callee(i,resolve){
        var xhr=new XMLHttpRequest;
        xhr.open("PUT",url,true);
        max=Math.min(i+BLOCKSIZE+1,data.length);
        xhr.setRequestHeader("Content-Range","Bytes "+i+"-"+max);
        xhr.timeout=5000;
        xhr.url=url;
        xhr.addEventListener("timeout",callee.bind(null,i,resolve));
        xhr.addEventListener("load",resolve);
        xhr.send(new Uint8Array(data.buffer.slice(i,max)));
      }.bind(null,i)));
    };
    return Promise.all(requests);
  }).then(function(s){ //处理响应
    return new Promise(function(resolve){
      s.forEach(function(e){
        if(e.target.status==201)resolve(e.target.url);
      });
    });
  }).then(function(url){ //完成上传
    console.log(url);
  });
});
</script>
网名:
3.80.55.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^