繁体   English   中英

如何使用 JavaScript 将二进制文件发送到服务器

[英]How to send binary file to the server using JavaScript

我正在为我的信使进行文件加密,并且在加密完成后我正在努力上传文件。

加密在性能方面看起来不错,但是当我尝试上传时,浏览器完全挂起。 Profiler 无限写入“small GC”事件,每 10 秒出现一次关于挂起脚本的黄色条。

我已经尝试过的:

  1. 用 FileReader 读取文件到 ArrayBuffer,然后把它变成一个基本的 Array,加密它,然后创建一个 FormData object,从数据中创建一个 File,append 到 FormData 并发送。 当我没有进行加密时,它可以快速处理大小约为 1.3 Mb 的原始、未触及的文件,但在上传后加密的“假”文件 object 上,我得到了 4.7 Mb 的文件,它不可用。

  2. 作为普通 POST 字段发送(多部分表单数据编码)。 以这种方式保存在 PHP 上后数据已损坏。

  3. 作为 Base64 编码的 POST 字段发送。 最后,在我发现将 function 从二进制数组快速转换为 Base64 字符串后,它开始以这种方式工作。 btoa() 在编码/解码后给出了错误的结果。 但是在我尝试了一个 8.5 Mb 大小的文件后,它又挂了。

  4. 我尝试将额外数据移动到 URL 字符串并将文件作为 Blob 发送,如此所述。 没有成功,浏览器仍然挂起。

  5. 我尝试向 Blob 构造函数传递一个基本数组,一个由它组成的 Uint8Array,最后我尝试按照文档中的建议发送文件,但结果仍然相同,即使是小文件。

代码有什么问题? 发生此挂起时,HDD 负载为 0%。 有问题的文件也非常小

当我通过按下按钮紧急终止 JS 脚本时,这是我从服务器脚本获得的 output 的内容:

Warning: Unknown: POST Content-Length of 22146226 bytes exceeds the limit of 8388608 bytes in Unknown on line 0

Warning: Cannot modify header information - headers already sent in Unknown on line 0

Warning: session_start() [function.session-start]: Cannot send session cache limiter - headers already sent in D:\xmessenger\upload.php on line 2
Array ( ) 

这是我的 JavaScript:

function uploadEncryptedFile(nonce) {
    if (typeof window['FormData'] !== 'function' || typeof window['File'] !== 'function') return
    
    var file_input = document.getElementById('attachment')
    
    if (!file_input.files.length) return
    var file = file_input.files[0]
    
    var reader = new FileReader();
    
    reader.addEventListener('load', function() {
        var data = Array.from(new Uint8Array(reader.result))
        var encrypted = encryptFile(data, nonce)
        //return  //Here it never hangs
        var form_data = new FormData()
        form_data.append('name', file.name)
        form_data.append('type', file.type)
        form_data.append('attachment', arrayBufferToBase64(encrypted))
        /* form_data.append('attachment', btoa(encrypted)) // Does not help */
        form_data.append('nonce', nonce)
        var req = getXmlHttp()
        req.open('POST', 'upload.php?attachencryptedfile', true)
        req.onload = function() {
            var data = req.responseText.split(':')
            document.getElementById('filelist').lastChild.realName = data[2]
            document.getElementById('progress2').style.display = 'none'
            document.getElementById('attachment').onclick = null
            encryptFilename(data[0], data[1], data[2])
        }
        req.send(form_data)
        /* These lines also fail when the file is larger */
        /* req.send(new Blob(encrypted)) */
        /* req.send(new Blob(new Uint8Array(encrypted))) */
    })
    reader.readAsArrayBuffer(file)
}

function arrayBufferToBase64(buffer) {
    var binary = '';
    var bytes = new Uint8Array(buffer);
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return window.btoa(binary);
}

这是我的 PHP 处理程序代码:

if (isset($_GET['attachencryptedfile'])) {
    $entityBody = file_get_contents('php://input');
    if ($entityBody == '') exit(print_r($_POST, true)); 
    else exit($entityBody);
    
    if (!isset($_POST["name"])) exit("Error");
    
    $name = @preg_replace("/[^0-9A-Za-z._-]/", "", $_POST["name"]);
    $nonce = @preg_replace("/[^0-9A-Za-z+\\/]/", "", $_POST["nonce"]);
    
    if ($name == ".htaccess") exit();

    $data = base64_decode($_POST["attachment"]);
    //print_r($_POST);
    //exit();
    if (strlen($data)>1024*15*1024) exit('<script type="text/javascript">parent.showInfo("Файл слишком большой"); parent.document.getElementById(\'filelist\').removeChild(parent.document.getElementById(\'filelist\').lastChild); parent.document.getElementById(\'progress2\').style.display = \'none\'; parent.document.getElementById(\'attachment\').onclick = null</script>');
    $uname = uniqid()."_".str_pad($_SESSION['xm_user_id'], 6, "0", STR_PAD_LEFT).substr($name, strrpos($name, "."));
    file_put_contents("upload/".$uname, $data);
    mysql_query("ALTER TABLE `attachments` AUTO_INCREMENT=0");
    mysql_query("INSERT INTO `attachments` VALUES('0', '".$uname."', '".$name."', '0', '".$nonce."')");
    exit(mysql_insert_id().":".$uname.":".$name);
}

HTML 形式:

<form name="fileForm" id="fileForm" method="post" enctype="multipart/form-data" action="upload.php?attachfile" target="ifr">
    <div id="fileButton" title="Прикрепить файл" onclick="document.getElementById('attachment').click()"></div>
    <input type="file" name="attachment" id="attachment" title="Прикрепить файл" onchange="addFile()" />
</form>

UPD:不幸的是,问题没有解决。 我的回答只是部分正确。 现在我在代码中犯了一个愚蠢的错误(忘记更新服务器端),我发现了另一个可能挂起的原因。 如果我提交一个基本的 POST 表单(x-www-urlencoded)和 PHP 脚本中的代码尝试执行此行(定义了 $uname,$_FILES 是一个空数组)

if (!copy($_FILES['attachment']['tmp_name'], "upload/".$uname)) exit("Error");

然后整个事情又挂了。 如果我终止脚本,服务器响应是代码 200,并且正文内容很好(我的开发机器上有错误 output)。 我知道这是一件坏事——使用完全未定义的第一个参数调用copy ,但即使服务器错误 500 也不能以这种方式挂起浏览器(顺便说一句,Firefox 的最新版本也受到影响)。

我在 Windows 7 x64 和 PHP 5.3 上有 Apache 2.4。 有人可以验证这件事吗? 也许应该向 Apache/Firefox 团队提交一个错误?


我的天啊。 这种可怕的行为是由...在 php.ini 中设置的 post_max_size post_max_size = 8M引起的。 并且小于 8 Mb 的文件实际上并没有挂起浏览器,正如我所想的那样。

最后一个问题是——为什么? 为什么 Apache/PHP(我有 Apache 2.4 顺便说一句,它不旧)不能以某种方式优雅地中止连接,告诉浏览器超出限制? 或者它可能是 XHR 实现中的一个错误,不适用于基本表单提交。 无论如何,对偶然发现它的人会有用。

顺便说一句,我在具有相同 POST 大小限制的 Chrome 中尝试了它,它并没有像在 Firefox 中那样完全挂在那里(请求仍然处于“无响应可用”的挂起状态,但是 JS 引擎和 UI没有被阻止)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM