Jak włączyć CORS w kolbie

89

Próbuję wysłać żądanie cross-origin za pomocą jquery, ale jest ono odrzucane wraz z komunikatem

XMLHttpRequest nie może załadować http: // ... Żądany zasób nie zawiera nagłówka „Access-Control-Allow-Origin”. Dlatego Origin ... nie ma dostępu.

Używam flask, heroku i jquery

kod klienta wygląda następująco:

$(document).ready(function() {
    $('#submit_contact').click(function(e){
        e.preventDefault();
        $.ajax({
            type: 'POST',
            url: 'http://...',
            // data: [
            //      { name: "name", value: $('name').val()},
            //      { name: "email", value: $('email').val() },
            //      { name: "phone", value: $('phone').val()},
            //      { name: "description", value: $('desc').val()}
            //
            // ],
            data:"name=3&email=3&phone=3&description=3",
            crossDomain:true,
            success: function(msg) {
                alert(msg);
            }
        });
    }); 
});

po stronie heroku używam kolby i tak jest

from flask import Flask,request
from flask.ext.mandrill import Mandrill
try:
    from flask.ext.cors import CORS  # The typical way to import flask-cors
except ImportError:
    # Path hack allows examples to be run without installation.
    import os
    parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    os.sys.path.insert(0, parentdir)

    from flask.ext.cors import CORS
app = Flask(__name__)

app.config['MANDRILL_API_KEY'] = '...'
app.config['MANDRILL_DEFAULT_FROM']= '...'
app.config['QOLD_SUPPORT_EMAIL']='...'
app.config['CORS_HEADERS'] = 'Content-Type'

mandrill = Mandrill(app)
cors = CORS(app)

@app.route('/email/',methods=['POST'])
def hello_world():
    name=request.form['name']
    email=request.form['email']
    phone=request.form['phone']
    description=request.form['description']

    mandrill.send_email(
        from_email=email,
        from_name=name,
        to=[{'email': app.config['QOLD_SUPPORT_EMAIL']}],
        text="Phone="+phone+"\n\n"+description
    )

    return '200 OK'

if __name__ == '__main__':
    app.run()
Lopes
źródło

Odpowiedzi:

165

Oto, co zadziałało w moim przypadku, kiedy wdrożyłem w Heroku.

http://flask-cors.readthedocs.org/en/latest/
Zainstaluj flask-cors, uruchamiając - pip install -U flask-cors

from flask import Flask
from flask_cors import CORS, cross_origin
app = Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'

@app.route("/")
@cross_origin()
def helloWorld():
  return "Hello, cross-origin-world!"
Daniel Rasmuson
źródło
31
Plus 1 za Hello Cross Origin World!
Simon Nicholls,
było to jedyne rozwiązanie, które na mnie działa. Dzięki!
psc37
1
Jesteś zbawcą życia! Działał jak urok.
Rohit Swami
Cześć! Czy możesz mi pomóc dowiedzieć się, co się dzieje w moim przypadku? Napisałem proste API w Pythonie / Flasku, nawet bez widoku. Doszedłem do niego curlpoleceniami. Teraz napisałem krótką stronę html i próbuję wysłać żądanie metodą JS fetch () do mojego API, które w oparciu o Heroku i mam błąd CORS. Po zastosowaniu twojego kodu mój terminal zaczął odpowiadać kodem HTML (usługa HTTP / 1.1 503 niedostępna) zamiast JSON. Co może być tutaj błędem? Dziękuję Ci!!
Nikita Basharkin
5
Zanim ktokolwiek skopiuje ten kod do swojej aplikacji, zapoznaj się z dokumentacją, ponieważ potrzebne są tylko niektóre z tych wierszy.
rovyko
45

OK, nie sądzę, aby oficjalny fragment wspomniany przez galuszkak był używany wszędzie, powinniśmy obawiać się, że podczas obsługi może zostać uruchomiony jakiś błąd, taki jak hello_worldfunction. Niezależnie od tego, czy odpowiedź jest poprawna, czy niepoprawna, Access-Control-Allow-Originpowinniśmy zwrócić uwagę na nagłówek. Tak więc sprawa jest bardzo prosta, tak jak poniżej:

@blueprint.after_request # blueprint can also be app~~
def after_request(response):
    header = response.headers
    header['Access-Control-Allow-Origin'] = '*'
    return response

To wszystko ~~

zhangqy
źródło
Pomogło mi to również w przypadku małego projektu z podstawowymi operacjami CRUD. Nie potrzeba niczego nadzwyczajnego, po prostu
pomiń
34

Właśnie stanąłem przed tym samym problemem i doszedłem do wniosku, że inne odpowiedzi są nieco bardziej skomplikowane niż powinny, więc oto moje podejście dla tych, którzy nie chcą polegać na większej liczbie bibliotek lub dekoratorów:

Żądanie CORS w rzeczywistości składa się z dwóch żądań HTTP. Żądanie inspekcji wstępnej, a następnie rzeczywiste żądanie, które jest wysyłane tylko wtedy, gdy inspekcja wstępna zakończy się pomyślnie.

Żądanie inspekcji wstępnej

Przed faktycznym POSTżądaniem międzydomenowym przeglądarka wyśle OPTIONSżądanie. Ta odpowiedź nie powinna zwracać żadnej treści, a jedynie pewne uspokajające nagłówki informujące przeglądarkę, że wykonanie tego żądania między domenami jest w porządku i nie jest to część ataku typu cross-site scripting.

Napisałem funkcję w Pythonie, aby zbudować tę odpowiedź przy użyciu make_responsefunkcji z flaskmodułu.

def _build_cors_prelight_response():
    response = make_response()
    response.headers.add("Access-Control-Allow-Origin", "*")
    response.headers.add("Access-Control-Allow-Headers", "*")
    response.headers.add("Access-Control-Allow-Methods", "*")
    return response

Ta odpowiedź jest odpowiedzią wieloznaczną, która działa dla wszystkich żądań. Jeśli chcesz uzyskać dodatkowe zabezpieczenia zapewniane przez CORS, musisz podać białą listę źródeł, nagłówków i metod.

Ta odpowiedź przekona Twoją przeglądarkę (Chrome) do kontynuowania i wykonania właściwego żądania.

Rzeczywiste żądanie

Podczas obsługi rzeczywistego żądania należy dodać jeden nagłówek CORS - w przeciwnym razie przeglądarka nie zwróci odpowiedzi na wywołujący kod JavaScript. Zamiast tego żądanie zakończy się niepowodzeniem po stronie klienta. Przykład z jsonify

response = jsonify({"order_id": 123, "status": "shipped"}
response.headers.add("Access-Control-Allow-Origin", "*")
return response

Napisałem również funkcję do tego.

def _corsify_actual_response(response):
    response.headers.add("Access-Control-Allow-Origin", "*")
    return response

umożliwiając zwrot jednej linijki.

Kod końcowy

from flask import Flask, request, jsonify, make_response
from models import OrderModel

flask_app = Flask(__name__)

@flask_app.route("/api/orders", methods=["POST", "OPTIONS"])
def api_create_order():
    if request.method == "OPTIONS": # CORS preflight
        return _build_cors_prelight_response()
    elif request.method == "POST": # The actual request following the preflight
        order = OrderModel.create(...) # Whatever.
        return _corsify_actual_response(jsonify(order.to_dict()))
    else
        raise RuntimeError("Weird - don't know how to handle method {}".format(request.method))

def _build_cors_prelight_response():
    response = make_response()
    response.headers.add("Access-Control-Allow-Origin", "*")
    response.headers.add('Access-Control-Allow-Headers', "*")
    response.headers.add('Access-Control-Allow-Methods', "*")
    return response

def _corsify_actual_response(response):
    response.headers.add("Access-Control-Allow-Origin", "*")
    return response
Niels B.
źródło
Wielkie dzięki @Niels B. zaoszczędziłeś mi czas. Dodałem wcześniej konfigurację cors, ale nie ustawiłem jej poprawnie.
Günay Gültekin
1
To zdecydowanie najlepsza odpowiedź na ten problem z CORS w Flasku. Działał jak urok! Dzięki @ Niels
Chandra Kanth
Dziękuję za bardzo szczegółowe wyjaśnienie !! To było bardzo pomocne!
jones-chris
Używaj wielu rozwiązań, w tym CORS i twojego, ale wszystkie z nich nie działają dla aws (postępuj zgodnie z tym przykładem - aws.amazon.com/getting-started/projects/… ), czy ktoś wie, co się dzieje?
StereoMatching
To rozwiązanie jest naprawdę proste, ale eleganckie! Dzięki, naprawdę zaoszczędziłeś mi czasu.
Gerry
19

Jeśli chcesz włączyć CORS dla wszystkich tras, następnie wystarczy zainstalować flask_cors rozszerzenie ( pip3 install -U flask_cors) i owinąć apptak: CORS(app).

To wystarczy, aby to zrobić (przetestowałem to z POSTprośbą o przesłanie obrazu i zadziałało):

from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # This will enable CORS for all routes

Ważna uwaga: jeśli w Twojej trasie jest błąd, powiedzmy, że próbujesz wydrukować zmienną, która nie istnieje, otrzymasz komunikat związany z błędem CORS, który w rzeczywistości nie ma nic wspólnego z CORS.

Billal Begueradj
źródło
1
Wielkie dzięki! To proste i ogólne rozwiązanie pozwoliło mi już wywołać mój interfejs API z mojego kodu internetowego React bez bloku CORS.
Sebastian Diaz
4

Wypróbuj następujące dekoratory:

@app.route('/email/',methods=['POST', 'OPTIONS']) #Added 'Options'
@crossdomain(origin='*')                          #Added
def hello_world():
    name=request.form['name']
    email=request.form['email']
    phone=request.form['phone']
    description=request.form['description']

    mandrill.send_email(
        from_email=email,
        from_name=name,
        to=[{'email': app.config['QOLD_SUPPORT_EMAIL']}],
        text="Phone="+phone+"\n\n"+description
    )

    return '200 OK'

if __name__ == '__main__':
    app.run()

Ten dekorator zostałby utworzony w następujący sposób:

from datetime import timedelta
from flask import make_response, request, current_app
from functools import update_wrapper


def crossdomain(origin=None, methods=None, headers=None,
                max_age=21600, attach_to_all=True,
                automatic_options=True):

    if methods is not None:
        methods = ', '.join(sorted(x.upper() for x in methods))
    if headers is not None and not isinstance(headers, basestring):
        headers = ', '.join(x.upper() for x in headers)
    if not isinstance(origin, basestring):
        origin = ', '.join(origin)
    if isinstance(max_age, timedelta):
        max_age = max_age.total_seconds()

    def get_methods():
        if methods is not None:
            return methods

        options_resp = current_app.make_default_options_response()
        return options_resp.headers['allow']

    def decorator(f):
        def wrapped_function(*args, **kwargs):
            if automatic_options and request.method == 'OPTIONS':
                resp = current_app.make_default_options_response()
            else:
                resp = make_response(f(*args, **kwargs))
            if not attach_to_all and request.method != 'OPTIONS':
                return resp

            h = resp.headers

            h['Access-Control-Allow-Origin'] = origin
            h['Access-Control-Allow-Methods'] = get_methods()
            h['Access-Control-Max-Age'] = str(max_age)
            if headers is not None:
                h['Access-Control-Allow-Headers'] = headers
            return resp

        f.provide_automatic_options = False
        return update_wrapper(wrapped_function, f)
    return decorator

Możesz również sprawdzić ten pakiet Flask-CORS

Newtt
źródło
wciąż nie działa. Już tego próbowałem i korzystałem również z pakietu Flask-CORS. Myślę, że Flask-CORS jest zbudowany na tym
Lopes
2

Moje rozwiązanie to opakowanie wokół app.route:

def corsapp_route(path, origin=('127.0.0.1',), **options):
    """
    Flask app alias with cors
    :return:
    """

    def inner(func):
        def wrapper(*args, **kwargs):
            if request.method == 'OPTIONS':
                response = make_response()
                response.headers.add("Access-Control-Allow-Origin", ', '.join(origin))
                response.headers.add('Access-Control-Allow-Headers', ', '.join(origin))
                response.headers.add('Access-Control-Allow-Methods', ', '.join(origin))
                return response
            else:
                result = func(*args, **kwargs)
            if 'Access-Control-Allow-Origin' not in result.headers:
                result.headers.add("Access-Control-Allow-Origin", ', '.join(origin))
            return result

        wrapper.__name__ = func.__name__

        if 'methods' in options:
            if 'OPTIONS' in options['methods']:
                return app.route(path, **options)(wrapper)
            else:
                options['methods'].append('OPTIONS')
                return app.route(path, **options)(wrapper)

        return wrapper

    return inner

@corsapp_route('/', methods=['POST'], origin=['*'])
def hello_world():
    ...
yurzs
źródło
2

Ulepszenie rozwiązania opisanego tutaj: https://stackoverflow.com/a/52875875/10299604

With after_requestwe can obsłużyć nagłówki odpowiedzi CORS, unikając dodawania dodatkowego kodu do naszych punktów końcowych:

    ### CORS section
    @app.after_request
    def after_request_func(response):
        origin = request.headers.get('Origin')
        if request.method == 'OPTIONS':
            response = make_response()
            response.headers.add('Access-Control-Allow-Credentials', 'true')
            response.headers.add('Access-Control-Allow-Headers', 'Content-Type')
            response.headers.add('Access-Control-Allow-Headers', 'x-csrf-token')
            response.headers.add('Access-Control-Allow-Methods',
                                'GET, POST, OPTIONS, PUT, PATCH, DELETE')
            if origin:
                response.headers.add('Access-Control-Allow-Origin', origin)
        else:
            response.headers.add('Access-Control-Allow-Credentials', 'true')
            if origin:
                response.headers.add('Access-Control-Allow-Origin', origin)

        return response
    ### end CORS section
Frank Escobar
źródło
0

Wszystkie powyższe odpowiedzi działają poprawnie, ale prawdopodobnie nadal będziesz otrzymywać błąd CORS, jeśli aplikacja zgłosi błąd, którego nie obsługujesz, na przykład błąd klucza, jeśli na przykład nie wykonujesz poprawnie sprawdzania poprawności danych wejściowych. Można dodać procedurę obsługi błędów, aby przechwytywać wszystkie wystąpienia wyjątków i dodawać nagłówki odpowiedzi CORS w odpowiedzi serwera

Więc zdefiniuj procedurę obsługi błędów - errors.py:

from flask import json, make_response, jsonify
from werkzeug.exceptions import HTTPException

# define an error handling function
def init_handler(app):

    # catch every type of exception
    @app.errorhandler(Exception)
    def handle_exception(e):

        #loggit()!          

        # return json response of error
        if isinstance(e, HTTPException):
            response = e.get_response()
            # replace the body with JSON
            response.data = json.dumps({
                "code": e.code,
                "name": e.name,
                "description": e.description,
            })
        else:
            # build response
            response = make_response(jsonify({"message": 'Something went wrong'}), 500)

        # add the CORS header
        response.headers['Access-Control-Allow-Origin'] = '*'
        response.content_type = "application/json"
        return response

następnie używając odpowiedzi Billala :

from flask import Flask
from flask_cors import CORS

# import error handling file from where you have defined it
from . import errors

app = Flask(__name__)
CORS(app) # This will enable CORS for all routes
errors.init_handler(app) # initialise error handling 
Edrich
źródło
0

Jeśli nie możesz znaleźć swojego problemu, a kod powinien działać, może to oznaczać, że Twoje żądanie zbliża się do maksymalnego czasu, w którym heroku pozwala na zgłoszenie żądania. Heroku anuluje żądania, jeśli trwa to dłużej niż 30 sekund.

Źródła: https://devcenter.heroku.com/articles/request-timeout

Do
źródło
0

Rozwiązałem ten sam problem w Pythonie używając flask i tej biblioteki. flask_cors

Źródła: https://flask-cors.readthedocs.io/en/latest/

pedro Orozco
źródło
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie. - Z recenzji
Jason Aller