21
votes

We are generating resumable upload url through the cloud storage JSON API from our App Engine application which are used on mobile as well as a web app. In the web app, using XmlHttpRequest to upload a file with the resumable upload url we get the following error :

XMLHttpRequest cannot load https://www.googleapis.com/upload/storage/v1beta2/b/... No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://ourapp.appspot.com' is therefore not allowed access.

In Chrome developer tools, the network log show a first OPTIONS request with the appropriate "Origin" request header and "Access-Control-Allow-Origin" response header but the following PUT request fails as mentioned.

The cors xml on our bucket looks like this:

<?xml version="1.0" encoding="UTF-8"?>
    <CorsConfig>
      <Cors>
        <Origins>
          <Origin>*</Origin>
        </Origins>
        <Methods>
          <Method>PUT</Method>
          <Method>GET</Method>
          <Method>POST</Method>
          <Method>HEAD</Method>
          <Method>DELETE</Method>
          <Method>OPTIONS</Method>
        </Methods>
        <ResponseHeaders>
          <ResponseHeader>*</ResponseHeader>
        </ResponseHeaders>
        <MaxAgeSec>1800</MaxAgeSec>
      </Cors>
    </CorsConfig>

Any suggestions are welcome.

Thanks.

5
I'm having this same problem -- CORS is set up correctly and I have no problems with any of the other service endpoints. Did you ever fix it?ovangle
@ovangle No, We gave up on using upload URL for the time being, we are using the XML Api instead.Jkmn
are you using the cors query string parameter in your PUT request?akgill
I'm actually experiencing the exact same issue. @akgill I believe the ?cors query string is for changing the cors headers associated with a bucket. Not for making resumable upload requests to a bucket as we are trying to do here.Wilgert
You don't need to set up CORS on the bucket. Just set the origin correctly when requesting the resumable upload url.Emlyn O'Regan

5 Answers

6
votes

Ran into this problem and discovered that it was due to a missing "origin" header in the initial POST request coming from App Engine.

My POST request contains content-length (set to 0), x-upload-content-type, and origin and all is well.

[1]https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload

3
votes

I had the same problem, and the solution was to add a Header Origin: "https://ourapp.appspot.com" to the initial resumable request.

However, some librares, for example sun.net.www.protocol.http.HttpURLConnection doesn't allow you to change the Origin header because of the following variable :

restrictedHeaders = new String[]{"Access-Control-Request-Headers", "Access-Control-Request-Method", "Connection", "Content-Length", "Content-Transfer-Encoding", "Host", "Keep-Alive", "Origin", "Trailer", "Transfer-Encoding", "Upgrade", "Via"};

My workaround was to create a new HttpRequest with a library that allows to update the Origin header. I used Okhttp in my case (as former Android developper).

OkHttpClient client = new OkHttpClient();
AppIdentityService appIdentityService = credential.getAppIdentityService();
Collection<String> scopes = credential.getScopes();
String accessToken = appIdentityService.getAccessToken(scopes).getAccessToken();
Request request = new Request.Builder()
        .url("https://www.googleapis.com/upload/storage/v1/b/" + bucket + "/o?name=" + fileName + "&uploadType=resumable")
        .post(RequestBody.create(MediaType.parse(mimeType), new byte[0]))
        .addHeader("X-Upload-Content-Type", mimeType)
        .addHeader("X-Upload-Content-Length", "" + length)
        .addHeader("Origin", "https://ourapp.appspot.com")
        .addHeader("Origin", "*")
        .addHeader("authorization", "Bearer "+accessToken)
        .build();
Response response = client.newCall(request).execute();
return response.header("location");
2
votes

This is a known problem with using resumable upload with the JSON API. I assume the "origin" used to start the resumable upload and the "origin" used to upload data are different in your case, right?

There are two parts involved in this issue:

1) When using resumable upload protocol, the "origin" from the first (start upload) request is always used to decide the "access-control-allow-origin" header in the response, even if you use a different "origin" for subsequent requests.

2) The CORS config in GCS only works for the XML API. I think our documentation could use some improvement to make this more clear, right now it's only kind of mentioned here (https://developers.google.com/storage/docs/cross-origin#Sending-a-Cross-Domain_Request) if you click through the link to see what request URIs will be responsive to the CORS config. The JSON API ignores the CORS config, and will always allow cross-origin access for the "origin" in the request.

So if you use resumable upload with JSON API, it will only use the "origin" from the first request, and set "access-control-allow-origin" header to that origin. Thus, if the origin changes in subsequent upload requests, they will not work.

Currently you have two ways to work around this issue:

1) Use the same origin for the first and subsequent requests.

2) Switch to use the XML API, and set the CORS config to <Origin>*</Origin>.

1
votes

The problem is not with the CORs document shown above. One can upload files with xhr if the xhr request is formdata as described here: http://www.html5rocks.com/en/tutorials/file/xhr2/#toc-sending . If the request is not FormData we get the 'Access-Control-Allow_Origin' error.

This works for me:

$("input[type=file]").change(function() {

    var formData = new FormData();

    formData.append("field, ...);
    formData.append("field, ...);
    formData.append("file", filesList[0]);

    var xhr = new XMLHttpRequest();
    xhr.open('POST', "https://my-bucket.storage.googleapis.com/", true);
    xhr.onload = function(e) { 
        console.log("File Uploaded!")
    };

    xhr.send(formData);
}

See a full working node.js example here: https://github.com/sfarthin/crop-rotate-and-sample-in-browser

-1
votes

The endpoint https://www.googleapis.com is not allowing Cross-Origin Resource Sharing (CORS). That means all browsers will prevent requests when issued from a website which is not running on the origin host (www.googleapis.com).

Make sure you configured CORS for your bucket e.g.:

<?xml version="1.0" encoding="UTF-8"?>
<CorsConfig>
  <Cors>
    <Origins>
      <Origin>https://ourapp.appspot.com</Origin>
    </Origins>
    <Methods>
      <Method>GET</Method>
      <Method>HEAD</Method>
      <Method>PUT</Method>
    </Methods>
  </Cors>
</CorsConfig> 

If it is still not working set <Origin>*</Origin>, try with curl and report the outcome.