js + php + apache 视频切片上传,无法上传48MB以上的文件。后续请求全部500,需要做哪些调整?

作者站长头像
站长
· 阅读数 21

这个问题与服务器或者后端的关系更大吧,不过现在是自己做小项目,没法找后端对线。(我自己就是...)

问题描述与相关代码

主要有参考这里 https://www.shuzhiduo.com/A/KE5QmG605L/文件上限2GB,文件切片每片为1MB,那么最多切片为2000片。前几次测试了小文件没遇到问题,上传更大些的文件后就发现,当上传完48片后,后续的所有请求都成500了。有换过不同的文件进行测试过,故目前只能上传48MB的文件。后续又尝试了修改切片大小,将切片修改为了10MB每块依旧会产生问题,只能上传4块了,就是40MB。那么现在的状况就是不能上传48MB以上的文件。修改apache和php配置有看过其他的回答尝试过,既然依旧有问题也许没改到点上吧。请教各位是否有办法与建议,如果网络上有这方面的讲解或者这个问题的其他解法,请麻烦告诉我也可以节省时间重复回答。辛苦了!

JS

function videoFileUpload(){
    var xhr = new XMLHttpRequest();
    var form_data = new FormData();
    //每片切片1MB
    const LENGTH = 1024 * 1024 * 1;
    var start = 0;
    var end = start + LENGTH;
    var blob;
    var blob_num = 1;
    var is_stop = 0;

    //开始上传
    this.start = function(){
        //开始上传
        var file = upload_video_file;
        blob = cutFile(file);
        sendFile(blob,file);
        blob_num += 1;
    }
    //停止上传
    this.stop = function () {
        //后续在这里发送一个请求提示后台删除缓存切片文件
        is_stop = 1;
    }

    //切割文件
    function cutFile(file){
        var file_blob = file.slice(start,end);
        start = end;
        end = start + LENGTH;
        return file_blob;
    };
    //发送文件
    function sendFile(blob,file){
        //只有处于不停止状态才执行
        if(is_stop == 0){
            var total_blob_num = Math.ceil(file.size / LENGTH);
            form_data.append('file',blob);
            form_data.append('blob_num',blob_num);
            form_data.append('total_blob_num',total_blob_num);
            form_data.append('file_name',file.name);

            //原本是异步的,但是这会导致请求取消,只能取消异步
            xhr.open('POST','https://www.xxx.com/api/Video_file_upload.php',false);
            xhr.onreadystatechange = function () {

                //进行下一次发送
                var t = setTimeout(function(){
                    //不处于停止状态时才执行
                    if(start < file.size){
                        blob = cutFile(file);
                        sendFile(blob,file);
                        blob_num += 1;
                    }
                    else{
                        setTimeout(t);
                    }
                },1000);
            }
            xhr.send(form_data);
        }
    }
}

php

<?php
//实例化并获取系统变量传参
$upload = new Upload($_FILES['file']['tmp_name'],$_POST['blob_num'],$_POST['total_blob_num'],$_POST['file_name']);
//调用方法,返回结果
$upload->apiReturn();

class Upload{
    private $filepath = '../source/video'; //上传目录
    private $tmpPath; //PHP文件临时目录
    private $blobNum; //第几个文件块
    private $totalBlobNum; //文件块总数
    private $fileName; //文件名

    public function __construct($tmpPath, $blobNum, $totalBlobNum, $fileName){
        $this->tmpPath = $tmpPath;
        $this->blobNum = $blobNum;
        $this->totalBlobNum = $totalBlobNum;
        $this->fileName = $fileName;

        $this->moveFile();
        $this->fileMerge();
    }

    //判断是否是最后一块,如果是则进行文件合成并且删除文件块
    private function fileMerge(){
        if($this->blobNum == $this->totalBlobNum){
            $blob = '';

            for($i=1; $i<= $this->totalBlobNum; $i++){
                $blob .= file_get_contents($this->filepath.'/'. $this->fileName.'__'.$i);
            }
            file_put_contents($this->filepath.'/'. $this->fileName,$blob);
            $this->deleteFileBlob();
        }
    }

    //删除文件块
    private function deleteFileBlob(){
        for($i=1; $i<= $this->totalBlobNum; $i++){
            @unlink($this->filepath.'/'. $this->fileName.'__'.$i);
        }
    }

    //移动文件
    private function moveFile(){
        $this->touchDir();
        $filename = $this->filepath.'/'. $this->fileName.'__'.$this->blobNum;
        move_uploaded_file($this->tmpPath,$filename);
    }

    //API返回数据
    public function apiReturn(){
        if($this->blobNum == $this->totalBlobNum){
            if(file_exists($this->filepath.'/'. $this->fileName)){
                $data['code'] = 2;
                $data['msg'] = 'success';
                $data['file_path'] = 'http://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['DOCUMENT_URI']).str_replace('.','',$this->filepath).'/'. $this->fileName;
            }
        }
        else{
            if(file_exists($this->filepath.'/'. $this->fileName.'__'.$this->blobNum)){
                $data['code'] = 1;
                $data['msg'] = 'waiting for all';
                $data['file_path'] = '';
            }
        }
        header('Content-type: application/json');
        echo json_encode($data);
    }

    //建立上传文件夹
    private function touchDir(){
        if(!file_exists($this->filepath)){
            return mkdir($this->filepath);
        }
    }
}

相关图片

网络请求,网络状态有关的图片

js + php + apache 视频切片上传,无法上传48MB以上的文件。后续请求全部500,需要做哪些调整?js + php + apache 视频切片上传,无法上传48MB以上的文件。后续请求全部500,需要做哪些调整?js + php + apache 视频切片上传,无法上传48MB以上的文件。后续请求全部500,需要做哪些调整?

成功被上传到服务器的文件,从图可见只能上传到第48片,之前上传成功的文件容量都小于48MB。

js + php + apache 视频切片上传,无法上传48MB以上的文件。后续请求全部500,需要做哪些调整?

日志

access.log

js + php + apache 视频切片上传,无法上传48MB以上的文件。后续请求全部500,需要做哪些调整?

ssl_request.log

js + php + apache 视频切片上传,无法上传48MB以上的文件。后续请求全部500,需要做哪些调整?

error.log

js + php + apache 视频切片上传,无法上传48MB以上的文件。后续请求全部500,需要做哪些调整?

php的日志没有报错(在5月27日有做过几次测试,但日志没有发生更新)

js + php + apache 视频切片上传,无法上传48MB以上的文件。后续请求全部500,需要做哪些调整?

回复
1个回答
avatar
test
2024-07-03

之前没有 Apache 的环境,看你上面一说,还真以为是 Apache 的问题,就没去复现,后面看到你补充说跟 FcgidMaxRequestLen,那肯定就不是 Apache 的问题了,抽时间专成搭了个 Apache 的环境,跑起来,然后发现是你前端代码的问题。

你前端代码里面,把 FormData 构造函数里面,FormData 只会在对象初始化的时候进行一次实例化,后续的 sendFile 里面就始终使用的是最开始的那一个 FormData 对象,而 FormData 的 append 方法又比较特殊,即使键名重复了,浏览器仍然会放进去,就导致你的每一个切片,都累计了前面切片的。

  • 切片 1

answer image

  • 切片 2

answer image

  • 切片 3

answer image

最终就导致了发送的大小远超了 Apache 的 FcgidMaxRequestLen 设定值,最后 Apache 响应了 500

answer image


切片上传的目的,就是为了在服务端设置较小的允许大小的情况下上传大体积的文件,而你这里直接去改 Apache 的配置,就跟切片上传没有关系了都

要修复这个 BUG,只需要把 new FormData 的操作放到 sendFile 内部去,或者将 append 改为 set ,另外 xhr 对象最好也是放到里面去重新初始化。

另外,你的 setTimeout 也存在 bug,只不过刚好有 if 条件兜住,else 里面应该是 clearTimeout(t) 才对。


修复后的前端代码:

<!doctype html>
<html lang="zh-hans">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<input type="file" id="files">
<hr>
<button id="up">Go!</button>
<script>

    var fileUpload = new videoFileUpload();
    up.onclick = _ => fileUpload.start();

    function videoFileUpload() {
        //每片切片1MB
        const LENGTH = 1024 * 1024;
        var start = 0;
        var end = start + LENGTH;
        var blob;
        var blob_num = 1;
        var is_stop = 0;

        //开始上传
        this.start = function () {
            //开始上传
            var file = files.files[0];
            blob = cutFile(file);
            sendFile(blob, file);
            blob_num += 1;
        }
        //停止上传
        this.stop = function () {
            //后续在这里发送一个请求提示后台删除缓存切片文件
            is_stop = 1;
        }

        //切割文件
        function cutFile(file) {
            var file_blob = file.slice(start, end);
            start = end;
            end = start + LENGTH;
            return file_blob;
        };

        //发送文件
        function sendFile(blob, file) {
            //只有处于不停止状态才执行
            if (is_stop == 0) {
                var xhr = new XMLHttpRequest();
                var form_data = new FormData();

                var total_blob_num = Math.ceil(file.size / LENGTH);
                form_data.append('file', blob);
                form_data.append('blob_num', blob_num);
                form_data.append('total_blob_num', total_blob_num);
                form_data.append('file_name', file.name);

                //原本是异步的,但是这会导致请求取消,只能取消异步
                xhr.open('POST', '/upload.php', false);
                xhr.onreadystatechange = function () {

                    //进行下一次发送
                    var t = setTimeout(function () {
                        //不处于停止状态时才执行
                        if (start < file.size) {
                            blob = cutFile(file);
                            sendFile(blob, file);
                            blob_num += 1;
                        } else {
                            clearTimeout(t);
                        }
                    }, 1000);
                }
                xhr.send(form_data);
            }
        }
    }
</script>
</body>
</html>

另外你的后端代码里面,在合并文件的时候,打开了文件读取后再进行的合并,这样会导致内存不足导致大文件在合并时失败,你应该找一些合适的方式去处理。


最后,我还想说,你参照的那个站,是一个内容农场,建议屏蔽。

回复
likes
适合作为回答的
  • 经过验证的有效解决办法
  • 自己的经验指引,对解决问题有帮助
  • 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
  • 询问内容细节或回复楼层
  • 与题目无关的内容
  • “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容