Search code examples
pythonflaskgoogle-cloud-platformcorsflask-cors

Enabling CORS Python in Google Cloud Platform Function


I've followed a few Medium articles as well as Google's own tutorials and still can't get CORS enabled using flask on this function :(

What code am I missing to enable CORS? I want wildcard of * as I want all services/ domains to be able to access this and other functions.

I've heard GCP does not work well with @app. Why, I am not so sure.

Note I have been successful with testing outputs directly on my browser's url bar as well as Triggering a JSON test within GCP itself.

My intended use is for GET requests only.

from flask import Flask
import re

def hello_method(request):
    from flask import abort

    if request.method == 'GET':
        
        headers = {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'GET',
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Max-Age': '3600'
        }

        request_json = request.get_json()
        pattern = re.compile("([0-9]+(\.[0-9]+)?)")
        
        if request.args and 'x' and 'y' in request.args:
            x_str = request.args.get('x')
            y_str = request.args.get('y')

            if not (pattern.match(x_str)):
                rtn = "{\"error\":true,\"string\":" + "NaN" + "*" + y_str + "=" + "NaN" + ",\"answer\":" + "NaN" +"}"
                return (rtn, 204, headers)
            if not (pattern.match(y_str)):
                rtn = "{\"error\":true,\"string\":" + x_str + "*" + "NaN" + "=" + "NaN" + ",\"answer\":" + "NaN" +"}"
                return (rtn, 204, headers)

            x = float(x_str)
            y = float(y_str)
            print("Args X: " + str(x))
            print("Args Y: " + str(y))
            ans = x * y
            print("Args Answer: " + str(ans))
            rtn = "{\"error\":false,\"string\":" + str(x) + "*" + str(y) + "=" + str(ans) + ",\"answer\":" + str(ans) +"}"
            return (rtn, 204, headers)
        elif request_json and 'x' and 'y' in request_json:
            x_str = request.args.get('x')
            y_str = request.args.get('y')

            if not (pattern.match(x_str)):
                rtn = "{\"error\":true,\"string\":" + "NaN" + "*" + y_str + "=" + "NaN" + ",\"answer\":" + "NaN" +"}"
                return (rtn, 204, headers)
            if not (pattern.match(y_str)):
                rtn = "{\"error\":true,\"string\":" + x_str + "*" + "NaN" + "=" + "NaN" + ",\"answer\":" + "NaN" +"}"
                return (rtn, 204, headers)

            print("JSON: ", request_json)
            x = float(request_json['x'])
            y = float(request_json['y'])
            print("JSON X: ", str(x)) 
            print("JSON Y: ", str(y))
            ans = x * y
            print("JSON Answer 2: " + str(ans))
            rtn = "{\"error\":false,\"string\":" + str(x) + "*" + str(y) + "=" + str(ans) + ",\"answer\":" + str(ans) +"}"
            return (rtn, 204, headers)
        else:
            return f"Please pass 2 variables x and y as http"

    elif request.method == 'PUT':
        return abort(403)
    else:
        return abort(405)

I seem to be aware of the theory of CORS but my practice isn't yet strong enough and GCP seemingly works with flask-cors differently.


Solution

  • First, don't use Flask. Each function is intended to be a single action and Cloud Functions is already responsible of request routing and the framework handling.

    As for the CORS issue there are examples available in the Cloud Functions documentation on how to handle CORS.


    In the updated code you're denying the PUT requests and returning the CORS headers on GET method instead of in OPTIONS method. This is not what the documentation linked above does, please check it carefully. The schema of the Cloud Function should be like:

    def the_function(request):
        # Return CORS headers on OPTIONS request.
        if request.method == 'OPTIONS':
            headers = {
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Methods': 'GET',
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Max-Age': '3600'
            }
            return ('', 204, headers)
        
        # If the request is GET then handle it normally
        if request.method == 'GET':
            x = request.args('x')
            y = request.args('x')
            result = int(x) * int(y)
            headers = {
                'Access-Control-Allow-Origin': '*'
            }
            return (result, 200, headers)
         else:
            # If the request is not GET or OPTIONS, deny.
            return '', 400
    

    I understand that you only want to allow GET requests to the function. However, the OPTIONS HTTP method must be allowed for CORS to work properly. When a webpage requests content to another domain the browser might send a preflight request to the target domain (Cloud Function in this case) asking whether the actual GET/POST/etc.. request will be allowed. In order to accomplish this an OPTIONS request is sent to the server and the server must respond acknowledging the allowed HTTP methods. Therefore, for the function to work as expected it must:

    1. Return the Access-Control-Allow-XXX headers on OPTIONS method.
    2. Accept whatever other method returns the actual response, GET in this case.