1

I am developing a single page web application using Python, Flask & Angular on GCP (google cloud platform) and have run into a stubborn issue related to CORS. When I build and serve the frontend I get the following error:

Access to XMLHttpRequest at 'https://jsonData-devshell.appspot.com/org' from origin 'https://mySPA-devshell.appspot.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

BACKEND

To provision CORS I have imported the flask_cors module and added it to the server file by adding CORS(app) in my server file which is main.py.

main.py

from .entities.entity import Session, eco_env_engine, Base
from .entities.org import Org, OrgSchema
from flask import Flask, jsonify
from flask_cors import CORS


app = Flask(__name__)
# enable cross origin resource sharing
CORS(app)

Base.metadata.create_all(eco_env_engine)

@app.route('/org', methods=['GET'])
def get_org():
    session = Session()
    org_objects = session.query(Org).all()
    # transform into JSON-serializable objects
    schema = OrgSchema(many=True)
    org = schema.dump(org_objects)
    # serialize as json
    session.close()
    response = jsonify(org)
    return response

I have a shell script server.sh that creates an interface that I can access at http://0.0.0.0:5000.

server.sh


# set ../src/main.py as the value of the FLASK_APP environment variable
export FLASK_APP=../src/main.py
# FLASK_ENV=development
# FLASK_DEBUG=1

#activate the virtual environment
source $(pipenv --venv)/bin/activate

#run flask listening on all interfaces
#-h binds to interface
flask run -h 0.0.0.0

When I use curl -I http://0.0.0.0:5000/org I get back

HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 4766
Access-Control-Allow-Origin: *
Server: Werkzeug/1.0.0 Python/3.7.3
Date: Wed, 01 Apr 2020 01:27:11 GMT

notice the Access-Control-Allow-Origin:* header which in my understanding should address the issue but it does not.

I am writing my code within the GCP Cloud Shell editor and deploying from within the GCP Cloud Shell Terminal. To get things running I deploy my backend with ./server.sh & and frontend with ng serve

CORS variations I have tried:

CORS(app, resource={r'*/*': {'origins': '*/*'}})
CORS(app, resources={r"*": {"origins": ['https://mySPA-devshell.appspot.com', 'https://jsonData-devshell.appspot.com']}})
CORS(app, resources={r'*': {"origins": "*"}})
CORS(app, resources={r"*": {"origins": ['http://localhost:4200', 'http://localhost:5000']}})

FRONTEND

org-api.service.ts

import {Injectable} from '@angular/core';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import {HttpHeaders, HttpClientModule} from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import {API_URL} from '../env';
import {Org} from './org.model';
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

@Injectable()
export class OrgApiService {

  //create private variable 'http' of type HttpClient
  constructor(private http: HttpClient) {
  }

  private static _handleError(err: HttpErrorResponse | any) {
    // return Observable.throw(err.message || 'Error: Unable to complete request.');
    return throwError(err.message || 'Error: Unable to complete request.');
  }

  getOrg(): Observable<Org[]> {
    return this.http    
      .get<Org[]>(`${API_URL}/org`)
      .pipe(catchError(OrgApiService._handleError));
  }

}

org.component.ts

import {Component, OnDestroy, OnInit} from '@angular/core';
import {Subscription} from 'rxjs/Subscription';
import {Org} from './org.model';
import {OrgApiService} from './org-api.service';

@Component({
  selector: 'org',
   template: `
     <div>
       <ul>
         <li *ngFor="let org of orgList">
           {{org.name}}
         </li>
       </ul>
     </div>
   `,
  styleUrls: ['org.component.css'],
})

export class OrgComponent implements OnInit, OnDestroy {
  orgListSubs: Subscription;
  orgList: Org[];
  authenticated = false;

  constructor(private orgApi: OrgApiService) { }

  ngOnInit() {
    this.orgListSubs = this.orgApi
      .getOrg()
      .subscribe(res => {
          this.orgList = res;
        },
        console.error
      );
    const self = this;
  }

  ngOnDestroy() {
    this.orgListSubs.unsubscribe();
  }
}

Additionally, I came across the following solution and decided to employ it because of why not...to no avail: Create a proxy.conf.json in my frontend directory with the following contents

{
    "/": {
        "target": "http://0.0.0.0:5000",
        "secure": false,
        "logLevel": "debug"
    }
}

then added a proxyConfig option to the serve target.

UPDATE

@sideshowbarker pointed out the 302 response in some cases does not have a 'Access-Control-Allow-Origin' header which in my case is true. Upon further digging I came across this post which mentions chrome cancelling a request upon receiving status code 302. It also put me on to chrome://net-export/ where I discovered my request is being cancelled as well.

There is no apparent/accepted solution for this that I have across yet. If you are reading this and have a suggestion.. please share.

t=131761 [st=  4]      QUIC_CONNECTION_MIGRATION_MODE
                       --> connection_migration_mode = 0
t=131761 [st=  4]     +HTTP_TRANSACTION_SEND_REQUEST  [dt=0]
t=131761 [st=  4]        HTTP_TRANSACTION_QUIC_SEND_REQUEST_HEADERS
                         --> :method: GET
                             :authority: 5000-dot-XXXXXXX-dot-devshell.appspot.com
                             :scheme: https
                             :path: /
                             accept: application/json, text/plain, */*
                             sec-fetch-dest: empty
                             user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
                             origin: https://4200-dot-XXXXXXX-dot-devshell.appspot.com
                             sec-fetch-site: cross-site
                             sec-fetch-mode: cors
                             referer: https://4200-dot-XXXXXXX-dot-devshell.appspot.com/?authuser=0&environment_name=default
                             accept-encoding: gzip, deflate, br
                             accept-language: en-US,en;q=0.9
                         --> quic_priority = 4
                         --> quic_stream_id = 5
t=131761 [st=  4]     -HTTP_TRANSACTION_SEND_REQUEST
t=131761 [st=  4]     +HTTP_TRANSACTION_READ_HEADERS  [dt=168]
t=131928 [st=171]        HTTP_TRANSACTION_READ_RESPONSE_HEADERS
                         --> HTTP/1.1 302
                             status: 302
                             date: Wed, 01 Apr 2020 06:33:46 GMT
                             content-type: text/html; charset=utf-8
                             content-length: 498
                             location: https://some.url.com
                             via: 1.1 google
                             alt-svc: quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,h3-T050=":443"; ma=2592000
t=131929 [st=172]     -HTTP_TRANSACTION_READ_HEADERS
t=131929 [st=172]      HTTP_CACHE_WRITE_INFO  [dt=0]
t=131929 [st=172]      HTTP_CACHE_WRITE_DATA  [dt=0]
t=131929 [st=172]      HTTP_CACHE_WRITE_INFO  [dt=0]
t=131929 [st=172]      NETWORK_DELEGATE_HEADERS_RECEIVED  [dt=1]
t=131930 [st=173]      URL_REQUEST_DELEGATE_RECEIVED_REDIRECT  [dt=0]
t=131930 [st=173]      CANCELLED
t=131930 [st=173] -REQUEST_ALIVE

I have spent days trying to solve this however I have been largely unsuccessful. I feel as though there is a simpler solution to this than everything I have tried. Thank you in advance for your help!

user3890141
  • 111
  • 1
  • 1
  • 10
  • Response Headers.status: 302 . It provides a link to the location that contains the data I am looking for. @sideshowbarker – user3890141 Apr 01 '20 at 02:31
  • So it seems that maybe the problem is that the 302 response doesn’t have the Access-Control-Allow-Origin header. Some (most) servers by default don’t add additional headers to 3xx redirects. Have you tried taking the URL in the value of the Location header in that 302 response, and changing your code to make the request directly to that URL? (Maybe the difference between your existing request URL and the URL in that Location header is just a `/` trailing slash.) – sideshowbarker Apr 01 '20 at 02:36
  • @sideshowbarker should I make the call in `org-api.service.ts` by replacing ``${API_URL}/org`` in `get(\`${API_URL}/org\`)` with the location header url? – user3890141 Apr 01 '20 at 03:07
  • @sideshowbarker I called the location url as I described it above. In response I received status:400. The location url I called appears to be the result of 2FA; it is an extremely long url. When I paste this url into the browser it changes to https://jsonData-devshell.appspot.com/org. I then tried calling https://jsonData-devshell.appspot.com/org as described in the previous comment with and got status 302 – user3890141 Apr 01 '20 at 03:44
  • Is the `https://jsondata-devshell.appspot.com/org` server not one that you control? You don’t have admin access to that server environment? – sideshowbarker Apr 01 '20 at 04:04
  • This is not a direct answer because I do not know for sure. If it helps, I do not create the devshell.appspot.com domain (also the actual domain is 5000-dot-XXXXXXX-dot-devshell.appspot.com), this is GCP Cloud Shell generated. 'main.py' connects to a cloudsql instance on GCP. I interface with this locally(cloud shell terminal) on http://0.0.0.0:5000. I did notice that the port i define for the interface corresponds to the url that is generated hence the 5000-dot-XXXXXXX-dot-devshell.appspot.com – user3890141 Apr 01 '20 at 04:30
  • Is your backend serving static content? If so, you should [set the http_headers property for your handlers](https://cloud.google.com/appengine/docs/standard/python3/config/appref#cors-support) on your app.yaml – Jose V Apr 01 '20 at 14:30
  • @JoseV dynamic content generated from a call to the cloudsql database. The app isn't deployed to app engine yet. I am running this in my terminal from a compute engine environment. – user3890141 Apr 02 '20 at 19:06

1 Answers1

1

SOLVED!

edited env.ts in frontend/app to

export const API_URL = 'http://localhost:4200'

instead of localhost:5000

API_URL corresponds to the ng serve development deployment

user3890141
  • 111
  • 1
  • 1
  • 10