zoukankan      html  css  js  c++  java
  • 记一次 CORS 跨域请求出现 OPTIONS 请求的问题及解决方法

    今天前后端在联调接口的时候,发生了跨域请求资源获取不到的问题。
    首先说明下跨域问题的由来。引自HTTP 访问控制 的一段话:

    当 Web 资源请求由其它域名或端口提供的资源时,会发起跨域 HTTP 请求(cross-origin HTTP request)。
    比如,站点 http://domain-a.com 的某 HTML 页面通过 <img> 的 src 请求 http://domain-b.com/image.jpg。网络上,很多页面从其他站点加载各类资源(包括 CSS、图片、JavaScript 脚本)。
    出于安全考虑,浏览器会限制脚本中发起的跨域请求。比如,使用 XMLHttpRequest 和 Fetch 发起的 HTTP 请求必须遵循同源策略。因此,Web 应用通过 XMLHttpRequest 对象或 Fetch 仅能向同域资源发起 HTTP 请求。 
    

    既然知道了导致问题的原因,就开始解决吧。
    笔者使用的 是 Django 框架。github 上面已经有人分享了解决办法,就是 django-cors-headers。我们直接

    pip install django-cors-headers 
    

    安装一下呗。
    安装好了以后,需要我们去 settings 文件中去配置一下。常见的配置如下:

    • 先在 INSTALLED_APPS 中引入 corsheaders:
    INSTALLED_APPS = (
        ...
        'corsheaders',
        ...
    )
    
    • 接着,在 MIDDLEWARE_CLASSES 里面加入 CorsMiddleware 中间件:
    MIDDLEWARE_CLASSES = [
        ...
        'corsheaders.middleware.CorsMiddleware',  # cors
        'django.middleware.common.CommonMiddleware',
        ...
    
    • 然后,配置下一些基本参数:
    CORS_ORIGIN_ALLOW_ALL = True
    CORS_ALLOW_CREDENTIALS = True
    

    一些文章还有配置 CORS_ORIGIN_WHITELIST 参数。

    笔者也是看了别人的解决方法,之前也是实践过了。配置好这三个参数就OK了。本来也以为大工告成了。没想到,居然没解决!!!
    怎么回事?
    通过追踪请求日志,发现每次客户端请求接口的时候,都会有一个 OPTIONS 请求。

    为什么会有 OPTIONS 请求?
    原来,产生 OPTIOINS 请求的原因是:自定义 Headers 头信息导致的。为了限制接口的访问,我在 request 中间件里面加了一层过滤,通过判断 headers 中是否有约定好的字段及其对应的值(比如:key为 aaa, value为 bbb),如果有,就默认可以请求。设置完自定义 header 字段后,问题就出现了:原来的简单请求会变成预检请求。

    XHR对象对于HTTP跨域请求有三种:简单请求、Preflighted 请求、Preflighted 认证请求。简单请求不需要发送OPTIONS嗅探请求,但只能按发送简单的GET、HEAD或POST请求,且不能自定义HTTP Headers。Preflighted 请求和认证请求,XHR会首先发送一个OPTIONS嗅探请求,然后XHR会根据OPTIONS请求返回的Access-Control-*等头信息判断是否有对指定站点的访问权限,并最终决定是否发送实际请求信息。
    

    浏览器会去向 Server 端发送一个 OPTIONS 请求,看 Server 返回的 "Access-Control-Allow-Headers" 是否有自定义的 header 字段。因为我之前没有返回自定义的字段,所以,默认是不允许的,造成了客户端没办法拿到数据。
    既然已经知道了原因,且知道了解决思路,就动手干吧。通过阅读 django-cors-headers 的源码后,发现 **corsheaders/middleware.py ** 里面已经有实现了,那就不再重复造轮子了。

    def process_response(self, request, response):
            """
            Add the respective CORS headers
            """
            origin = request.META.get('HTTP_ORIGIN')
            if not origin:
                return response
    
            enabled = getattr(request, '_cors_enabled', None)
            if enabled is None:
                enabled = self.is_enabled(request)
    
            if not enabled:
                return response
    
            # todo: check hostname from db instead
            url = urlparse(origin)
    
            if conf.CORS_ALLOW_CREDENTIALS:
                response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = 'true'
    
            if (
                not conf.CORS_ORIGIN_ALLOW_ALL and
                not self.origin_found_in_white_lists(origin, url) and
                not self.origin_found_in_model(url) and
                not self.check_signal(request)
            ):
                return response
    
            if conf.CORS_ORIGIN_ALLOW_ALL and not conf.CORS_ALLOW_CREDENTIALS:
                response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
            else:
                response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin
                patch_vary_headers(response, ['Origin'])
    
            if len(conf.CORS_EXPOSE_HEADERS):
                response[ACCESS_CONTROL_EXPOSE_HEADERS] = ', '.join(conf.CORS_EXPOSE_HEADERS)
    
            if request.method == 'OPTIONS':
                response[ACCESS_CONTROL_ALLOW_HEADERS] = ', '.join(conf.CORS_ALLOW_HEADERS)
                response[ACCESS_CONTROL_ALLOW_METHODS] = ', '.join(conf.CORS_ALLOW_METHODS)
                if conf.CORS_PREFLIGHT_MAX_AGE:
                    response[ACCESS_CONTROL_MAX_AGE] = conf.CORS_PREFLIGHT_MAX_AGE
    
            return response
    

    看完后,发现只要配置下 CORS_ALLOW_HEADERS 就好。

    from corsheaders.defaults import default_headers
    
    CORS_ALLOW_HEADERS = default_headers + (
        'aaa'
    )
    
    

    至此,问题就算解决了。

    参考链接:

  • 相关阅读:
    如何将网格式报表打印成其它样式
    拥有与实力不相称的脾气是种灾难——北漂18年(23)
    8.8.1 Optimizing Queries with EXPLAIN
    mysql 没有rowid 怎么实现根据rowid回表呢?
    secondary index
    8.5.5 Bulk Data Loading for InnoDB Tables 批量数据加载
    mysql 中key 指的是索引
    8.5.4 Optimizing InnoDB Redo Logging 优化InnoDB Redo 日志
    8.5.3 Optimizing InnoDB Read-Only Transactions 优化InnoDB 只读事务
    8.5.1 Optimizing Storage Layout for InnoDB Tables InnoDB表的存储布局优化
  • 原文地址:https://www.cnblogs.com/scharfsinnig/p/6769737.html
Copyright © 2011-2022 走看看