AberSheeran
Aber Sheeran
I know nothing except the fact of my ignorance.

解决跨域请求

起笔自
所属文集: Django-Simple-Api
共计 3415 个字符
落笔于

作为一个Api服务器,提供跨域支持是最基本的需求之一。

这里可能有人会说,已经有django-cors-headers这么好的实现了,不需要自己再写一个。

但我认为能用几行代码解决的事情,不需要用一个新的库,只会徒增使用者的负担。

跨域

首先我们要明白,要让一个请求被允许跨域,在响应预检方法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类型,它必须拥有HOSTSCOOKIESHEADERS三个值。分别是

  • 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问题

如果你觉得本文值得,不妨赏杯茶
解析非POST请求
没有下一篇