Cómo habilitar CORS en matraz

89

Estoy tratando de hacer una solicitud de origen cruzado usando jquery pero sigue siendo rechazada con el mensaje

XMLHttpRequest no puede cargar http: // ... No hay un encabezado 'Access-Control-Allow-Origin' presente en el recurso solicitado. Origen ... por tanto, no se permite el acceso.

Estoy usando flask, heroku y jquery.

el código del cliente se ve así:

$(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);
            }
        });
    }); 
});

en el lado de heroku estoy usando un matraz y es así

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
fuente

Respuestas:

165

Esto es lo que funcionó para mí cuando me desplegué en Heroku.

http://flask-cors.readthedocs.org/en/latest/
Instale flask-cors ejecutando - 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
fuente
31
¡Más 1 para hola mundo de origen cruzado!
Simon Nicholls
fue la única solución que me funcionó. ¡Gracias!
psc37
1
¡Eres un salvador de vidas! Trabajado como un encanto.
Rohit Swami
¡Hola! ¿Podrías ayudarme a averiguar qué sucede en mi caso? Escribí una API simple usando Python / Flask, sin siquiera una vista para ella. Lo alcancé por curlórdenes. Ahora escribí una página html corta e intenté hacer una solicitud con el método JS fetch () a mi API que, según Heroku y tengo el error CORS. Después de aplicar su código, mi terminal comenzó a responderme con código HTML (HTTP / 1.1 503 Servicio no disponible) en lugar del JSON. ¿Qué podría ser un error aquí? ¡¡Gracias!!
Nikita Basharkin
5
Antes de que alguien copie este código en su aplicación, consulte la documentación porque solo se necesitan algunas de estas líneas.
rovyko
45

De acuerdo, no creo que el fragmento oficial mencionado por galuszkak deba usarse en todas partes, deberíamos preocuparnos por el caso de que se pueda desencadenar algún error durante el controlador, como la hello_worldfunción. Ya sea que la respuesta sea correcta o incorrecta, el Access-Control-Allow-Originencabezado es lo que debemos preocuparnos. Entonces, la cosa es muy simple, como a continuación:

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

Eso es todo ~~

zhangqy
fuente
Esto también me ayudó para un pequeño proyecto con operaciones CRUD básicas. No necesita nada elegante, simplemente omita el error :)
Narshe
34

Acabo de enfrentar el mismo problema y llegué a creer que las otras respuestas son un poco más complicadas de lo necesario, así que este es mi enfoque para aquellos que no quieren depender de más bibliotecas o decoradores:

Una solicitud CORS en realidad consta de dos solicitudes HTTP. Una solicitud de verificación previa y luego una solicitud real que solo se realiza si la verificación previa pasa con éxito.

La solicitud de verificación previa

Antes de la POSTsolicitud real entre dominios , el navegador emitirá una OPTIONSsolicitud. Esta respuesta no debe devolver ningún cuerpo, sino solo algunos encabezados tranquilizadores que le dicen al navegador que está bien hacer esta solicitud entre dominios y que no es parte de ningún ataque de scripts entre sitios.

Escribí una función de Python para construir esta respuesta usando la make_responsefunción del flaskmódulo.

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

Esta respuesta es un comodín que funciona para todas las solicitudes. Si desea la seguridad adicional obtenida por CORS, debe proporcionar una lista blanca de orígenes, encabezados y métodos.

Esta respuesta convencerá a su navegador (Chrome) de seguir adelante y realizar la solicitud real.

La solicitud real

Al atender la solicitud real, debe agregar un encabezado CORS; de lo contrario, el navegador no devolverá la respuesta al código JavaScript de invocación. En cambio, la solicitud fallará en el lado del cliente. Ejemplo con jsonify

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

También escribí una función para eso.

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

permitiéndole devolver un one-liner.

Código final

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.
fuente
Muchas gracias @Niels B., me ahorraste el tiempo. He agregado la configuración de cors antes, pero no la configuré correctamente.
Günay Gültekin
1
Esta es, con mucho, la mejor respuesta a este problema de CORS en Flask. ¡Trabajado como un encanto! Gracias @Niels
Chandra Kanth
¡Gracias por su explicación tan detallada! ¡Esto fue muy útil!
jones-chris
Utilice muchas soluciones, incluidas CORS y la suya, pero todas no funcionan para aws (siga este ejemplo: aws.amazon.com/getting-started/projects/… ), ¿alguien sabe qué está pasando?
StereoMatching
¡Esta solución es realmente simple pero elegante! Gracias, realmente me salvaste el tiempo.
Gerry
20

Si desea habilitar CORS para todas las rutas, a continuación, sólo tiene que instalar flask_cors extensión ( pip3 install -U flask_cors) y la envoltura de appla siguiente manera: CORS(app).

Eso es suficiente para hacerlo (probé esto con una POSTsolicitud para cargar una imagen, y funcionó para mí):

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

Nota importante: si hay un error en su ruta, digamos que intenta imprimir una variable que no existe, obtendrá un mensaje relacionado con el error CORS que, de hecho, no tiene nada que ver con CORS.

Billal Begueradj
fuente
1
¡Muchas gracias! Esta solución simple y general me permitió llamar a mi API desde mi código web React sin el bloque CORS.
Sebastian Diaz
1
Gracias ! La parte de la nota importante me ahorró bastante tiempo.
Gabriel
4

Prueba los siguientes decoradores:

@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()

Este decorador se crearía de la siguiente manera:

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

También puede consultar este paquete Flask-CORS

Newtt
fuente
sigue sin funcionar. Ya probé eso y también usé el paquete Flask-CORS. Creo que Flask-CORS se basa en eso
Lopes
2

Mi solución es una envoltura alrededor de 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
fuente
2

Mejorando la solución descrita aquí: https://stackoverflow.com/a/52875875/10299604

Con after_requestpodemos manejar los encabezados de respuesta CORS evitando agregar código extra a nuestros endpoints:

    ### 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
fuente
0

Todas las respuestas anteriores funcionan bien, pero probablemente obtendrá un error CORS si la aplicación arroja un error que no está manejando, como un error de clave, si no está haciendo la validación de entrada correctamente, por ejemplo. Puede agregar un controlador de errores para detectar todas las instancias de excepciones y agregar encabezados de respuesta CORS en la respuesta del servidor

Así que defina un controlador de errores: 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

luego usando la respuesta de Billal :

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
fuente
0

Si no puede encontrar su problema y su código debería funcionar, es posible que su solicitud esté llegando al máximo de tiempo que heroku le permite hacer una solicitud. Heroku cancela las solicitudes si demora más de 30 segundos.

Referencia: https://devcenter.heroku.com/articles/request-timeout

Hasta
fuente
0

Resolví este mismo problema en Python usando un matraz y con esta biblioteca. flask_cors

Referencia: https://flask-cors.readthedocs.io/en/latest/

pedro orozco
fuente
Si bien este enlace puede responder a la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden dejar de ser válidas si cambia la página enlazada. - De la crítica
Jason Aller