fileReader.readAsBinaryString для завантаження файлів


83

Спроба використовувати fileReader.readAsBinaryString для завантаження файлу PNG на сервер через AJAX, позбавлений код (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. Я тестував це у Firefox та Chrome, обидва на Mac, отримуючи однаковий результат в обох. Бекенд-завантаження обробляється модулем завантаження NGINX , знову запущеним на Mac. Сервер і клієнт на одній машині. Те саме відбувається з будь-яким файлом, який я намагаюся завантажити, я просто вибрав PNG, тому що це був найочевидніший приклад.

Відповіді:


74

Використовуйте fileReader.readAsDataURL( fileObject ), це кодує його до base64, який ви можете безпечно завантажити на свій сервер.


8
Поки це працює, версія файлу, збереженого на сервері, кодується Base64 (як і має бути). Чи немає можливості передати їх у вигляді двійкових даних, а не в кодованому Base64 (IE так, ніби його було завантажено за допомогою звичайного <input type="file">поля)
Smudge

2
Якщо у вас PHP на сервері, ви можете створити base64_decode (файл) перед тим, як зберігати його. І ні - немає безпечного способу передачі необроблених двійкових даних через http.
c69

Використання readAsDataURL дає мені це imgur.com/1LHya на сервері, запускаючи його назад через PHP base64_decode (ми фактично використовуємо Python, але PHP - хороший тест), я отримую imgur.com/0uwhy , все ще не вихідні двійкові дані і неправильне зображення = (
Розмазати

20
@ imgur.com/1LHya О, боже ! На сервері ви повинні розділити рядок base64 на "," і зберегти лише другу частину - так mime-тип не зберігатиметься з фактичним вмістом файлу.
c69

7
ні це не ефективно. це збільшить розмір файлу на 137% і зробить накладні витрати на сервер. але немає іншого способу підтримати F *** IE
puchu

109

(Далі йде пізня, але повна відповідь)

Підтримка методів FileReader


FileReader.readAsBinaryString()є застарілим. Не використовуйте його! Цього більше немає в робочому проекті файлового API W3C :

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

NB: Зверніть увагу, що Fileце свого роду розширена Blobструктура.

Mozilla все ще реалізує readAsBinaryString()та описує це в документації 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()застарівання є, на мій погляд, наступне: стандартом для рядків JavaScript є такі, DOMStringщо приймають лише символи UTF-8, а НЕ випадкові двійкові дані. Тому не використовуйте readAsBinaryString (), це взагалі не безпечно та не відповідає ECMAScript.

Ми знаємо, що рядки JavaScript не повинні зберігати двійкові дані, але Mozilla якось може. На мою думку, це небезпечно. Blobі typed arrays( ArrayBufferі ще не реалізовані, але не потрібні 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.resultз FileReader.readAsArrayBuffer ( fileObject ). Складніше маніпулювати (для цього вам доведеться зробити окреме надсилання ()), але це РЕКОМЕНДОВАНИЙ ПІДХІД .
  2. send()FileReader.resultз FileReader.readAsDataURL( fileObject ). Він генерує марну затримку накладних витрат і стиснення, вимагає кроку декомпресії на стороні сервера, АЛЕ ним легко маніпулювати як рядком у Javascript.
  3. Будучи нестандартним і зsendAsBinary()FileReader.resultFileReader.readAsBinaryString( fileObject )

MDN стверджує, що:

Найкращий спосіб надсилання двійкового вмісту (наприклад, при завантаженні файлів) - використання ArrayBuffers або Blobs у поєднанні з методом send (). Однак, якщо ви хочете надіслати необмежені необроблені дані, використовуйте замість цього метод sendAsBinary () або суперклас набраних масивів StringView (не рідний).


10
Вибачте, що копаю це ще раз, просто хотів додати, що, мабуть, найпростіший спосіб надсилання двійкових даних (і т.д. PDF-файлу) - це через FileReader.readAsDataURLі на onloadобробнику, а не просто надсилаючи 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: У рекомендованому варіанті ви згадуєте про необхідність зробити окреме надсилання (). Чому?
Readren

Рекомендований підхід працював для завантаження вкладення за допомогою API TEST REST. Дякую!
RoJa,

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);
Li-chih Wu

7
Нарешті правильна відповідь також без використання FormData. Здається, всі використовують форму, тоді як все, що їм потрібно, - це завантаження одного файлу ... Дякую!
Вілт

Я годинами шукав, як це зробити для завантаження файлу mp3 через ajax, це робить трюк!
Джастін Вінсент

Одне, я думаю, вам може не знадобитися робити setRequestHeader, оскільки він буде встановлений автоматично, надіславши formData і виглядатиме приблизно так: "Content-Type: multipart / form-data; border = ---- WebKitFormBoundaryQA8d7glpaso6zKsA" У моєму випадку він зламався CORS, якщо я не видалив setRequestHeader.
Джастін Вінсент

Примітка: Мій коментар вище стосується лише використання об’єкта formData.
Джастін Вінсент
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.