How to upload files to Google drive using gapi and resumable uploads?
Asked Answered
C

2

13

I'm trying to follow this guide for doing resumable uploads on Google Drive through Google Api's.

This is my code, you can see it makes 2 requests as the guide asks to, the first part creates the metadata, and then we use the location for starting uploading the files with the session created by the first request.

        const file = new File(['Hello, world!'], 'hello world.txt', { type: 'text/plain;charset=utf-8' });
        const contentType = file.type || 'application/octet-stream';

        const reqMetadata = gapi.client.request({
            'path': 'upload/drive/v3/files',
            'method': 'POST',
            'params': { 'uploadType': 'resumable' },
            'headers': {
                'X-Upload-Content-Type': file.type,
                'X-Upload-Content-Length': file.size,
                'Content-Type': 'application/json; charset=UTF-8'
            },
            'body': {
                'name': file.name,
                'mimeType': contentType,
                'Content-Type': contentType,
                'Content-Length': file.size
            }
        });

        reqMetadata.execute((respMetadata, rawRespMetadata: any) => {
            const locationUrl = JSON.parse(rawRespMetadata).gapiRequest.data.headers.location;

            const reader = new FileReader();

            reader.onload = (e) => {
                const reqFile = gapi.client.request({
                    'path': locationUrl,
                    'method': 'PUT',
                    'headers': {
                        'Content-Type': file.type,
                        'Content-Length': file.size
                    },
                    'body': reader.result
                });

                reqFile.execute(respFile => {
                    console.log(respFile);
                });
            };

            reader.readAsArrayBuffer(file);
        });

What's the problem?

Well, seems that the Google Api library does not like the File / byte array as body on their gapi.client.request and they're truncating it away see image

What's the correct way to pass the file? I tried both body: file and body: reader.result but same result

PS: gapi is already fully authenticated & initialized with auth2, I'm able to create files / directory.

EDIT 1:

gapi library is just jsoing the FileArray, therefore the JSON function modifies it to a empty object, no way to make it work.. something must be missing.

EDIT 2:

I made it work without the GAPI, it correctly uploads the file but I have some issues with the CORS

            reader.onload = (e) => {                    

                const authHeader = `Bearer ${this.auth.currentUser.get().getAuthResponse().access_token}`;
                const headers = new Headers({
                    'Authorization': authHeader,
                    'Content-Type': file.type
                });
                const options = new RequestOptions({ headers });
                const url = locationUrl;

                this.http.put(url, reader.result, options).subscribe(res => {
                    observer.next(res);
                }, (err) => {
                    observer.next({});
                });
            };

            reader.readAsArrayBuffer(file);

If somebody has some hints..

Citron answered 11/9, 2017 at 16:39 Comment(6)
What GAPI version are you using? Also, the GAPI seems to be using sreams for uploading ifles in NodeJS. You could try with thisAdhamh
Should be the latest version, where did you find documentation about the use of streams (sockets)?Citron
Here, look at the NodeJS examples.Adhamh
I'm on the browser, can't use NodeJS streams.Citron
Have you tried to send feedback about the issue developers.google.com/drive/v3/web/…?Boutwell
good idea, thanks. issuetracker.google.com/issues/66142841Citron
S
6

You must use XMLHttpRequest to make a cross origin HTTP request. The gapi client does not support XMLHttpRequest. (but there is this pull request that's been open for a while) Even though you aren't sending the file binary data in the initial request, you must use XMLHttpRequest for both the initial and the request where the file is uploaded in order for the returned location url provide the appropriate response header (Access-Control-Allow-Origin: YOUR_URL) and satisfy the CORS requirements.

Here is a great tutorial about CORS and XMLHttpRequest that may be useful in converting your requests.

You can use the request info described in the page you linked to. This example shows the request info but does not provide any info on getting the auth token. But this example does!

I was able to successfully upload the file using the following code:

const file = new File(['Hello, world!'], 'hello world.txt', { type: 'text/plain;charset=utf-8' });
const contentType = file.type || 'application/octet-stream';
const user = gapi.auth2.getAuthInstance().currentUser.get();
const oauthToken = user.getAuthResponse().access_token;
const initResumable = new XMLHttpRequest();
initResumable.open('POST', 'https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable', true);
initResumable.setRequestHeader('Authorization', 'Bearer ' + oauthToken);
initResumable.setRequestHeader('Content-Type', 'application/json');
initResumable.setRequestHeader('X-Upload-Content-Length', file.size);
initResumable.setRequestHeader('X-Upload-Content-Type', contentType);
initResumable.onreadystatechange = function() {
  if(initResumable.readyState === XMLHttpRequest.DONE && initResumable.status === 200) {
    const locationUrl = initResumable.getResponseHeader('Location');
    const reader = new FileReader();
    reader.onload = (e) => {
      const uploadResumable = new XMLHttpRequest();
      uploadResumable.open('PUT', locationUrl, true);
      uploadResumable.setRequestHeader('Content-Type', contentType);
      uploadResumable.setRequestHeader('X-Upload-Content-Type', contentType);
      uploadResumable.onreadystatechange = function() {
        if(uploadResumable.readyState === XMLHttpRequest.DONE && uploadResumable.status === 200) {
          console.log(uploadResumable.response);
         }
      };
      uploadResumable.send(reader.result);
    };
    reader.readAsArrayBuffer(file);
  }
};

// You need to stringify the request body containing any file metadata

initResumable.send(JSON.stringify({
  'name': file.name,
  'mimeType': contentType,
  'Content-Type': contentType,
  'Content-Length': file.size
}));

But there is a more robust repo for dealing with all this here: https://github.com/googledrive/cors-upload-sample

Sweaty answered 21/9, 2017 at 2:0 Comment(3)
Thanks man, you deserve the bounty. I did the same you did, but the angular $http service at the end was giving me some CORS error. Probably I messed up some http-header and the drive rest was refusing me to upload file.Citron
Added an additional answer below with the Angular http service instead of plain xhrCitron
getting gapi.auth2.getAuthInstance() is nullComplacence
C
1

This is the BMcV solution translated to angular http service.

const contentType = file.type || 'application/octet-stream';
const baseRoot = gapi['config'].get('googleapis.config').root;

const reader = new FileReader();

reader.onload = (e) => {

    const authHeader = `Bearer ${this.auth.currentUser.get().getAuthResponse().access_token}`;

    const metadataHeaders = {
        'Authorization': authHeader,
        'Content-Type': 'application/json',
        'X-Upload-Content-Length': file.size,
        'X-Upload-Content-Type': contentType
    };
    const metadataOptions = new RequestOptions({ headers: new Headers(metadataHeaders) });

    const url = `${baseRoot}/upload/drive/v3/files?uploadType=resumable`;

    const metadata = {
        'name': file.name,
        'mimeType': contentType,
        'Content-Type': contentType,
        'Content-Length': file.size
    };

    this.http.post(url, metadata, metadataOptions).subscribe(metadataRes => {

        const locationUrl = metadataRes.headers.get('Location');

        const uploadHeaders = {
            'Content-Type': contentType,
            'X-Upload-Content-Type': contentType
        };
        const uploadOptions = new RequestOptions({ headers: new Headers(uploadHeaders) });

        this.http.put(locationUrl, reader.result, uploadOptions).subscribe(uploadRes => {
            console.log(uploadRes.json());
        });
    }, (err) => {
        console.error(err);
    });
};
reader.readAsArrayBuffer(file);
Citron answered 21/9, 2017 at 9:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.