Reputation: 2195
I have a Google App Engine app that works fine in production when everything is running on one host, and mostly works when the web app is running on a separate host. All queries to/from the server (GET
, POST
, PUT
, DELETE
) are behaving as expected. This indicates to me that I have all CORS configured correctly throughout the system (I fought that battle several weeks ago and have it all worked out).
The only piece that I cannot make work is file uploads. I am using django
, djangoappengine
, django-cors-headers
, and filetransfers
, and the net result of everything is that I am unable to upload files when running from a remote server but everything else is working correctly. In the JavaScript console in Chrome I am seeing the following error:
XMLHttpRequest cannot load http://localhost:8080/_ah/upload/ahl...<truncated>.
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:9000' is therefore not allowed
access. The response had HTTP status code 405.
This is clearly a CORS error, so I know roughly what needs to happen. Except I can't figure out how to make the necessary change to my configuration to overcome this.
Here's my overall setup:
dev_appserver.py
serving the API on port 8080grunt serve
serving the client app on port 9000CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = [ '(app.domain.com for my app)' ]
In production, I believe the fix will be to configure CORS on my bucket, but I'm not positive. However, I have no idea how to configure the local development server for this so that I can test the overall flow of data before I deploy.
Here's the JavaScript that's ultimately failing (the app is using AngularJS
):
var form = angular.element('#media-form');
var data = new FormData(form);
// have the API return a URL using prepare_upload in
// filetransfers module to upload to:
var uploadActionUrl = https://api.domain.com/upload_url/';
$http.get(uploadActionUrl)
.then(function(response) {
// I get here with no problem
$http.post(response.data.action, formData)
.then(function(response) {
console.log('got:', response);
}, function(error) {
console.log('upload error:', error); // <- this is where I end up
});
}, function(error) {
console.log('get upload URL error:', error);
});
Again, code very much like this functions correctly when run from the same host (so the API itself is functioning correctly), and (importantly) all HTTP methods work on all endpoints other than uploading my file, so CORS itself is set up correctly for interactions with App Engine. It's only the file upload piece that is not functioning.
It occurs to me that perhaps the fix includes assembling my form for upload using JSON instead of FormData
, but I have never found a way to do this in the past.
--- UPDATED TO ADD ---
As a point of clarification, the endpoint which is causing this error is not inside my app directly, it is at a URL handled by a separate Google service. The code that gives me the URL is:
from google.appengine.ext.blobstore import create_upload_url
def prepare_upload(request, url, **kwargs):
return create_upload_url(
url,
gs_bucket_name = settings.GOOGLE_CLOUD_STORAGE_BUCKET
), {}
The URL I get back is of the form /_ah/upload/<one-time key>
, and everything that happens at that URL is (it seems) outside of my control, including adding headers.
Upvotes: 3
Views: 1206
Reputation: 319
Your http handlers must have OPTIONS method for sending cors headers to browser.
For example; Chrome always sends an OPTIONS request to same URL before PUT requests for checking CORS headers. If browser can't get response for OPTIONS request, cors will fail.
Check this app engine webapp2 example.
class BaseRestHandler(webapp2.RequestHandler):
def options(self, *args, **kwargs):
self.response.headers['Access-Control-Allow-Origin'] = '*'
self.response.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept'
self.response.headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE'
https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
Upvotes: 1
Reputation: 3893
One way is to set http headers for the particular url in your app.yaml
So for example:
handlers:
- url: /_ah/upload....
...
http_headers:
Access-Control-Allow-Origin: http://localhost:9000
Upvotes: 1