基于AJAX/PHP的上传,带有进度条,适用于大型文件

基于AJAX/PHP的上传,带有进度条,适用于大型文件,php,ajax,file,upload,progress,Php,Ajax,File,Upload,Progress,我一直在尝试创建一个非flash上传面板,其中还显示一个进度条。 在我们的服务器上,我们有PHP5.3(目前无法升级到5.4,因此不能使用新的上传进度功能=>)。 我们不能使用基于flash的解决方案、扩展或类似产品 因此,我尝试将XMLHttpRequest与AJAX结合使用。 这里的问题是我只取得了部分成功 我已经设法上传并在服务器上保存了一个大约380MB的文件,但是,当尝试使用更大的文件(如4GB)时,它不会保存在服务器上(如果我在某一点上与Firebug进行检查,它会说“POST ab

我一直在尝试创建一个非flash上传面板,其中还显示一个进度条。 在我们的服务器上,我们有PHP5.3(目前无法升级到5.4,因此不能使用新的上传进度功能=>)。 我们不能使用基于flash的解决方案、扩展或类似产品

因此,我尝试将XMLHttpRequest与AJAX结合使用。 这里的问题是我只取得了部分成功

我已经设法上传并在服务器上保存了一个大约380MB的文件,但是,当尝试使用更大的文件(如4GB)时,它不会保存在服务器上(如果我在某一点上与Firebug进行检查,它会说“POST aborted”)

另一件奇怪的事情是,对于同一个文件,xhr.upload.loaded从xhr.upload.total的相同维度开始计算

有人知道如何解决这个问题,或者有其他的解决方案吗

客户端代码为:


函数uploadToServer()
{
fileField=document.getElementById(“uploadedFile”);
var fileToUpload=fileField.files[0];
var xhr=new XMLHttpRequest();
var uploadStatus=xhr.upload;
uploadStatus.addEventListener(“进度”,函数(ev){
if(电动长度可计算){
$(“#上传百分比”).html((每辆装载/每辆总计)*100+“%”;
}
},假);
uploadStatus.addEventListener(“error”,函数(ev){$(“#error”).html(ev)},false);
uploadStatus.addEventListener(“加载”,函数(ev){$(“#错误”).html(“APPOSTO!”),false);
xhr.open(
“职位”,
“serverUpload.php”,
真的
);
setRequestHeader(“缓存控制”、“无缓存”);
setRequestHeader(“内容类型”、“多部分/表单数据”);
setRequestHeader(“X-File-Name”,fileToUpload.fileName);
setRequestHeader(“X-File-Size”,fileToUpload.fileSize);
setRequestHeader(“X-File-Type”,fileToUpload.Type);
//setRequestHeader(“内容类型”、“应用程序/八位字节流”);
xhr.send(fileToUpload);
}
$(函数(){
$(“#上载按钮”)。单击(上载到服务器);
});
HTML部分:


服务器端代码:



与web服务器相关的限制是PHP无法更改的。例如,在IIS中,它们的默认最大post请求大小为30MB…还有一个您可能遇到的最大超时。与大小无关,但您的post请求需要多长时间…即提交文件需要多长时间。这两种设置都可以受IIS或Apache的约束。

我正在写一篇关于xhr.upload.loaded的奇怪行为的文章,它从一个大数字开始

我也有类似的问题,我找不到原因。 唯一可能有帮助的线索是,依靠ISP,问题有时会消失!
例如,当我在家测试时,它工作正常,我没有看到这种奇怪的行为,但在工作互联网上,问题仍然存在。

您可以将代码与本教程进行比较。本教程可以上载任何大小的文件。它与您的代码非常相似。 file\u get\u contents()获取文件的内容,并使用内部指针将其放入RAM的缓冲区中。 如果您没有足够的ram,或者使用32位版本的apache/php,当试图分配太多内存时,它可能会崩溃

您可能想试试这样的方法:

$upload = fopen("php://input", "r");
while (!feof($upload)) {
    file_put_contents($path . $filename, fread($upload, 4096), FILE_APPEND);
}
fclose($upload);

欢呼声

其他人已经指出,在任何正确配置的生产PHP服务器上都会遇到一些限制。要启动的内存、post和文件最大值。此外,httpd服务通常也会限制这些功能

对于如此大的上传量,解决办法是将文件切成块,用不同的put或post(取决于浏览器)发送每个块

有一个已经存在的库能够上传区块文件,所以我将以它为例。为了支持分块上传,上传处理程序使用contentrange报头,该报头由插件为每个分块传输

UploadHandler类中的handle_file_upload函数是如何使用PHP在服务器端处理分块文件上载的一个很好的示例。-

该函数采用参数
$content\u range=null
,该参数在HTTP头中传递给服务器,并从
$\u服务器['HTTP\u content\u range'检索

稍后,我们需要确定是否将文件上传附加到一个已经存在的文件,因此我们设置了一个变量。如果HTTP请求报告的文件大小大于服务器上的实际文件大小,则
$content\u range
变量不为空,并且文件存在,我们需要将此上载附加到现有文件

$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);
            }
        }
        else {
            // Non-multipart uploads (PUT method support)
            file_put_contents(
                $file_path,
                fopen('php://input', 'r'),
                $append_file ? FILE_APPEND : 0
            );
        }
根据文档:分块文件上传仅受支持XHR文件上传和Blob API的浏览器支持,包括Google Chrome和Mozilla Firefox 4+-

要在Mozilla Firefox 4-6(Firefox 7之前支持XHR上传的Firefox版本)中进行分块上传,还必须将multipart选项设置为false。下面是在服务器端处理这些情况的代码

        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);
            }
        }
        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的一个插件,它使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);
}
var chunksize=1000000//1MB
var chunks=math.ceil(ch
        $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');
            }
        }
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);
}
<form enctype="multipart/form-data" method="post">
<input type="file" id="video_file" name="video_file" accept=".mp4, .avi, .mkv">
<input type="submit" class="btn btn-success" id="video-upload-btn" name="video_upload_btn" value="Upload">
<div class="video-bar">
    <span class="video-bar-fill" id="video-bar-fill-id"><span class="video-bar-fill-text" id="video-bar-fill-text-id"></span></span>
</div>
</form>
.video-bar{
    width: 100%;
    background: #eee;
    padding: 3px;
    margin-bottom: 10px;
    box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);
    border-radius: 3px;
    box-sizing: border-box;
}

.video-bar-fill{
    height: 20px;
    display: block;
    background: cornflowerblue;
    width: 0;
    border-radius: 3px;
    transition: width 0.8s ease;
}
.video-bar-fill-text{
    color: #fff;
    padding: 3px;
}
<script type="text/javascript">
    var app = app || {};

    (function(video_op){
        "use strict";

        var video_ajax, video_getFormData, video_setProgress;

        video_ajax = function(data){

            var xmlhttp = new XMLHttpRequest(), uploaded;

            xmlhttp.addEventListener('readystatechange', function(){
                if(this.readyState==4){
                    if(this.status==200){
                        uploaded = JSON.parse(this.response);
                        console.log(uploaded);

                        if(typeof video_op.options.finished==='function'){
                            video_op.options.finished(uploaded);
                        }
                    } else {
                        if(typeof video_op.options.error === 'function'){
                            video_op.options.error();
                        }
                    }
                }
            });

            xmlhttp.upload.addEventListener("progress", function(event){
                var percent;
                if(event.lengthComputable===true){
                    percent = Math.round((event.loaded / event.total) * 100);
                    video_setProgress(percent);
                }

            });

            if(video_op.options.videoProgressBar!==undefined){
                video_op.options.videoProgressBar.style.width=0;
            }
            if(video_op.options.videoProgressText!==undefined){
                video_op.options.videoProgressText.innerText=0;
            }

            xmlhttp.open("post", video_op.options.videoProcessor);
            xmlhttp.send(data);

        };

        video_getFormData = function(source1){
            var data = new FormData(), i;

            for(i=0;i<source1.length; i++){
                data.append('video_file', source1[i]);
            }

            data.append("ajax", true);

            return data;

        };

        video_setProgress = function(value){
            if(video_op.options.videoProgressBar!==undefined){
                video_op.options.videoProgressBar.style.width = value? value+"%":0;
            }
            if(video_op.options.videoProgressText!==undefined){
                video_op.options.videoProgressText.innerText=value?value+"%":0;
            }
        };

        video_op.videouploader = function(options){
            video_op.options = options;

            if(video_op.options.videoFiles !== undefined){
                var videoFormDataValue = video_getFormData(video_op.options.videoFiles.files);

                video_ajax(videoFormDataValue);
            }
        }

    }(app));

    document.getElementById("video-upload-btn").addEventListener("click", function(e){
        e.preventDefault();

        document.getElementById("video-upload-btn").setAttribute("disabled", "true");

        var videof = document.getElementById('video_file'),
            videopb = document.getElementById('video-bar-fill-id'),
            videopt = document.getElementById('video-bar-fill-text-id');

        app.videouploader({
            videoFiles: videof,
            videoProgressBar: videopb,
            videoProgressText: videopt,
            videoProcessor: "upload.php",

            finished: function(data){
                console.log(data);

            },

            error: function(){
                console.log("error");
            }
        });
    });
</script>
<?php
    if(!empty($_FILES["video_file"]))
    {
        if(!empty($_FILES["video_file"]["error"]))
        {
            if(move_uploaded_file($_FILES["video_file"]["tmp_name"], __DIR__."/".$_FILES["video_file"]["name"] ))
            {
                echo "success";
            }
            else
            {
                echo "failed";
            }
        }
        else
        {
            echo "error";
        }

    }
?>
Open php ini file - 
sudo nano /etc/php5/apache2/php.ini

Update these values-
post_max_size = 6000M
upload_max_filesize = 6000M

restart apache
sudo /etc/init.d/apache2 restart