[英]How to properly send a binary file over http and download it using javascript?
[英]How to send binary file to the server using JavaScript
我正在为我的信使进行文件加密,并且在加密完成后我正在努力上传文件。
加密在性能方面看起来不错,但是当我尝试上传时,浏览器完全挂起。 Profiler 无限写入“small GC”事件,每 10 秒出现一次关于挂起脚本的黄色条。
我已经尝试过的:
用 FileReader 读取文件到 ArrayBuffer,然后把它变成一个基本的 Array,加密它,然后创建一个 FormData object,从数据中创建一个 File,append 到 FormData 并发送。 当我没有进行加密时,它可以快速处理大小约为 1.3 Mb 的原始、未触及的文件,但在上传后加密的“假”文件 object 上,我得到了 4.7 Mb 的文件,它不可用。
作为普通 POST 字段发送(多部分表单数据编码)。 以这种方式保存在 PHP 上后数据已损坏。
作为 Base64 编码的 POST 字段发送。 最后,在我发现将 function 从二进制数组快速转换为 Base64 字符串后,它开始以这种方式工作。 btoa() 在编码/解码后给出了错误的结果。 但是在我尝试了一个 8.5 Mb 大小的文件后,它又挂了。
我尝试将额外数据移动到 URL 字符串并将文件作为 Blob 发送,如此处所述。 没有成功,浏览器仍然挂起。
我尝试向 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.