fileReader.readAsBinaryString zum Hochladen von Dateien

83

Beim Versuch, mit fileReader.readAsBinaryString eine PNG-Datei über AJAX auf den Server hochzuladen, wurde der Code reduziert (fileObject ist das Objekt, das Informationen zu meiner Datei enthält).

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

Das Untersuchen der ersten Zeilen einer Datei vor dem Hochladen (unter Verwendung von VI) gibt mir

Geben Sie hier die Bildbeschreibung ein

Die gleiche Datei nach dem Hochladen wird angezeigt

Geben Sie hier die Bildbeschreibung ein

Es sieht also nach einem Formatierungs- / Codierungsproblem aus. Ich habe versucht, eine einfache UTF8-Codierungsfunktion für die rohen Binärdaten zu verwenden

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

Dann im Originalcode

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

das gibt mir die Ausgabe von

Geben Sie hier die Bildbeschreibung ein

Immer noch nicht, was die Rohdatei war = (

Wie codiere / lade / verarbeite ich die Datei, um die Codierungsprobleme zu vermeiden, sodass die in der HTTP-Anforderung empfangene Datei mit der Datei vor dem Hochladen identisch ist.

Einige andere möglicherweise nützliche Informationen: Wenn ich anstelle von fileReader.readAsBinaryString () fileObject.getAsBinary () verwende, um die Binärdaten abzurufen, funktioniert dies einwandfrei. GetAsBinary funktioniert jedoch nur in Firefox. Ich habe dies in Firefox und Chrome getestet, beide auf Mac, und in beiden das gleiche Ergebnis erzielt. Die Backend-Uploads werden vom NGINX-Upload-Modul verarbeitet , das wiederum auf dem Mac ausgeführt wird. Der Server und der Client befinden sich auf demselben Computer. Das gleiche passiert mit jeder Datei, die ich hochladen möchte. Ich habe mich nur für PNG entschieden, weil dies das offensichtlichste Beispiel war.

Verschmieren
quelle

Antworten:

74

Verwenden Sie fileReader.readAsDataURL( fileObject )diese Option, um es in base64 zu codieren, das Sie sicher auf Ihren Server hochladen können.

c69
quelle
8
Währenddessen funktioniert die auf dem Server gespeicherte Version der Datei Base64-codiert (wie es sein sollte). Gibt es keine Möglichkeit, es als Binärdaten anstatt Base64-codiert zu übertragen (IE, als ob es mit einem normalen <input type="file">Feld hochgeladen worden wäre )
Smudge
2
Wenn Sie PHP auf dem Server haben, können Sie base64_decode (Datei) vor dem Speichern. Und nein - es gibt keine sichere Möglichkeit, binäre Rohdaten über http zu übertragen.
c69
Die Verwendung von readAsDataURL gibt mir dieses imgur.com/1LHya auf dem Server und führt es über PHPs base64_decode zurück (wir verwenden tatsächlich Python, aber PHP ist ein guter Test). Ich erhalte imgur.com/0uwhy , immer noch nicht die ursprünglichen Binärdaten und kein gültiges Bild = (
Smudge
20
@ imgur.com/1LHya O, mein schlechtes! Auf dem Server müssen Sie die base64-Zeichenfolge durch "," teilen und nur den zweiten Teil speichern, damit der MIME-Typ nicht mit dem tatsächlichen Dateiinhalt gespeichert wird.
c69
7
Nein, es ist nicht effizient. Dies erhöht die Dateigröße um 137% und erhöht den Server-Overhead. aber es gibt keine andere Möglichkeit, F *** IE
puchu
108

(Es folgt eine späte, aber vollständige Antwort)

Unterstützung für FileReader-Methoden


FileReader.readAsBinaryString()ist veraltet. Benutze es nicht! Es ist nicht mehr im Arbeitsentwurf der W3C-Datei-API enthalten :

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

NB: Beachten Sie, dass dies Fileeine Art erweiterte BlobStruktur ist.

Mozilla implementiert readAsBinaryString()und beschreibt es weiterhin in der MDN FileApi-Dokumentation :

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

Der Grund für die readAsBinaryString()Ablehnung ist meiner Meinung nach der folgende: Der Standard für JavaScript-Zeichenfolgen ist, DOMStringdass nur UTF-8-Zeichen akzeptiert werden, NICHT zufällige Binärdaten. Verwenden Sie also nicht readAsBinaryString (), das ist nicht sicher und ECMAScript-kompatibel.

Wir wissen, dass JavaScript-Zeichenfolgen keine Binärdaten speichern sollen , Mozilla jedoch in irgendeiner Weise. Das ist meiner Meinung nach gefährlich. Blobund typed arrays( ArrayBufferund die noch nicht implementierten, aber nicht notwendigen StringView) wurden für einen Zweck erfunden: die Verwendung von reinen Binärdaten ohne Einschränkungen der UTF-8-Zeichenfolgen zu ermöglichen.

Unterstützung für das Hochladen von XMLHttpRequest


XMLHttpRequest.send() hat die folgenden Aufrufoptionen:

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

XMLHttpRequest.sendAsBinary() hat die folgenden Aufrufoptionen:

void sendAsBinary(   in DOMString body );

sendAsBinary () ist KEIN Standard und wird in Chrome möglicherweise nicht unterstützt.

Lösungen


Sie haben also mehrere Möglichkeiten:

  1. send()die FileReader.resultvon FileReader.readAsArrayBuffer ( fileObject ). Die Manipulation ist komplizierter (Sie müssen dafür ein separates send () erstellen ), aber es ist der EMPFOHLENE ANSATZ .
  2. send()die FileReader.resultvon FileReader.readAsDataURL( fileObject ). Es erzeugt nutzlosen Overhead und Komprimierungslatenz, erfordert einen Dekomprimierungsschritt auf der Serverseite, ABER es ist einfach, als Zeichenfolge in Javascript zu manipulieren.
  3. Nicht-Standard sein und sendAsBinary()das FileReader.resultvonFileReader.readAsBinaryString( fileObject )

MDN gibt an, dass:

Der beste Weg, um binären Inhalt zu senden (wie beim Hochladen von Dateien), ist die Verwendung von ArrayBuffers oder Blobs in Verbindung mit der send () -Methode. Wenn Sie jedoch stringifizierbare Rohdaten senden möchten, verwenden Sie stattdessen die Methode sendAsBinary () oder die Superklasse vom Typ StringView (nicht native) Arrays.

KrisWebDev
quelle
10
Es tut mir leid, dies noch einmal zu graben, ich wollte nur hinzufügen, dass der wahrscheinlich einfachste Weg, Binärdaten (usw. eine PDF-Datei) zu senden, FileReader.readAsDataURLüber den onloadHandler erfolgt, anstatt nur die event.target.result(die keine saubere base64-codierte Zeichenfolge ist) Sie zu senden Bereinigen Sie es zuerst mit einem regulären Ausdruck wie event.target.result = event.target.result.match(/,(.*)$/)[1]und senden Sie die echte base64 an den Server, um sie zu dekodieren.
Da jeder MDN bearbeiten kann, würde ich es wahrscheinlich nicht als Quelle verwenden.
Chris Anderson
@vlzvl: Du bist mein Held!
Yster
4
@ user1299518, besser nutzen event.target.result.split(",", 2)[1], nicht match.
MrKsn
1
@KrisWebDev: In der empfohlenen Option erwähnen Sie die Notwendigkeit, ein separates send () durchzuführen. Warum?
Readren
24

Der beste Weg in Browsern, die dies unterstützen, besteht darin, die Datei als Blob zu senden oder FormData zu verwenden, wenn Sie ein mehrteiliges Formular wünschen. Sie benötigen dafür keinen FileReader. Dies ist sowohl einfacher als auch effizienter als der Versuch, die Daten zu lesen.

Wenn Sie es speziell als senden möchten multipart/form-data, können Sie ein FormData-Objekt verwenden:

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

Sie können die Daten auch direkt senden, anstatt sie zu verwenden multipart/form-data. Siehe die Dokumentation . Dies erfordert natürlich auch eine serverseitige Änderung.

// 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);

Informationen zur Browserunterstützung finden Sie unter: http://caniuse.com/#feat=xhr2 (die meisten Browser, einschließlich IE 10+).

Ralf
quelle
xmlHttpRequest.send (formData);
Li-chih Wu
7
Endlich eine richtige Antwort auch ohne zu benutzen FormData. Es scheint, dass jeder ein Formular verwendet, während er nur eine einzelne Datei hochladen muss ... Danke!
Wilt
Ich habe stundenlang gesucht, wie man das für einen MP3-Datei-Upload über Ajax zum Laufen bringt, das macht den Trick!
Justin Vincent
Ich denke, Sie müssen setRequestHeader möglicherweise nicht ausführen, da es durch das Senden von formData automatisch festgelegt wird und ungefähr so ​​aussieht: "Inhaltstyp: mehrteilig / Formulardaten; Grenze = ---- WebKitFormBoundaryQA8d7glpaso6zKsA" In meinem Fall ist es kaputt gegangen CORS, es sei denn, ich habe setRequestHeader entfernt.
Justin Vincent
Hinweis: Mein obiger Kommentar gilt nur bei Verwendung des formData-Objekts.
Justin Vincent