修改 Flask 的默认响应头实现跨域(CORS)支持
要提供一个 RESTful API ,就必须考虑 跨域请求(CORS) 问题。在 Flask 中,我们可以进行这样的简单处理:
1@main.route('/', methods=['GET'])
2def index():
3 resp = jsonify({'error':False})
4 # 跨域设置
5 resp.headers['Access-Control-Allow-Origin'] = '*'
6 return resp
当路由较多的时候,这样写未免不优雅,我们可以封装一个方法 responseto 用来代替 jsonify:
1def responseto(message=None, error=None, data=None, **kwargs):
2 """ 封装 json 响应
3 """
4 # 如果提供了 data,那么不理任何其他参数,直接响应 data
5 if not data:
6 data = kwargs
7 data['error'] = error
8 if message:
9 # 除非显示提供 error 的值,否则默认为 True
10 # 意思是提供了 message 就代表有 error
11 data['message'] = message
12 if error is None:
13 data['error'] = True
14 else:
15 # 除非显示提供 error 的值,否则默认为 False
16 # 意思是没有提供 message 就代表没有 error
17 if error is None:
18 data['error'] = False
19 if not isinstance(data, dict):
20 data = {'error':True, 'message':'data 必须是一个 dict!'}
21 resp = jsonify(data)
22 # 跨域设置
23 resp.headers['Access-Control-Allow-Origin'] = '*'
24 return resp
这样,上面的代码可以简化为:
1@main.route('/', methods=['GET'])
2def index():
3 return responseto()
要响应一个错误消息,可以简化为:
1responseto('发生了一个错误!')
但是,当我使用 PUT 方法请求时,出现了这样的错误:
1XMLHttpRequest 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.
从当时请求的内容(见下方)可以看出,请求变成了 OPTIONS 而不是 PUT 。这是由于根据 CORS 规范 (via) ,浏览器会做一次 preflight 请求,这次请求询问服务器支持哪些方法。
1OPTIONS /account/status/ HTTP/1.1
2Host: 127.0.0.1:5000
3Connection: keep-alive
4Pragma: no-cache
5Cache-Control: no-cache
6Access-Control-Request-Method: PUT
7Origin: http://localhost:5001
8User-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
9Access-Control-Request-Headers:
10Accept: */*
11Referer: http://localhost:5001/account/
12Accept-Encoding: gzip, deflate, sdch, br
13Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
因此,上面提到的使用 responseto
的方法就没有作用了。因为任何一个路由都有可能包含 preflight 请求。
我们可以通过继承 Flask 的 Response 类来实现这个需求,让 flask 回复的任何响应都带有 Access-Control-Allow-*
的 HEAD。通过设置 Flask app 的 response_class
属性可以让 Flask 使用我们自定义的子类作为响应。
1from flask import Flask, Response
2
3class MyResponse(Response):
4 pass
5
6def create_app():
7 app = Flask(__name__)
8 app.response_class = MyResponse
我们需要在 MyResponse 类中对 headers 进行一些操作。为此我们需要了解 Flask Response 的源码实现。
Flask 的 Response 是对 werkzeug.wrappers.Response 的一个简单继承。下面是 flask 中 Response 的源码实现(位于 wrappers.py 中):
1from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
2
3class Response(ResponseBase):
4 """The response object that is used by default in Flask. Works like the
5 response object from Werkzeug but is set to have an HTML mimetype by
6 default. Quite often you don't have to create this object yourself because
7 :meth:`~flask.Flask.make_response` will take care of that for you.
8
9 If you want to replace the response object used you can subclass this and
10 set :attr:`~flask.Flask.response_class` to your subclass.
11 """
12 default_mimetype = 'text/html'
没错,就是仅此而已。因此我们还需要去看 werkzeug.wrappers.Response 的源码。下面是一点点节选:
1 def __init__(self, response=None, status=None, headers=None,
2 mimetype=None, content_type=None, direct_passthrough=False):
3 if isinstance(headers, Headers):
4 self.headers = headers
5 elif not headers:
6 self.headers = Headers()
7 else:
8 self.headers = Headers(headers)
由于参数太多,在实现继承的时候,我们仅保留一个 response 参数,其余的使用 **kwargs
代替:
1from werkzeug.datastructures import Headers
2
3class MyResponse(Response):
4 def __init__(self, response=None, **kwargs):
5 kwargs['headers'] = ''
6 headers = kwargs.get('headers')
7 # 跨域控制
8 origin = ('Access-Control-Allow-Origin', '*')
9 methods = ('Access-Control-Allow-Methods', 'HEAD, OPTIONS, GET, POST, DELETE, PUT')
10 if headers:
11 headers.add(*origin)
12 headers.add(*methods)
13 else:
14 headers = Headers([origin, methods])
15 kwargs['headers'] = headers
16 return super().__init__(response, **kwargs)
使用上面的代码可以方便地实现跨域支持,根据需要调整 methods 和 origin 的值即可。
关于自定义响应类,Miguel 的这篇文章写得更详细: Customizing the Flask Response Class 。
(全文完)
- 文章ID:2615
- 原文作者:zrong
- 原文链接:https://blog.zengrong.net/post/modify-the-response-head-in-flask/
- 版权声明:本作品采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可,非商业转载请注明出处(原文作者,原文链接),商业转载请联系作者获得授权。