fileReader.readAsBinaryString上传文件


83

尝试使用fileReader.readAsBinaryString通过AJAX将PNG文件上传到服务器,并精简代码(fileObject是包含我的文件信息的对象);

var fileReader = new FileReader();

fileReader.onload = function(e) {
    var xmlHttpRequest = new XMLHttpRequest();
    //Some AJAX-y stuff - callbacks, handlers etc.
    xmlHttpRequest.open("POST", '/pushfile', true);
    var dashes = '--';
    var boundary = 'aperturephotoupload';
    var crlf = "\r\n";

    //Post with the correct MIME type (If the OS can identify one)
    if ( fileObject.type == '' ){
        filetype = 'application/octet-stream';
    } else {
        filetype = fileObject.type;
    }

    //Build a HTTP request to post the file
    var data = dashes + boundary + crlf + "Content-Disposition: form-data;" + "name=\"file\";" + "filename=\"" + unescape(encodeURIComponent(fileObject.name)) + "\"" + crlf + "Content-Type: " + filetype + crlf + crlf + e.target.result + crlf + dashes + boundary + dashes;

    xmlHttpRequest.setRequestHeader("Content-Type", "multipart/form-data;boundary=" + boundary);

    //Send the binary data
    xmlHttpRequest.send(data);
}

fileReader.readAsBinaryString(fileObject);

在上传之前检查文件的前几行(使用VI)

在此处输入图片说明

上传后显示相同的文件

在此处输入图片说明

因此,它看起来像是某个格式/编码问题,我尝试对原始二进制数据使用简单的UTF8编码功能

    function utf8encode(string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }

        return utftext;
    )

然后在原始代码中

//Build a HTTP request to post the file
var data = dashes + boundary + crlf + "Content-Disposition: form-data;" + "name=\"file\";" + "filename=\"" + unescape(encodeURIComponent(file.file.name)) + "\"" + crlf + "Content-Type: " + filetype + crlf + crlf + utf8encode(e.target.result) + crlf + dashes + boundary + dashes;

这给了我输出

在此处输入图片说明

仍然不是原始文件==

我如何编码/加载/处理文件以避免编码问题,因此HTTP请求中接收到的文件与上传之前的文件相同。

其他一些可能有用的信息,如果我不是使用fileReader.readAsBinaryString()而是使用fileObject.getAsBinary()来获取二进制数据,则它可以正常工作。但是getAsBinary仅在Firefox中有效。我已经在Mac上的Firefox和Chrome中对此进行了测试,两者都得到了相同的结果。后端上传由NGINX Upload Module处理,该模块再次在Mac上运行。服务器和客户端在同一台计算机上。我尝试上传的任何文件都发生了同样的事情,我只是选择了PNG,因为这是最明显的例子。

Answers:


74

使用fileReader.readAsDataURL( fileObject ),这会将其编码为base64,您可以安全地将其上传到服务器。


8
在这种情况下,保存在服务器上的文件版本是Base64编码的(应该是这样)。有没有办法将其作为二进制数据而不是Base64编码(IE,就好像它是使用常规<input type="file">字段上传的)进行传输
涂抹

2
如果服务器上有PHP,则可以在存储前使用base64_decode(file)。而且-没有安全的方法可以通过HTTP传输原始二进制数据。
2011年

使用readAsDataURL在服务器上给了我imgur.com/1LHya,通过PHP的base64_decode将其重新运行(我们实际上使用的是Python,但是PHP是一个很好的测试),我得到了imgur.com/0uwhy,仍然不是原始的二进制数据,不是有效的图片=(
涂抹

20
@ imgur.com/1LHya O,我不好!在服务器上,必须将base64字符串除以“,”,然后仅存储第二部分-这样,MIME类型将不会与实际文件内容一起存储。
c69 2011年

7
不,它没有效率。这将增加文件大小137%,并增加服务器开销。但是没有其他方法可以支持F *** IE
puchu 2013年

109

(以下是较晚但完整的答案)

FileReader方法支持


FileReader.readAsBinaryString()弃用。不要使用它!W3C File API工作草案中不再包含此内容:

void abort();
void readAsArrayBuffer(Blob blob);
void readAsText(Blob blob, optional DOMString encoding);
void readAsDataURL(Blob blob);

注意:请注意,这File是一种扩展Blob结构。

Mozilla仍在MDN FileApi文档中实现readAsBinaryString()和描述它:

void abort();
void readAsArrayBuffer(in Blob blob); Requires Gecko 7.0
void readAsBinaryString(in Blob blob);
void readAsDataURL(in Blob file);
void readAsText(in Blob blob, [optional] in DOMString encoding);

readAsBinaryString()我认为弃用的原因如下:JavaScript字符串的标准DOMString仅接受UTF-8字符,不接受随机二进制数据。因此,请勿使用readAsBinaryString(),因为它不安全且完全符合ECMAScript。

我们知道JavaScript字符串不应该存储二进制数据,但是Mozilla可以存储。我认为这很危险。Blobtyped arraysArrayBuffer还有尚未实现但不是必需的StringView)是出于一种目的而发明的:允许使用纯二进制数据,而没有UTF-8字符串限制。

XMLHttpRequest上传支持


XMLHttpRequest.send() 具有以下调用选项:

void send();
void send(ArrayBuffer data);
void send(Blob data);
void send(Document data);
void send(DOMString? data);
void send(FormData data);

XMLHttpRequest.sendAsBinary() 具有以下调用选项:

void sendAsBinary(   in DOMString body );

sendAsBinary()不是标准的,Chrome可能不支持。

解决方案


因此,您有几种选择:

  1. send()FileReader.resultFileReader.readAsArrayBuffer ( fileObject )。操作起来更加复杂(您必须为其单独创建一个send()),但这是推荐的方法
  2. send()FileReader.resultFileReader.readAsDataURL( fileObject )。它产生无用的开销和压缩延迟,需要在服务器端BUT上执行解压缩步骤,因此很容易将其作为Javascript中的字符串进行操作。
  3. 作为非标及sendAsBinary()FileReader.resultFileReader.readAsBinaryString( fileObject )

MDN指出:

发送二进制内容的最佳方法(例如在文件上传中)是将ArrayBuffers或Blob与send()方法结合使用。但是,如果要发送可字符串化的原始数据,请改用sendAsBinary()方法或StringView(非本机)类型的数组超类。


10
我很抱歉再次挖这一点,只是想补充一点,可能是发送二进制数据(等PDF文件)最简单的方法就是通过FileReader.readAsDataURLonload处理程序,而不是仅仅发送的event.target.result(这是不干净的base64编码字符串)你首先使用一些正则表达式清理它,event.target.result = event.target.result.match(/,(.*)$/)[1]然后将实际的base64发送到服务器进行解码。

由于任何人都可以编辑MDN,因此我可能不会将其用作源。
克里斯·安德森

4
@ user1299518,更好地使用event.target.result.split(",", 2)[1],而不是match
MrKsn

1
@KrisWebDev:在推荐的选项中,您提到需要制作一个单独的send()。为什么?
Readren '17

推荐的方法适用于使用TFS REST API上传附件。谢谢!
RoJaIt

24

在支持它的浏览器中,最好的方法是将文件作为Blob发送,或者如果需要多部分表单,则使用FormData。您不需要为此的FileReader。这比尝试读取数据既简单又有效。

如果您特别希望将其发送为multipart/form-data,则可以使用FormData对象:

var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open("POST", '/pushfile', true);
var formData = new FormData();
// This should automatically set the file name and type.
formData.append("file", file);
// Sending FormData automatically sets the Content-Type header to multipart/form-data
xmlHttpRequest.send(formData);

您也可以直接发送数据,而不是使用multipart/form-data。请参阅文档。当然,这也需要在服务器端进行更改。

// file is an instance of File, e.g. from a file input.
var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open("POST", '/pushfile', true);

xmlHttpRequest.setRequestHeader("Content-Type", file.type);

// Send the binary data.
// Since a File is a Blob, we can send it directly.
xmlHttpRequest.send(file);

有关浏览器的支持,请访问:http : //caniuse.com/#feat=xhr2(大多数浏览器,包括IE 10+)。


xmlHttpRequest.send(formData);
吴立志2014年

7
最后一个正确的答案也无需使用FormData。似乎每个人都在使用表单,而他们所需要的只是上传一个文件...谢谢!
2015年

我一直在寻找几个小时,如何通过ajax将其用于mp3文件上传,这确实可行!
贾斯汀·文森特

我认为您可能不需要执行setRequestHeader,因为它将通过发送formData自动设置,并且看起来像这样:“ Content-Type:multipart / form-data; boundary = ---- WebKitFormBoundaryQA8d7glpaso6zKsA” CORS,除非我删除了setRequestHeader。
贾斯汀·文森特

注意:我上面的评论仅在使用formData对象时适用。
贾斯汀·文森特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.