Django, Heroku, boto: direct file upload to Google cloud storage

boto, django, google-cloud-storage

Solution

The header issue is not coming from your app, I think it's coming from the cloud storage bucket. I had the same issue when setting up an api, the resource you are posting to is missing the header.

https://cloud.google.com/storage/docs/cross-origin

While useful for preventing malicious behavior, this security measure also prevents useful and legitimate interactions between known origins. For example, a script on a page hosted from Google App Engine at example.appspot.com might want to use static resources stored in a Cloud Storage bucket at example.storage.googleapis.com. However, because these are two different origins from the perspective of the browser, the browser won't allow a script from example.appspot.com to fetch resources from example.storage.googleapis.com using XMLHttpRequest because the resource being fetched is from a different origin.

So it looks like you need to configure the bucket to allow cors requests. The google documentation shows the following code to be run from the google cli.

https://cloud.google.com/storage/docs/cross-origin#Configuring-CORS-on-a-Bucket

gsutil cors set cors-json-file.json gs://example

[
    {
      "origin": ["http://mysite.heroku.com"],
      "responseHeader": ["Content-Type"],
      "method": ["GET", "HEAD", "DELETE", "PUT"],
      "maxAgeSeconds": 3600
    }
]

Which would allow you get, upload, and delete content. Hope that helps.

Problem

In Django projects deployed on Heroku, I used to upload files to Google cloud storage via boto. However, recently I have to upload large files which will cause Heroku timeout. I am following Heroku's documentation about direct file upload to S3, and customizing as follows: Python: ``` conn = boto.connect_gs(gs_access_key_id=GS_ACCESS_KEY, gs_secret_access_key=GS_SECRET_KEY) presignedUrl = conn.generate_url(expires_in=3600, method='PUT', bucket=<bucketName>, key=<fileName>, force_http=True) ``` JS: ``` url = 'https://<bucketName>.storage.googleapis.com/<fileName>?Signature=...&Expires=1471451569&GoogleAccessId=...'; // "presignUrl" postData = new FormData(); postData.append(...); ... $.ajax({ url: url, type: 'PUT', data: postData, processData: false, contentType: false, }); ``` I got the following error message: ``` XMLHttpRequest cannot load http:/... Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access. ``` EDIT: The output of `gsutil cors get gs://<bucketName>`: ``` [{"maxAgeSeconds": 3600, "method": ["GET", "POST", "HEAD", "DELETE", "PUT"], "origin": ["*"], "responseHeader": ["Content-Type"]}] ``` It seems the CORS is OK. So, how do I solve the problem? Thanks. EDIT 2: The header of the OPTION request from Firefox: ``` Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.5,en;q=0.3 Access-Control-Request-Method: PUT Connection: keep-alive Host: <bucketName>.storage.googleapis.com Origin: http://localhost:8000 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:48.0) Gecko/20100101 Firefox/48.0 ``` The header of the OPTION request from Chrome: ``` Accept:*/* Accept-Encoding:gzip, deflate, sdch Accept-Language:zh-TW,zh;q=0.8,en;q=0.6,en-US;q=0.4,zh-CN;q=0.2 Access-Control-Request-Headers: Access-Control-Request-Method:PUT Connection:keep-alive Host:directupload.storage.googleapis.com Origin:http://localhost:8000 Referer:http://localhost:8000/ User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 X-Client-Data:CIe2yQEIprbJAQjznMoB ```

Original source