Google云端硬盘API:通过Multipart API上传二进制文件的正确方法

时间:2018-08-29 15:59:11

标签: javascript file-upload character-encoding google-drive-api multipart

我正在尝试通过将二进制文件上传到Google云端硬盘 multipart upload API v3

这是文件内容的十六进制表示形式:

FF FE

由于某种原因,以上内容被编码为UTF-8(我认为) 当我尝试发布时,包含在多部分有效载荷中:

--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为2的byteLength)。

这是我用来生成多部分负载的最小代码:

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编码? (可能不是,因为 this answer 表示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 "simple" API 不允许我上传到文件夹 (它仅通过Slug HTTP标头接受文件名作为元数据, 而在多部分情况下,JSON元数据对象也允许指定parent文件夹ID)。 (考虑到提及这一点,是因为“简单” API可以正确处理事情 当我直接将File(来自选择器)或ArrayBuffer(来自FileReader#readAsArrayBuffer)发布为XHR的有效载荷时。)

我不想使用任何第三方库,因为

  • 我想让东西尽可能轻,
  • 不考虑创新和最佳实践的东西,由第三方库完成的任何事情也应该可以通过普通JS来完成(这只是fun exercise)。

为了完整起见,我尝试通过GDrive Web界面上传相同的文件,并且上传得很好。 但是,Web界面似乎可以对有效负载进行base64编码,我想避免这种情况 (因为它不必要地使有效负载膨胀,尤其是对于较大的有效负载,这是我最终的目标)。

1 个答案:

答案 0 :(得分:1)

此修改如何?

修改点:

  • 使用new FormData()创建多部分/表单数据。
  • 使用reader.readAsArrayBuffer(file)代替reader.readAsBinaryString(file)
  • 将文件发送为Blob。在这种情况下,数据将以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范围。

参考:

在我的环境中,我可以确认此脚本有效。但是,如果这在您的环境中不起作用,对不起。