如何在Flask中启用CORS

223

我正在尝试使用jQuery进行跨源请求,但它被拒绝并显示如下信息:

XMLHttpRequest无法加载http://... 请求的资源上不存在'Access-Control-Allow-Origin'头部。因此不允许从源头...访问。

我正在使用Flask、Heroku和jQuery。

客户端代码如下:

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

在Heroku这一侧,我正在使用Flask,代码如下:

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()
12个回答

380

当我部署到Heroku时,以下是对我有效的方法。

http://flask-cors.readthedocs.org/en/latest/
运行以下命令安装flask-cors - 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!"

119
你好跨域世界!加一分! - Simon Nicholls
1
这是唯一对我有效的解决方案。谢谢! - psc37
2
你真是个救命恩人!运行得非常顺利。 - Rohit Swami
21
在将此代码复制到应用程序之前,请查看文档,因为只有 部分 行是必需的。请注意不要改变原来的意思。 - rovyko
7
是的,回应@rovyko的建议,在这个片段中使用了几个重叠的特性,因此请查阅文档。对于我来说,“from flask_cors import CORS”然后跟着“CORS(app)”就足够了。 - data princess
显示剩余4条评论

93

我刚遇到了同样的问题,我认为其他答案比必要的复杂,所以这里是我的方法,适用于那些不想依赖更多库或装饰器的人:

CORS请求实际上由两个HTTP请求组成。 一个preflight请求,然后是只有在preflight成功通过后才会发出的实际请求。

Preflight请求

在实际的跨域POST请求之前,浏览器将发出OPTIONS请求。该响应不应返回任何正文,而只应返回一些安心的标头,告诉浏览器可以进行此跨域请求,并且它不是某种跨站点脚本攻击的一部分。

我编写了一个Python函数来构建此响应,使用flask模块中的make_response函数。

def _build_cors_preflight_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

这个响应是通配符响应,适用于所有请求。如果你想获得CORS提供的额外安全性,你必须提供一个白名单包括来源、头部和方法。
这个响应将会说服你的(Chrome)浏览器继续执行实际请求。
实际请求
在服务实际请求时,你必须添加一个CORS头 - 否则浏览器不会将响应返回给调用JavaScript代码。相反,请求将在客户端失败。例如使用jsonify。
response = jsonify({"order_id": 123, "status": "shipped"})
response.headers.add("Access-Control-Allow-Origin", "*")
return response

我也为此编写了一个函数。
def _corsify_actual_response(response):
    response.headers.add("Access-Control-Allow-Origin", "*")
    return response

使您能够返回一个一行代码。

最终代码

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_preflight_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_preflight_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

3
这绝对是关于Flask中CORS问题最好的答案。效果非常好!感谢@Niels。 - Chandra Kanth
这个解决方案真的非常简单而优雅!谢谢,你真的节省了我的时间。 - Gerry
有没有什么理由将“prelight_response”称为“prelight”,而不是“preflight”? - Fred Zimmerman
那是个打字错误。没注意到它。 - Niels B.
1
我已经使用了这个解决方案,而且它完美地运行了。所以我的问题是:如果仅仅添加这个头信息就足够了,那么为什么 flask_cors 包会存在呢? - dsenese
显示剩余7条评论

68

好的,我认为不应该在所有地方使用galuszkak提到的官方代码片段,我们应该考虑在处理程序(例如hello_world函数)期间可能会触发某些错误的情况。无论响应是正确还是不正确,我们都应该关注Access-Control-Allow-Origin头信息。所以,很简单,就像下面的代码片段:

# define your bluprint
from flask import Blueprint
blueprint = Blueprint('blueprint', __name__)

# put this sippet ahead of all your bluprints
# blueprint can also be app~~
@blueprint.after_request 
def after_request(response):
    header = response.headers
    header['Access-Control-Allow-Origin'] = '*'
    # Other headers can be added here if needed
    return response

# write your own blueprints with business logics
@blueprint.route('/test', methods=['GET'])
def test():
    return "test success"

就这些啦~~


这也帮助我完成了一个涉及基本CRUD操作的小型项目。没有必要使用复杂的东西,只需要解决这个错误 :) - Narshe
解决方案对我来说可以,但需要添加:header ['Access-Control-Allow-Headers'] ='Content-Type' - MR_1204

50
如果您想为所有路由启用CORS,则只需安装flask_cors扩展程序(pip3 install -U flask_cors),并像这样包装appCORS(app)。这就足够了(我使用POST请求上传图像进行了测试,对我有效)。
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # This will enable CORS for all routes

重要提示:如果您的路由出现错误,例如尝试打印不存在的变量,则会收到与CORS无关的CORS错误相关消息。


2
非常感谢!这个简单通用的解决方案让我能够在我的React Web代码中调用API,不再受到CORS阻止。 - Sebastian Diaz
5
谢谢!重要提示部分为我节省了不少时间。 - Gabriel
2
谢谢!你的笔记非常宝贵。 - matisa
4
就是这样。我仍然遇到了CORS错误,但当我运行heroku logs --tail时,我看到了一个模块导入错误。然后我在requirements.txt文件中添加了Flask-Cors==3.0.10,它就可以工作了。我不需要重新启动dynos。顺便说一下,您可以运行pip freeze > requirements.txt自动获取所有模块要求。 - Alaa M.

22

我使用Flask框架和这个库解决了同样的问题。 flask_corsinit.py文件中:

#pip install flask_cors

from flask_cors import CORS

app = Flask(__name__)
CORS(app)
cors = CORS(app, resource={
    r"/*":{
        "origins":"*"
    }
})

就是这样了。

参考资料:https://flask-cors.readthedocs.io/en/latest/


虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅有链接的答案可能会失效。- 来自审查 - Jason Aller
谢谢你说出来,下次我会做得更好。 - Pedro Orozco
通过构造函数初始化时,CORS参数似乎是“resources”,而不是“resource”(根据文档)。 - amucunguzi
由于某种原因,CROS对我来说不起作用,指定的配置没有生效。 - JAD
这必须是被接受的答案。 - Nam G VU

9

改进此处描述的解决方案:https://dev59.com/ql8e5IYBdhLWcg3wlbLx#52875875

使用after_request,我们可以处理CORS响应标头,而无需向端点添加额外代码:

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

这可能会导致您的系统遭受CORS攻击。相反,请使用Access-Control-Allow-Origin:* - PaxPrz
@Pax,你能再解释一下吗? - Frank Escobar
如果响应中包含 Access-Control-Allow-Credentials: true,则不能在任何响应头中使用通配符运算符,如 Access-Control-Allow-Origin。因此,如果同时使用通配符和 allow-credentials,则浏览器会采取安全措施。 - PaxPrz
这里有另一个答案,解释得更好。 - PaxPrz
从上述解决方案中,这个选项是解决我的问题的方法。 - JAD

7

首先,您需要安装flask-cors。您可以按照以下步骤执行:

pip install flask-cors

安装完成后,您可以按照以下方式在Flask应用程序中使用:

  1. 如果需要为所有路线启用CORS:
from flask_cors import CORS

app = Flask(__name__)
CORS(app)
  1. 如果你只想为特定路由启用 CORS,可以将资源参数传递给 CORS 函数。例如,
CORS(app, resources={r"/api/*": {"origins": "*"}})

在这个例子中,该代码将仅为以“/api/”开头的路由启用 CORS,并允许来自任何来源的请求。您可以根据需要自定义资源参数。
有关更多信息,请阅读文档

5
所有以上的回答都可以正常工作,但是如果应用程序抛出一个没有被处理的错误,比如键错误,或者你没有正确地进行输入验证,那么你仍然可能会遇到CORS错误。你可以添加一个错误处理程序来捕获所有异常的实例,并在服务器响应中添加CORS响应头。
因此,定义一个错误处理程序 - 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

然后使用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 

4
尝试使用以下装饰器:
@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()

这个装饰器可以按照以下方式创建:

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

你可以查看这个包Flask-CORS

仍然不起作用。我已经尝试过了,我还使用了Flask-CORS包。我认为Flask-CORS是建立在此之上的。 - Lopes

2
我的解决方案是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():
    ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接