MartinSahlen / cloud-functions-python

Get some python in google cloud functions
203 stars 29 forks source link

CORS (Cross-origin resource sharing) not supported by cloud-function-python #48

Closed santhoshdc1590 closed 6 years ago

santhoshdc1590 commented 6 years ago

On POSTMAN the cloud function is working as expected.

Only when the cloud function URL is called from a browser I'm getting this error in the logs.

Traceback (most recent call last): File "function.py", line 29, in File "site-packages/cloudfn/http.py", line 45, in handle_http_event File "site-packages/cloudfn/http.py", line 12, in init KeyError: 'body'

This is my function.py file content

from FTUL import mainP
from cloudfn.google_account import get_credentials
from cloudfn.http import handle_http_event, Response
from google.cloud import bigquery
import json

def handle_http(req):

    biquery_client = bigquery.Client(credentials=get_credentials())

    console.log(req)
    console.log(req.method)
    console.log(req.body)

    if req.method == 'OPTIONS':
        return Response(
            status_code=200,
        )

    if req.method == 'POST':

        rBody = json.loads(req.body)
        im = rBody['image']
        rID = rBody['refID']
        rTable = rBody['refTable']
        uAlgo = 0
        empID = rBody['empID']
        val = mainP(im,rID,rTable,uAlgo,empID)

        return Response(
            status_code=200,
        )

handle_http_event(handle_http)

On the browser logs the error is being shown as a 500 error in the 'OPTIONS'

Browser is sending a req.method as OPTIONS but we have no body present in this req but function is expecting a body inside req.method.

What's the work around for this?

MartinSahlen commented 6 years ago

This seems to be a bug - does the same thing happen in GET requests?

santhoshdc1590 commented 6 years ago

No this is not a BUG, its CORS.

Making Cross-Domain Requests with CORS

First OPTIONS is checked by the browser then the POST request is handled.

screen shot 2018-04-11 at 6 51 54 pm

But the file http.py, seems to have initialised the request in a pre-defined format, which expects body. Which is never going to happen. I need to make the request stop the search for the body by removing it from constructer I might screw up the entire code. I'm thinking about commenting out the self.body = raw_json['body']

Here's the http.py file

import json
import sys

import six
from six.moves.urllib_parse import urlparse

class Request:
    def __init__(self, raw_json):
        self.headers = raw_json['headers']
        self.method = raw_json['method']
        self.body = raw_json['body']
        self.url = raw_json['url']
        self.ip = raw_json['remote_addr']

        components = urlparse(self.url)
        self.path = components.path
        self.host = components.hostname
        self.scheme = components.scheme
        self.query = components.query
        self.port = components.port
        self.fragment = components.fragment
        self.params = components.params
        self.netloc = components.netloc

class Response:
    def __init__(self, headers=None, body='', status_code=200):
        self.headers = {} if headers is None else headers
        if isinstance(body, (six.text_type, six.binary_type, dict, list)):
            self.body = body
        else:
            self.body = str(body)
        self.status_code = status_code

    def _json_string(self):
        return json.dumps({
            'body': self.body,
            'status_code': self.status_code,
            'headers': self.headers,
        })

def handle_http_event(handle_fn):
    req = Request(json.loads(sys.stdin.read()))
    res = handle_fn(req)
    if isinstance(res, Response):
        sys.stdout.write(res._json_string())
    else:
        sys.stdout.write(Response()._json_string())
santhoshdc1590 commented 6 years ago

@MartinSahlen Can you help with this? I seem to be stuck, commenting the self.body = raw_json['body'] doesn't seem to be working

santhoshdc1590 commented 6 years ago

@MartinSahlen Will this work? A Python package for dealing with HTTP requests and same-origin policie Can you help out here?

santhoshdc1590 commented 6 years ago

@MartinSahlen As you have written the main code here for the cloud-function-python and how it actually works. I feel you can fix this easily than me. Can you please fix it?

santhoshdc1590 commented 6 years ago

Tried to solve the CORS issue using django but I'm having python environments issue according to this issue I raised ImportError: Module "django.middleware.common" does not define a "CorsMiddleware" attribute/class

So tried versions of django-cors-headers, djangorestframework and django that are tested and working on python3.5. I still get ImportError. Looks like flask is the way to go

santhoshdc1590 commented 6 years ago

@MartinSahlen Looks like only you can help me. Please can you?

MartinSahlen commented 6 years ago

Hey! Unfortunately I do not have a lot of free time on my hands to investigate all of these issues, and at the moment the status of the library is very alpha, ie it “works on my machine”. I really am sorry to hear about all the issues you’re having though. I will try to carve out some time in the next week to help you out. In the meantime, you’re doing a (unfortunately for you) lot of good investigation so it’s easier to know where to start looking.

santhoshdc1590 commented 6 years ago

@MartinSahlen I like to be optimistic. It's really nice that you have carved out so much of time to implement this cloud-function-python, Thank You for that. It's not unfortunate for me, I'm learning new things. Plus this is required at work for me, in fact this is currently my main work. 😅 I get that you don't have enough time to go through entire things at once to solve it.

Functionality I want to run on the cloud-function is perfect.

I want the function to be called from the browser. CORS is the only way to get the cloud-function when the browser sends out a request.

I am doing my best to figure out how you actually done this, to correct things on my own.

I DO NEED YOUR HELP BADLY, MORE THAN YOU KNOW😅

santhoshdc1590 commented 6 years ago

@MartinSahlen Non of the version of the django-cors-headers are getting installed on the cloud. I'm getting the same error ImportError: No module named 'corsheaders'.

Failed to execute script function Traceback (most recent call last): File "function.py", line 2, in File "/app/cloudfn/pip-cache-3.5/lib/python3.5/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module File "mysite/wsgi.py", line 16, in File "site-packages/django/core/wsgi.py", line 13, in get_wsgi_application File "site-packages/django/init.py", line 27, in setup File "site-packages/django/apps/registry.py", line 85, in populate File "site-packages/django/apps/config.py", line 90, in create File "importlib/init.py", line 126, in import_module ImportError: No module named 'corsheaders'

I had a silly guess that memory of 256MB cloud-function was not enough so I used 2GB entire. But still the same error.

santhoshdc1590 commented 6 years ago

@MartinSahlen I had this error while importing the scipy

[13] Failed to execute script function Traceback (most recent call last): File "function.py", line 7, in File "/app/cloudfn/pip-cache-3.5/lib/python3.5/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module File "FTUL.py", line 4, in File "/app/cloudfn/pip-cache-3.5/lib/python3.5/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module File "site-packages/scipy/spatial/init.py", line 95, in File "/app/cloudfn/pip-cache-3.5/lib/python3.5/site-packages/PyInstaller/loader/pyimod03_importers.py", line 714, in load_module File "messagestream.pxd", line 5, in init scipy.spatial.qhull ImportError: No module named 'scipy._lib.messagestream'

I had to add

'--hidden-import', 'scipy._lib.messagestream', to cli.py line number 95 to support scipy

to fix it

I guess I just have to a similar import for this, before building cloud function python

Failed to execute script function Traceback (most recent call last): File "function.py", line 2, in File "/app/cloudfn/pip-cache-3.5/lib/python3.5/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module File "mysite/wsgi.py", line 16, in File "site-packages/django/core/wsgi.py", line 13, in get_wsgi_application File "site-packages/django/init.py", line 27, in setup File "site-packages/django/apps/registry.py", line 85, in populate File "site-packages/django/apps/config.py", line 90, in create File "importlib/init.py", line 126, in import_module ImportError: No module named 'corsheaders'

santhoshdc1590 commented 6 years ago

I added

'--hidden-import', 'corsheaders.middleware', to cli.py file

which did take care of the ImportError: No module named 'corsheaders'

Now I'm getting this error

Internal Server Error: / Traceback (most recent call last): File "site-packages/django/core/handlers/exception.py", line 39, in inner File "site-packages/django/core/handlers/base.py", line 172, in _get_response File "site-packages/django/urls/resolvers.py", line 267, in resolve File "site-packages/django/utils/functional.py", line 35, in get File "site-packages/django/urls/resolvers.py", line 310, in url_patterns File "site-packages/django/utils/functional.py", line 35, in get File "site-packages/django/urls/resolvers.py", line 303, in urlconf_module File "importlib/init.py", line 126, in import_module File "", line 986, in _gcd_import File "", line 969, in _find_and_load File "", line 958, in _find_and_load_unlocked File "", line 673, in _load_unlocked File "/app/cloudfn/pip-cache-3.5/lib/python3.5/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module File "mysite/urls.py", line 17, in File "/app/cloudfn/pip-cache-3.5/lib/python3.5/site-packages/PyInstaller/loader/pyimod03_importers.py", line 631, in exec_module File "mysite/myapp/urls.py", line 2, in ImportError: No module named 'mysite.myapp.views'

I tried importing the module in all which ways mentioned but same ImportError

I guess the entire structure of django is not correct

santhoshdc1590 commented 6 years ago

@MartinSahlen

This is my urls.py file

from django.conf.urls import url
from .views import runAlgo

urlpatterns = [
    url(r'^run', runAlgo),
    ]

This is my views.py file

from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
from FTUL import mainP

@api_view(http_method_names=['POST','OPTIONS'])
@permission_classes((AllowAny, ),)
def runAlgo(request):

    if request.method== 'OPTIONS':
        return Response(status=status.HTTP_200_OK)

    if request.method == 'POST':

        ### Getting image URL
        image = request.POST.getlist('image')
        image = ''.join(str(e) for e in image)

        #### Getting RefID
        refID = request.POST.getlist('refID')

        #### Getting RefTable
        refTable = request.POST.getlist('refTable')

        #### Getting RefID
        empID = request.POST.getlist('empID')
        empID = ''.join(str(e) for e in empID)

        uAlgo = 0

        typeID = request.POST.getlist('typeID')

        appID = request.POST.getlist('appID')

        version = request.POST.getlist('version')

        #image URL, refID, refTable, Algo(cuurently selected 2), empID, typeID, appID, version 
        val = mainP(image,refID,refTable,2,empID,typeID,appID,version)

        return Response(
                status=status.HTTP_201_CREATED)

This is my POSTMAN result

screen shot 2018-04-16 at 7 39 13 pm

This is the logs on the google cloud

screen shot 2018-04-16 at 7 42 33 pm

I have ran out of options I really need this thing working.

kishore7403 commented 1 year ago

If you want to Allow all HTTP methods request to your google cloud function from the front end and face an issue with CORS. The Following template must work. It worked for me.

#import required packages
import json

def function_name(request):
    if request.method == 'OPTIONS':
        headers = {
            'Access-Control-Allow-Origin': '*',  # Allow your function to be called from any domain
            'Access-Control-Allow-Methods': '*',  # Allow all HTTP methods
            'Access-Control-Allow-Headers': 'Content-Type', 
            'Access-Control-Max-Age': '3600',
        }
        return ('', 204, headers)

    # Set CORS headers for main requests
    headers = {
        'Access-Control-Allow-Origin': '*',
    }

    # Your code logic goes here

    # Return your response
    return (json.dumps({'status': 'success'}), 200, headers)