Does https://www.googleapis.com/upload/drive/v2/fi

2019-01-20 15:03发布

问题:

Update: This is a bug in GoogleDrive, CORS is not enabled for upload URIs. @Nivco pointed me to a work around with Google's client library which uses an iframe and a proxy (not CORS). I'm putting the (tested) working code at the bottom, along with a detailed explanation. Please see the answer, below for the example.

Inserting File to Google Drive through API and Authorization of Google Drive using JavaScript say that the upload endpoints support CORS, but I haven't been able to use them. I can get authorization and insert an empty file, using Files: insert, but I can't upload content to it -- I get a 405 (Method not allowed) error when I use https://www.googleapis.com/upload/drive/v2/files when I use either of the two techniques given in the example in the inserting file stack overflow post.

Is it possible that CORS worked for v1 and hasn't been enabled for v2?

EDIT: By the way, the 405 error is on the OPTIONS request that chrome is making.

EDIT: Here's the code from one of my attempts:

Before I present the code I want to highlight that I am able to authenticate and list files. I just can't upload data to a file.

var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://www.googleapis.com/upload/drive/v2/files?uploadType=multipart');
xhr.setRequestHeader('Authorization', 'Bearer ' + params.access_token);
xhr.setRequestHeader("Content-Type",  'multipart/related; boundary="END_OF_PART"');
xhr.onreadystatechange = function(data) {
  if (xhr.readyState == DONE) {
    document.getElementById("files").innerHTML = "Uploaded file: " + xhr.responseText;
    };
  }
xhr.send([
  mimePart("END_OF_PART", "application/json", json),
  mimePart("END_OF_PART", "text/plain", "a\nb\n"),
  "\r\n--END_OF_PART--\r\n",
].join(''));
function mimePart(boundary, mimeType, content) {
  return [
    "\r\n--", boundary, "\r\n",
    "Content-Type: ", mimeType, "\r\n",
    "Content-Length: ", content.length, "\r\n",
    "\r\n",
    content,
  ].join('');
}

Here is the request:

Request URL:https://www.googleapis.com/upload/drive/v2/files?uploadType=multipart
Request Method:OPTIONS

Here is the response:

Status Code:405 Method Not Allowed
cache-control:no-cache, no-store, must-revalidate
content-length:0
content-type:text/html; charset=UTF-8
date:Mon, 23 Jul 2012 22:41:29 GMT
expires:Fri, 01 Jan 1990 00:00:00 GMT
pragma:no-cache
server:HTTP Upload Server Built on Jul 17 2012 16:15:04 (1342566904)
status:405 Method Not Allowed
version:HTTP/1.1

There is no response, because Chrome gets a 405 error for that OPTIONS request. There is no POST, because Chrome can't proceed, since its OPTIONS request failed with a 405, and so it prints this error in the console:

XMLHttpRequest cannot load https://www.googleapis.com/upload/drive/v2/files?uploadType=multipart. Origin https://leisurestorage.appspot.com is not allowed by Access-Control-Allow-Origin.

回答1:

It seems that you are right, the upload API endpoints don't seem to support CORS requests whereas the other endpoints do support it (sorry for not having tested thoroughly). This is a bug and we have let our engineering team know about the issue.

In the mean time it seems that the only workaround is to use the Javascript client library and take advantage of the iframe proxy it uses as described in Authorization of Google Drive using JavaScript

Thanks for bringing this up!



回答2:

CORS is fully enabled now. See https://github.com/googledrive/cors-upload-sample for an example of how to do resumable uploads with vanilla JS.



回答3:

This answer (and in fact the question itself) is now redundant given full CORS support as confirmed by Steve Bazyl

Working code, using @Nivco's help, along with detailed explanation:

Here is the working code for a full test of this technique. To use this, you need to make two pages. The first page authenticates and launches the second page, which is your actual app. In order to be able to access the Google Drive API to upload a file, you need to register an app, which is described here.

Your first page will use OAuth, which is described in this Stackoverflow answer. It calls your app with a fragment that looks like this:

#access_token=ya29.AHES6ZSb4M4ju8U_X_zgFrz_MD2RsjrQu5V05HjsBtrCl0nh2SrnaA&token_type=Bearer&expires_in=3600

In JavaScript, you can access that fragment with location.hash. After you save the value, it's a good idea to set location.hash to the empty string right away, so that it doesn't show up in the browser's location bar. Your app needs to use the value of access_token from the fragment in its CORS requests and also in the proxied (non-CORS) request to the upload API. Here is an example launch page, which is really just a version of the code from OAuth example:

<html>
  <body>
    <a href="javascript:poptastic('https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file&client_id=270759921607.apps.googleusercontent.com&redirect_uri=https://leisurestorage.appspot.com&response_type=token');">Authorize Leisure Storage</a><br>
    <script>
      function poptastic(url) {
        var newWindow = window.open(url, 'name', 'height=600,width=450');
        if (window.focus) {
          newWindow.focus();
        }
      }
    </script>
  </body>
</html>

Here is an example app that uploads a\na\b\n to a file called leisureUpload in your GoogleDrive, using Google's Client Library for JavaScript. There is no need to use any of the gapi.auth methods, because it uses a raw gapi.client.request() call with the Authorization header directly in the call, just like it would with xmlHttpRequest() using CORS:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Leisure</title>
    <script type="text/javascript">
    var hash = location.hash.substring(1).split('&');
    location.hash = '';
    var params = {};

    for (var i = 0; i < hash.length; i++) {
        var p = hash[i].split('=');

        params[p[0]] = p[1];
    }
    function gapiClientLoaded() {/* do nothing */}
    function uploadTestFile() {
        var json = JSON.stringify({
            mimeType: 'text/plain',
            title: 'leisureUpload',
        });
        var xhr = new XMLHttpRequest();

        gapi.client.request({
            'path': '/upload/drive/v1/files',
            'method': 'POST',
            'params': {'uploadType': 'multipart'},
            'headers': {
                'Content-Type': 'multipart/mixed; boundary="END_OF_PART"',
                'Authorization': 'Bearer ' + params.access_token,
            },
            'body': [
                mimePart("END_OF_PART", "application/json", json),
                mimePart("END_OF_PART", "text/plain", "a\nb\n"),
                "\r\n--END_OF_PART--\r\n",
            ].join('')
        }).execute(function(file) {
            document.getElementById("result").innerHTML = "Uploaded file: " + file;
        });
    }
    function mimePart(boundary, mimeType, content) {
        return [
            "\r\n--", boundary, "\r\n",
            "Content-Type: ", mimeType, "\r\n",
            "Content-Length: ", content.length, "\r\n",
            "\r\n",
            content,
        ].join('');
    }
    </script>
    <script src="https://apis.google.com/js/client.js?onload=gapiClientLoaded"></script>
  </head>
  <body>
    <h1>Welcome to Leisure!</h1>
    <button onclick="uploadTestFile()">Upload Test File</button><br>
    <pre id="result"></pre>
  </body>
</html>