fileReader.readAsBinaryString do przesyłania plików

83

Próba użycia fileReader.readAsBinaryString do przesłania pliku PNG na serwer przez AJAX, uproszczony kod (fileObject to obiekt zawierający informacje o moim pliku);

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

Zbadanie kilku pierwszych wierszy pliku przed załadowaniem (używając VI) daje mi wyniki

wprowadź opis obrazu tutaj

Ten sam plik po przesłaniu pokazuje

wprowadź opis obrazu tutaj

Więc wygląda na to, że gdzieś występuje problem z formatowaniem / kodowaniem, próbowałem użyć prostej funkcji kodowania UTF8 na surowych danych binarnych

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

Następnie w oryginalnym kodzie

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

co daje mi wynik

wprowadź opis obrazu tutaj

Nadal nie jest to, czym był surowy plik = (

Jak zakodować / załadować / przetworzyć plik, aby uniknąć problemów z kodowaniem, aby plik odbierany w żądaniu HTTP był taki sam, jak plik przed przesłaniem.

Kilka innych potencjalnie przydatnych informacji, jeśli zamiast używać fileReader.readAsBinaryString () używam fileObject.getAsBinary () do pobierania danych binarnych, działa dobrze. Ale getAsBinary działa tylko w przeglądarce Firefox. Testowałem to w przeglądarce Firefox i Chrome, oba na Macu, uzyskując ten sam wynik w obu. Przesyłanie do zaplecza jest obsługiwane przez moduł NGINX Upload , ponownie działający na komputerze Mac. Serwer i klient znajdują się na tym samym komputerze. To samo dzieje się z każdym plikiem, który próbuję przesłać, po prostu wybrałem PNG, ponieważ był to najbardziej oczywisty przykład.

Plama
źródło

Odpowiedzi:

74

Użyj fileReader.readAsDataURL( fileObject ), to zakoduje go do base64, który możesz bezpiecznie przesłać na swój serwer.

c69
źródło
8
Chociaż to działa, wersja pliku zapisanego na serwerze jest zakodowana w standardzie Base64 (tak, jak powinna). Czy nie ma sposobu, aby przesłać je jako dane binarne zamiast zakodowanych w Base64 (IE tak, jakby zostały przesłane przy użyciu normalnego <input type="file">pola)
Smudge
2
Jeśli masz PHP na serwerze, możesz przed zapisaniem go base64_decode (plik). I nie - nie ma bezpiecznego sposobu przesyłania surowych danych binarnych przez http.
c69
Użycie readAsDataURL daje mi to imgur.com/1LHya na serwerze, uruchamiając go z powrotem przez kod PHP base64_decode (faktycznie używamy Pythona, ale PHP jest dobrym testem) Otrzymuję imgur.com/0uwhy , nadal nie są to oryginalne dane binarne i niepoprawny obraz = (
Smudge
20
@ imgur.com/1LHya O, moja wina! Na serwerze musisz podzielić ciąg base64 za pomocą „,” i zapisać tylko drugą część - aby typ MIME nie został zapisany z rzeczywistą zawartością pliku.
c69
7
nie, to nie jest wydajne. spowoduje to zwiększenie rozmiaru pliku o 137% i obciążenie serwera. ale nie ma innego sposobu, aby wesprzeć F *** IE
puchu
109

(Poniżej znajduje się późna, ale pełna odpowiedź)

Obsługa metod FileReader


FileReader.readAsBinaryString()jest przestarzały. Nie używaj tego! Nie ma go już w roboczej wersji roboczej W3C File API :

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

Uwaga: należy zauważyć, że Filejest to rodzaj rozbudowanej Blobstruktury.

Mozilla nadal implementuje readAsBinaryString()i opisuje to w dokumentacji MDN FileApi :

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()Moim zdaniem powód wycofania jest następujący: standard dla ciągów JavaScript DOMStringakceptuje tylko znaki UTF-8, a NIE losowe dane binarne. Więc nie używaj readAsBinaryString (), jest to w ogóle niebezpieczne i zgodne z ECMAScript.

Wiemy, że łańcuchy JavaScript nie powinny przechowywać danych binarnych, ale Mozilla może to zrobić. Moim zdaniem to niebezpieczne. Blobi typed arrays( ArrayBufferi jeszcze nie zaimplementowane, ale niekonieczne StringView) zostały wymyślone w jednym celu: zezwolić na użycie czystych danych binarnych, bez ograniczeń dotyczących łańcuchów UTF-8.

Obsługa przesyłania XMLHttpRequest


XMLHttpRequest.send() ma następujące opcje wywołań:

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

XMLHttpRequest.sendAsBinary() ma następujące opcje wywołań:

void sendAsBinary(   in DOMString body );

sendAsBinary () NIE jest standardem i może nie być obsługiwana w przeglądarce Chrome.

Rozwiązania


Masz więc kilka opcji:

  1. send()FileReader.resultod FileReader.readAsArrayBuffer ( fileObject ). Manipulowanie jest bardziej skomplikowane (będziesz musiał wykonać osobną wysyłkę ()), ale jest to PODEJŚCIE ZALECANE .
  2. send()FileReader.resultod FileReader.readAsDataURL( fileObject ). Generuje bezużyteczne narzuty i opóźnienie kompresji, wymaga kroku dekompresji po stronie serwera, ALE jest łatwy do manipulowania jako ciąg znaków w JavaScript.
  3. Jako niestandardowe i odsendAsBinary()FileReader.resultFileReader.readAsBinaryString( fileObject )

MDN stwierdza, że:

Najlepszym sposobem wysyłania zawartości binarnej (np. W przypadku przesyłania plików) jest użycie ArrayBuffers lub Blobs w połączeniu z metodą send (). Jeśli jednak chcesz wysłać nieprzetworzone dane, które można określić jako stringifable, użyj zamiast tego metody sendAsBinary () lub nadklasy tablic o typie StringView (nienatywnym).

KrisWebDev
źródło
10
Przepraszam kopać to ponownie, po prostu chciałem dodać, że prawdopodobnie najprostszy sposób wysyłania danych binarnych (plik PDF itd.) Jest za FileReader.readAsDataURLi onloadobsługi, a nie tylko wysyłania event.target.result(co nie jest czystą base64 zakodowany ciąg znaków), który wyczyść go najpierw za pomocą pewnych wyrażeń regularnych event.target.result = event.target.result.match(/,(.*)$/)[1]i wyślij prawdziwy base64 do serwera w celu zdekodowania.
Ponieważ każdy może edytować MDN, prawdopodobnie nie użyłbym go jako źródła.
chris
4
@ user1299518, lepsze wykorzystanie event.target.result.split(",", 2)[1], nie match.
MrKsn
1
@KrisWebDev: W zalecanej opcji wspominasz o konieczności wykonania oddzielnej metody send (). Czemu?
Readren
Zalecane podejście zadziałało w przypadku przekazywania załącznika przy użyciu interfejsu API REST TFS. Dzięki!
RoJaIt
24

Najlepszym sposobem w przeglądarkach, które go obsługują, jest wysłanie pliku jako obiektu BLOB lub użycie FormData, jeśli potrzebujesz formularza wieloczęściowego. Nie potrzebujesz do tego FileReadera. Jest to prostsze i wydajniejsze niż próba odczytania danych.

Jeśli chcesz wysłać go jako multipart/form-data, możesz użyć obiektu 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);

Możesz również wysłać dane bezpośrednio, zamiast używać multipart/form-data. Zobacz dokumentację . Oczywiście będzie to również wymagało zmiany po stronie serwera.

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

Informacje o obsłudze przeglądarek można znaleźć pod adresem : http://caniuse.com/#feat=xhr2 (większość przeglądarek, w tym IE 10+).

Ralf
źródło
xmlHttpRequest.send (formData);
Li-chih Wu
7
Wreszcie poprawna odpowiedź również bez użycia FormData. Wygląda na to, że wszyscy używają formularza, podczas gdy jedyne, czego potrzebują, to przesłanie jednego pliku ... Dzięki!
Wilt
Szukałem godzinami, jak sprawić, by to działało przy przesyłaniu pliku mp3 przez ajax, to załatwia sprawę!
Justin Vincent
Wydaje mi się, że możesz nie potrzebować setRequestHeader, ponieważ zostanie on automatycznie ustawiony przez wysłanie formularza formData i będzie wyglądał mniej więcej tak: "Content-Type: multipart / form-data; boundary = ---- WebKitFormBoundaryQA8d7glpaso6zKsA" W moim przypadku się zepsuło CORS, chyba że usunąłem setRequestHeader.
Justin Vincent
Uwaga: mój komentarz powyżej dotyczy tylko obiektu formData.
Justin Vincent