Enabling CORS on Google App Engine for a Django Application

2.6k views Asked by At

I have been trying to enable CORS headers on Google app engine but none of the methods that I found over the internet worked for me.

My application is on Python/Django and I want my frontend application (which is hosted separately) to be able to make API calls to my backend platform on Google App Engine.

The January 2017 release notes say that

We are changing the behavior of the Extensible Service Proxy (ESP) to deny cross-origin resource sharing (CORS) requests by default

It can be seenhere

And the solution to enable CORS given by them is to add the following snippet to the service's OpenAPI configuration.

"host": "echo-api.endpoints.YOUR_PROJECT_ID.cloud.goog",
"x-google-endpoints": [
    {
      "name": "echo-api.endpoints.YOUR_PROJECT_ID.cloud.goog",
      "allowCors": "true"
    }
 ],
...

So I followed this example and created two files in my code base

openapi.yml :

swagger: "2.0"
info:
  description: "Google Cloud Endpoints APIs"
  title: "APIs"
  version: "1.0.0"
host: "echo-api.endpoints.<PROJECT-ID>.cloud.goog"  
x-google-endpoints:
 - name: "echo-api.endpoints.<PROJECT-ID>.cloud.goog"
   allowCors: "true"
paths:
  "/api/v1/sign-up":
    post:
      description: "Sends an email for verfication"
      operationId: "signup"
      produces:
      - "application/json"
      responses:
        200:
          description: "OK"
      parameters:
      - description: "Email address of the user"
        in: body
        name: email
        required: true
        schema:
          type: string
      - description: "password1"
        in: body
        name: password1
        required: true
        schema:
          type: string
      - description: "password2"
        in: body
        name: password2
        required: true
        schema:
          type: string

openapi-appengine.yml:

swagger: "2.0"
info:
  description: "Google Cloud Endpoints API fo localinsights backend server"
  title: "Localinsights APIs"
  version: "1.0.0"
host: "<PROJECT-ID>.appspot.com"

Then I ran this command:

gcloud service-management deploy openapi.yml

Then I edited my app.yml file to make it look like this (The addition was endpoints_api_service. Before adding this, the app was getting deployed without any errors):

runtime: python
env: flex
entrypoint: gunicorn -b :$PORT myapp.wsgi

beta_settings:
    cloud_sql_instances: <cloud instance>

runtime_config: 
  python_version: 3

automatic_scaling:
  min_num_instances: 1
  max_num_instances: 1

resources:
  cpu: 1
  memory_gb: 0.90
  disk_size_gb: 10  

env_variables:
  DJANGO_SETTINGS_MODULE: myapp.settings.staging
  DATABASE_URL: <dj-database-url>

endpoints_api_service:
  name: "<PROJECT-ID>.appspot.com"
  config_id: "<CONFIG-ID>"

Then I just deployed the application with

gcloud app deploy

Now, the app got deployed successfully but it is behaving strangely. All the requests which are supposed to return a 200 response still throw CORS error but the ones which return a 400 status do work.

For example - The sign up API expects these fields - email, password1, password2 where password1 should be same as password2. Now when I send correct parameters, I get HTTP 502 saying

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin {origin-url} is therefore not allowed access. The response had HTTP status code 502

But when I send password1 not same as password2, I get HTTP 400 response which I am sure is coming from my code because the response is a dictionary written in the code if password1 and password2 do not match. Also in this case, the headers have Access-Control-Allow-Origin as * but in the former case, that was not true

I also checked my nginx error logs and it says

*27462 upstream prematurely closed connection while reading response header

What am I doing wrong here? Is this the right way to enable CORS in GAE?

2

There are 2 answers

0
Swapnil On BEST ANSWER

After banging my head for several days, I was able to figure out the the real problem. My database server was denying any connection to the webapp server.

Since in case of a HTTP 200 response, the webapp is supposed to make a database call, the webapp was trying to connect to the database server. This connection was taking too long and as soon as it reached beyond the NGINX's timeout time, NGINX used to send a response to the web browser with the status code as 502.

Since the 'access-control-allow-origin' header was being set from the webapp, NGINX did not set that header in its response. Hence the browser was interpreting it as a CORS denial.

As soon as I whitelisted my webapp's instance's IP address for the database server, things started running smoothly

Summary:

  1. There is no need of openapi.yml file to enable CORS for a Django application on GAE flexible environment
  2. Do not miss to check the NGINX logs :p

Update:

Just wanted to update my answer to specify the way through which you won't have to add you instance's IP to the whitelisted IP(s) of the SQL instance

Configure the DATABASES like this:

DATABASES = {
    'HOST': <your-cloudsql-connection-string>, # This is the tricky part
    'ENGINE': <db-backend>,
    'NAME': <db-name>,
    'USER': <user>,
    'PASSWORD': <password>
}

Note the HOST key in the databases. GAE has a way through which you won't have to whitelist your instance's IP but for that to work, the host should be the cloudsql-connection-string and NOT the IP of the SQL instance.

If you are not sure what's your cloudsql-connection-string, go to the Google cloud platform dashboard and select the SQL tab under the Storage section. You should see a table with a column Instance connection name. The value under this column is your cloudsql-connection-string.

2
DvTr On

Nginx as your reverse proxy, so, as the gateway to your server, should be who manage CORS against client browser requests, as first contact from beyond to your system. Should not be any of the backend servers (neither your database, neither anything).

Here you got my default config to enable CORS in nginx from Ajax calls to a REST service of my own (backserver glassfish). Feel free to check and use it and hope it serves to you.

server {
    listen   80; ## listen for ipv4; this line is default and implied
    server_name codevault;

    #Glassfish
    location /GameFactoryService/   {

            index index.html;

                add_header Access-Control-Allow-Origin $http_origin;

                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-NginX-Proxy true;
                proxy_ssl_session_reuse off;
                proxy_set_header Host $http_host;
                proxy_redirect off;
                proxy_set_header X-Forwarded-Server $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_pass http://127.0.0.1:18000/GameFactoryService/;

        }

        #static content
    location / {
        root /usr/share/nginx_static_content;
    }

    error_page 500 501 502 503 504 505 506 507 508 509 510 511 /50x.html;

    #error
        location = /50x.html {

      add_header Access-Control-Allow-Origin $http_origin;          
          internal;          
    }
}