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, который вы можете безопасно загрузить на свой сервер.

c69
источник
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 , все еще не исходные двоичные данные и not a valid image = (
Smudge
20
@ imgur.com/1LHya О, моя беда ! На сервере вы должны разделить строку base64 на "," и сохранить только вторую часть, чтобы тип mime не сохранялся вместе с фактическим содержимым файла.
c69
7
нет, это неэффективно. это увеличит размер файла на 137% и приведет к увеличению нагрузки на сервер. но другого способа поддержать F *** IE нет
puchu
109

(Ниже приводится поздний, но полный ответ)

Поддержка методов FileReader


FileReader.readAsBinaryString()является устаревшим. Не используйте это! Его больше нет в рабочем проекте W3C File API :

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 ). Этим более сложно манипулировать (для этого вам придется сделать отдельный send ()), но это РЕКОМЕНДУЕМЫЙ ПОДХОД .
  2. send()FileReader.resultиз FileReader.readAsDataURL( fileObject ). Он создает бесполезные накладные расходы и задержку сжатия, требует этапа распаковки на стороне сервера, НО его легко манипулировать как строку в Javascript.
  3. Будучи нестандартным и изsendAsBinary()FileReader.resultFileReader.readAsBinaryString( fileObject )

MDN заявляет, что:

Лучший способ отправить двоичный контент (например, при загрузке файлов) - использовать ArrayBuffers или Blobs в сочетании с методом send (). Однако, если вы хотите отправить строковые необработанные данные, используйте вместо этого метод sendAsBinary () или суперкласс типизированных массивов StringView (Non native).

KrisWebDev
источник
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 06
1
@KrisWebDev: В рекомендуемом варианте вы упоминаете о необходимости сделать отдельный send (). Почему?
Ридрен
Рекомендуемый подход работал для загрузки вложения с помощью 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);
Li-chih Wu
7
Наконец, правильный ответ также без использования FormData. Кажется, все используют форму, а все, что им нужно, это загрузить один файл ... Спасибо!
Уилт
Я часами искал, как заставить это работать для загрузки файла mp3 через ajax, это помогает!
Джастин Винсент
Одна вещь, я думаю, вам может не понадобиться делать setRequestHeader, поскольку он будет автоматически установлен путем отправки formData и будет выглядеть примерно так: «Content-Type: multipart / form-data; boundary = ---- WebKitFormBoundaryQA8d7glpaso6zKsA» В моем случае он сломался CORS, если я не удалил setRequestHeader.
Джастин Винсент
Примечание. Мой комментарий выше применяется только при использовании объекта formData.
Джастин Винсент