3

我一直在尝试创建一个非 Flash 上传面板,该面板还显示一个进度条。在我们的服务器上,我们有 PHP 5.3(目前无法升级到 5.4,因此无法使用新的上传进度功能 => http://php.net/manual/en/session.upload-progress.php)。我们不能使用基于闪存的解决方案、扩展或类似的。

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

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

另一个奇怪的事情是,对于同一个文件,xhr.upload.loaded 以 xhr.upload.total 的相同维度开始,并从那里开始计数。

有谁知道如何解决这个问题或有替代解决方案?

客户端代码为:

<script type="application/javascript" src="jquery.js"></script>

<script type="application/javascript">

function uploadToServer()
{
    fileField = document.getElementById("uploadedFile");
    var fileToUpload = fileField.files[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);

    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-Type", "application/octet-stream");
        xhr.send(fileToUpload);
}



$(function(){

    $("#uploadButton").click(uploadToServer);

});


</script>

HTML部分:

<form action="" name="uploadForm" method="post" enctype="multipart/form-data">

  <input id="uploadedFile" name="fileField" type="file" multiple />

<input id="uploadButton" type="button" value="Upload!">

</form>

<div id="uploadPercentage"></div>
<div id="error"></div>

服务器端代码:

<?php

$path = "./";
$filename = $_SERVER['HTTP_X_FILE_NAME'];
$filesize = $_SERVER['CONTENT_LENGTH'];


$file = "log.txt";
$fo= fopen($file, "w");
fwrite($fo, $path . PHP_EOL);
fwrite($fo, $filename . PHP_EOL);
fwrite($fo, $filesize . PHP_EOL);
fwrite($fo, $path . $filename . PHP_EOL);

file_put_contents($path . $filename, 
file_get_contents('php://input')
);

?>
4

6 回答 6

4

其他人已经指出,在任何正确配置的生产 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/分块文件上传

要在 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 标头值的格式为start-end/size。范围从 0 开始,因此"end"只能是比"size"小 1 的最大值。您可以使用范围"start-"来指示范围从"start"延伸到文件末尾。

编辑:

只是认为这可以在服务器上实现一个进度条,否则单个文件上传是不可能的。由于您知道每个块的大小以及每个请求的状态,因此您可以在每次循环运行时相应地更新状态栏。

另外值得注意的是某些浏览器的限制。Chrome 和 Firefox 应该能够处理 4GB 的文件,但 IE 低于 9 的版本有一个错误,无法处理大于 2GB 的文件。

于 2014-05-08T14:16:19.600 回答
3

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

于 2012-05-09T03:35:30.503 回答
1

您可以将您的代码与本教程进行比较。本教程能够上传任何大小的文件。它与您的代码非常相似。 http://www.youtube.com/watch?v=pTfVK73CUk8

于 2012-11-07T10:26:55.863 回答
0

我正在写 xhr.upload.loaded 的奇怪行为,它以大量开头......

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

于 2012-05-19T13:09:53.293 回答
0

file_get_contents() 获取文件的内容,并使用内部指针将其放入 RAM 中的 BUFFER 中。如果您没有足够的内存,或者有 32 位版本的 apache/php,则在尝试分配过多内存时可能会崩溃。

您可能想尝试这样的事情:

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

干杯

于 2013-03-14T20:58:40.997 回答
0

我尝试使用 ajax 上传 4GB 的视频文件。这是成功的。这是我的代码。

HTML ::

<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>

CSS ::

.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";
        }

    }
?>

还要更改下面列出的 php ini 值。

  1. post_max_size
  2. 上传最大文件大小

如果您使用的是 linux/ubuntu - 请按照以下步骤操作

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
于 2015-10-23T10:26:19.277 回答