Google Drive API: правильный способ загрузки бинарных файлов через Multipart API - PullRequest
0 голосов
/ 29 августа 2018

Я пытаюсь загрузить двоичный файл в Google Drive через API многоэтапной загрузки v3 .

Вот шестнадцатеричное представление содержимого файла:

FF FE

По какой-то причине вышеуказанный контент кодируется как UTF-8 (я полагаю) когда я пытаюсь POST его, заключенный в полезную нагрузку из нескольких частей:

--BOUNDARY
Content-Type: application/json

{"name": "F.ini"}

--BOUNDARY
Content-Type: application/octet-stream

ÿþ                <-- in the outbound request, this gets UTF-8 encoded
--BOUNDARY--

Шестнадцатеричное представление файла, который в итоге сохраняется на стороне сервера:

C3 BF C3 BE

Проблема возникает только на этапе отправки: если я проверяю длину содержимого, прочитанного из файла, я всегда получаю 2; независимо от того, использую ли я FileReader#readAsBinaryString или FileReader#readAsArrayBuffer (получая строку длиной 2 и ArrayBuffer с byteLength 2 соответственно).

Вот минимальный код, который я использую для создания составной полезной нагрузки:

file = picker.files[0];    // 'picker' is a file picker
reader = new FileReader();
reader.onload = function (e) {
    content = e.target.result;
    boundary = "BOUNDARY";
    meta = '{"name": "' + file.name + '"}';
    console.log(content.length);    // gives 2 as expected

    payload = [
        "--" + boundary, "Content-Type: application/json", "", meta, "", "--" + boundary,
        "Content-Type: application/octet-stream", "", content, "--" + boundary + "--"
    ].join("\r\n");
    console.log(payload.length);    // say this gives n

    xhr = new XMLHttpRequest();
    xhr.open("POST", "/", false);
    xhr.setRequestHeader("Content-Type", "multipart/related; boundary=" + boundary);
    xhr.send(payload);              // this produces a request with a 'Content-Length: n+2' header
                                    // (corresponding to the length increase due to UTF-8 encoding)
};
reader.readAsBinaryString(file);

У меня двоякий вопрос:

  • Есть ли способ избежать этой автоматической кодировки UTF-8? (Вероятно, нет, потому что этот ответ подразумевает, что кодировка UTF-8 является частью спецификации XHR.)
  • Если нет, как правильно «сообщить» Drive API о том, что содержимое моего файла имеет кодировку UTF-8? Я попробовал эти подходы, но безуспешно:
    • добавление ; charset=utf-8 или ; charset=UTF-8 к заголовку Content-Type двоичной части
    • делает то же самое с заголовком HTTP в родительском запросе (Content-Type: multipart/related; boundary=blablabla, charset=utf-8; также попытался заменить запятую точкой с запятой)

Мне нужен составной API, потому что AFAIU "простой" API не позволяет мне загружать в папку (он принимает только имя файла в качестве метаданных через HTTP-заголовок Slug, тогда как объект метаданных JSON в случае нескольких частей также позволяет указать идентификатор папки parent). (Просто подумал упомянуть об этом, потому что «простой» API обрабатывает вещи правильно когда я непосредственно ПОСТАВЛЯЮ File (от сборщика) или ArrayBuffer (от FileReader#readAsArrayBuffer) в качестве полезной нагрузки XHR.)

Я не хочу использовать какие-либо сторонние библиотеки, потому что

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

Ради полноты я попытался загрузить тот же файл через веб-интерфейс GDrive, и он загрузился просто отлично; однако веб-интерфейс, кажется, кодирует полезную нагрузку с помощью base64, чего я бы хотел избежать (поскольку это излишне раздувает полезную нагрузку, особенно для больших полезных нагрузок, что является моей конечной целью).

1 Ответ

0 голосов
/ 30 августа 2018

Как насчет этой модификации?

Очки модификации:

  • Используется new FormData() для создания multipart / form-data.
  • Используется reader.readAsArrayBuffer(file) вместо reader.readAsBinaryString(file).
  • Отправить файл в виде блоба. В этом случае данные отправляются как application/octet-stream.

Модифицированный скрипт:

file = picker.files[0];    // 'picker' is a file picker
reader = new FileReader();
reader.onload = function (e) {
    var content = new Blob([file]);
    var meta = {name: file.name, mimeType: file.type};
    var accessToken = gapi.auth.getToken().access_token;
    var payload = new FormData();
    payload.append('metadata', new Blob([JSON.stringify(meta)], {type: 'application/json'}));
    payload.append('file', content);
    xhr = new XMLHttpRequest();
    xhr.open('post', 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart');
    xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
    xhr.onload = function() {
      console.log(xhr.response);
    };
    xhr.send(payload);
};
reader.readAsArrayBuffer(file);

Примечание:

  • В этом измененном сценарии я поместил конечную точку и заголовок, включая токен доступа. Поэтому, пожалуйста, измените это для вашей среды.
  • В этом случае я использовал область действия https://www.googleapis.com/auth/drive.

Справка:

В моем окружении я мог подтвердить, что этот скрипт работает. Но если это не сработало в вашей среде, извините.

...