跨域
首先我们要明白,要让一个请求被允许跨域,在响应预检方法OPTIONS时需要返回哪些头部内容。那么查一下MDN。
可以看到MDN给出了一些例子,其中较为重要的。
-
Access-Control-Allow-Origin: 允许访问本站的域
-
Access-Control-Allow-Methods: 允许的请求方法
-
Access-Control-Allow-Credentials: 能否携带Cookies,当这个被设为
true
时,Access-Control-Allow-Origin将不能被设为*
-
Access-Control-Allow-Headers:请求时允许携带的头,一般这个会用在一些非Cookies认证身份的情况
那么可以写代码了。
编写ApiView类
在之前的一篇Django解析非POST请求,我们已经为Api解析好了数据,所以这一篇我们只需要考虑跨域和CSRF保护就好。
从Django最基本的View
类继承,分发请求到具体的方法这个事情,Django已经做完了,所以我们只要编写一下OPTIONS
方法,解决一下跨域就行。先写一个基本的处理
class SimpleApiView(View):
def options(self, request, *args, **kwargs):
response = HttpResponse()
response['Content-Length'] = '0'
return response
关于OPTIONS
应该是返回200还是204,StackOverflow有关于此的讨论。在这里,我们延续Django的源码的选择——返回200。
然后我们需要考虑从settings
中读取CORS_SETTINGS
。约定CORS_SETTINGS
是一个dict类型,它必须拥有HOSTS
,COOKIES
和HEADERS
三个值。分别是
-
HOSTS
:允许访问本站的域名列表或者'*'
(代表允许任意域),默认为'*'
。 -
COOKIES
:跨域请求是否允许携带cookies,默认为False。当这个值为True且HOSTS
为'*'
时,直接抛出一个异常。
class SimpleApiView(View):
def set_cors_header(self, request: HttpRequest, response: HttpResponse):
response['Access-Control-Allow-Methods'] = ', '.join(self._allowed_methods())
_headers = request.META.get('HTTP_ACCESS_CONTROL_REQUEST_HEADERS', "")
response['Access-Control-Allow-Headers'] = _headers
_hosts = settings.CORS_SETTINGS.get('HOSTS', "*")
response['Access-Control-Allow-Origin'] = ", ".join(_hosts)
if settings.CORS_SETTINGS.get('COOKIES', False):
if _hosts == "*":
response['Access-Control-Allow-Origin'] = request.META.get("HTTP_ORIGIN")
response['Access-Control-Allow-Credentials'] = 'true'
return response
def dispatch(self, request, *args, **kwargs):
return self.set_cors_header(request, super().dispatch(request, *args, **kwargs))
到这里,跨域工作就已经完成了。但我们再增加一个功能——指定某些方法不能跨域。
给类增加一个属性refused_cors_methods
,使用Python提供的集合求差集的方法,我们可以完美的完成这一点。
class SimpleApiView(View):
# 不允许跨域的方法列表
# 方法名称均为小写, 例如 ['post', 'delete']
refused_cors_methods = []
def set_cors_header(self, request: HttpRequest, response: HttpResponse):
response['Access-Control-Allow-Methods'] = ', '.join(
set(self._allowed_methods()) - set(self.refused_cors_methods)
)
_headers = request.META.get('HTTP_ACCESS_CONTROL_REQUEST_HEADERS', "")
response['Access-Control-Allow-Headers'] = _headers
_hosts = settings.CORS_SETTINGS.get('hosts', "*")
response['Access-Control-Allow-Origin'] = ", ".join(_hosts)
if settings.CORS_SETTINGS.get('cookie', False):
if _hosts == "*":
response['Access-Control-Allow-Origin'] = request.META.get("HTTP_ORIGIN")
response['Access-Control-Allow-Credentials'] = 'true'
return response
def dispatch(self, request, *args, **kwargs):
return self.set_cors_header(request, super().dispatch(request, *args, **kwargs))
其他东西
一般来说,跨域还需要考虑到的是Django的CSRF保护机制,但这已经不属于本系列文章的内容,所以独立一篇出来——Django跨域的CSRF问题