本站文章除注明转载外,均为本站原创或者翻译。
- 本站文章欢迎各种形式的转载,但请18岁以上的转载者注明文章出处,尊重我的劳动,也尊重你的智商;
本站部分原创和翻译文章提供markdown格式源码,欢迎使用 文章源码
进行转载;
本博客采用WPCMD 维护;
- 本文标题:修改 Flask 的默认响应头实现跨域(CORS)支持
本文链接: http://zengrong.net/post/2615.htm
要提供一个 RESTful API ,就必须考虑 跨域请求(CORS)
问题。在 Flask 中,我们可以简单得进行这样的处理:
@main.route('/', methods=['GET'])
def index():
resp = jsonify({'error':False})
# 跨域设置
resp.headers['Access-Control-Allow-Origin'] = '*'
return resp
当路由较多的时候,这样写未免太不优雅,我们可以封装一个方法 responseto 用来代替 jsonify:
def responseto(message=None, error=None, data=None, **kwargs):
""" 封装 json 响应
"""
# 如果提供了 data,那么不理任何其他参数,直接响应 data
if not data:
data = kwargs
data['error'] = error
if message:
# 除非显示提供 error 的值,否则默认为 True
# 意思是提供了 message 就代表有 error
data['message'] = message
if error is None:
data['error'] = True
else:
# 除非显示提供 error 的值,否则默认为 False
# 意思是没有提供 message 就代表没有 error
if error is None:
data['error'] = False
if not isinstance(data, dict):
data = {'error':True, 'message':'data 必须是一个 dict!'}
resp = jsonify(data)
# 跨域设置
resp.headers['Access-Control-Allow-Origin'] = '*'
return resp
这样,上面的代码可以简化为:
@main.route('/', methods=['GET'])
def index():
return responseto()
要响应一个错误消息,可以简化为:
responseto('发生了一个错误!')
但是,当我是因为 PUT 方法请求时,出现了这样的错误:
XMLHttpRequest cannot load http://127.0.0.1:5000/account/status/. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:5001' is therefore not allowed access.
从当时请求的内容(见下方)可以看出,请求变成了 OPTION 而不是 PUT 。这是由于根据 CORS 规范
( via
) ,浏览器会做一次 preflight 请求,这次请求询问服务器支持哪些方法。
OPTIONS /account/status/ HTTP/1.1
Host: 127.0.0.1:5000
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: PUT
Origin: http://localhost:5001
User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Mobile Safari/537.36
Access-Control-Request-Headers:
Accept: */*
Referer: http://localhost:5001/account/
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
因此,上面提到的使用 responseto
的方法就没有作用了。因为任何一个路由都有可能包含 preflight 请求。
我们可以通过自定义的 Response 类(作为 Flask Response 的子类)来实现这个需求,让 flask 回复的任何响应都带有 Access-Control-Allow-*
的 HEAD。通过设置 Flask app 的 response_class
属性可以做到这点。
from flask import Flask, Response
class MyResponse(Response):
pass
def create_app():
app = Flask(__name__)
app.response_class = MyResponse
我们需要在 MyResponse 类中对 headers 进行一些操作。为此我们需要了解 Flask Response 的源码实现。
Flask 的 Response 是对 werkzeug.wrappers.Response
的一个简单继承。下面是 flask 中 Response 的源码实现(位于 wrappers.py 中):
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
class Response(ResponseBase):
"""The response object that is used by default in Flask. Works like the
response object from Werkzeug but is set to have an HTML mimetype by
default. Quite often you don't have to create this object yourself because
:meth:`~flask.Flask.make_response` will take care of that for you.
If you want to replace the response object used you can subclass this and
set :attr:`~flask.Flask.response_class` to your subclass.
"""
default_mimetype = 'text/html'
没错,就是仅此而已。因此我们还需要去看 werkzeug.wrappers.Response
的源码。下面是一点点节选:
def __init__(self, respOnse=None, status=None, headers=None,
mimetype=None, content_type=None, direct_passthrough=False):
if isinstance(headers, Headers):
self.headers = headers
elif not headers:
self.headers = Headers()
else:
self.headers = Headers(headers)
了解了 __init__
的结构,我们可以这样做继承。由于参数太多,我们仅保留一个 response 参数,其余的使用 **kwargs
代替:
from werkzeug.datastructures import Headers
class MyResponse(Response):
def __init__(self, respOnse=None, **kwargs):
kwargs['headers'] = ''
headers = kwargs.get('headers')
# 跨域控制
origin = ('Access-Control-Allow-Origin', '*')
methods = ('Access-Control-Allow-Methods', 'HEAD, OPTIONS, GET, POST, DELETE, PUT')
if headers:
headers.add(*origin)
headers.add(*methods)
else:
headers = Headers([origin, methods])
kwargs['headers'] = headers
return super().__init__(response, **kwargs)
使用上面的代码可以方便地实现跨域支持,根据需要调整 methods 和 origin 的值即可。
关于自定义响应类,Miguel 的这篇文章写得更详细: Customizing the Flask Response Class
。
(全文完)