Отправляйте данные XMLHttpRequest кусками или как ReadableStream, чтобы уменьшить использование памяти для больших данных - PullRequest
1 голос
/ 12 июля 2020

Я пытался использовать класс JS XMLHttpRequest для загрузки файлов. Сначала я пробовал что-то вроде этого:

const file = thisFunctionReturnsAFileObject();

const request = new XMLHttpRequest();
request.open('POST', '/upload-file');

const rawFileData = await file.arrayBuffer();

request.send(rawFileData);

Приведенный выше код работает (ура!) И отправляет необработанные двоичные данные файла на мой сервер.

Однако ... . Он использует ТОННУ памяти (потому что весь файл хранится в памяти, а JS не особенно удобен для памяти) ... Я обнаружил, что на моей машине (16 ГБ ОЗУ) я не мог отправлять файлы большего размера чем ~ 100 МБ, потому что JS выделит слишком много памяти, а вкладка Chrome взломает sh с кодом SIGILL.

Итак, я подумал, что было бы неплохо использовать здесь ReadableStreams. В моем случае у него достаточно хорошая совместимость с браузером (https://caniuse.com/#search = ReadableStream ), и мой компилятор TypeScript сказал мне, что request.send (...) поддерживает ReadableStreams (позже я пришел к выводу, что это неверно ). В итоге у меня получился такой код:

const file = thisFunctionReturnsAFileObject();

const request = new XMLHttpRequest();
request.open('POST', '/upload-file');

const fileStream = file.stream();

request.send(fileStream);

Но мой компилятор TypeScript предал меня (что было больно), и я получил "[object ReadableStream]" на моем сервере ಠ_ಠ.

Я все еще не знаю Я слишком много изучал вышеуказанный метод, поэтому я не уверен, есть ли способ сделать это. Я также очень признателен за помощь в этом!

Разделение запроса на фрагменты было бы оптимальным решением, поскольку после отправки фрагмента мы можем удалить его из памяти до того, как будет получен весь запрос.

Я искал и искал, но пока не нашел способа сделать это (вот почему я здесь ...). Что-то вроде этого в псевдокоде было бы оптимальным:

const file = thisFunctionReturnsAFileObject();

const request = new XMLHttpRequest();
request.open('POST', '/upload-file');

const fileStream = file.stream();
const fileStreamReader = fileStream.getReader();

const sendNextChunk = async () => {
    const chunk = await fileStreamReader.read();

    if (!chunk.done) { // chunk.done implies that there is no more data to be read
        request.writeToBody(chunk.value); // chunk.value is a Uint8Array
    } else {
        request.end();
        break;
    }
}

sendNextChunk();

Я бы хотел, чтобы этот код отправлял запрос по частям и завершал запрос, когда все куски отправлены.

Самый полезный ресурс, который я пробовал, но не работал:

Метод потоковой передачи данных из браузера на сервер через HTTP

Не работал, потому что:

  • Мне нужно решение для работы в одном запросе
  • Я не могу использовать RTCDataChannel , это должен быть простой HTTP-запрос (есть ли Другой способ сделать это, кроме XMLHttpRequest?)
  • Мне нужно, чтобы он работал в современных Chrome / Firefox / Edge et c. (нет IE поддержка в порядке)

Изменить: я не хочу использовать составную форму (класс FormData). Я хочу отправлять фактические двоичные данные, считанные из файлового потока, кусками.

Ответы [ 2 ]

1 голос
/ 13 июля 2020

Вы столкнулись с ошибкой Chrome , когда они устанавливают жесткое ограничение в 256 МБ для размера ArrayBuffer, который может быть отправлен.

Но в любом случае отправка ArrayBuffer создаст копию данных , поэтому вам лучше отправлять свои данные в виде файла напрямую, так как это будет только читать файл точно так, как вы хотели, как поток небольшими кусками.

Итак, взяв ваш первый блок кода, который даст

const file = thisFunctionReturnsAFileObject();

const request = new XMLHttpRequest();
request.open('POST', '/upload-file');

request.send(file);

Ans, это будет работать и в Chrome, даже с несколькими файлами Gigs. Единственное ограничение, с которым вы столкнетесь здесь, будет раньше, когда вы будете выполнять любую обработку, которую вы делаете с этим файлом.

Что касается публикации ReadableStreams, это в конечном итоге произойдет, но на сегодняшний день, 13 июля 2020 года, только Chrome начали работу над его реализацией, и мы, веб-разработчики, все еще не можем играть с это, а спецификации по-прежнему трудно найти что-то стабильное. Но для вас это не проблема, так как вы все равно ничего не выиграете. Публикация ReadableStream, созданного из файла stati c, бесполезна, и fetch, и xhr уже сделают это внутри.

1 голос
/ 12 июля 2020

Вы не можете этого сделать с XHR afaik. Но более современный fetch API поддерживает передачу ReadableStream для тела запроса. В вашем случае:

const file = thisFunctionReturnsAFileObject();

const response = await fetch('/upload-file', {
  method: 'POST',
  body: file.stream(),
});

Однако я не уверен , действительно ли это будет использовать фрагментированную кодировку .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...