美文网首页
ajax跨域问题(python版本)

ajax跨域问题(python版本)

作者: warmsirius | 来源:发表于2019-08-17 01:25 被阅读0次

    一、为什么会出现跨域

    跨域问题来源于JavaScript的同源策略,即只有 协议+主机名+端口号 (如存在)相同,则允许相互访问。即JavaScript只能访问和操作自己域下的资源,不能访问和操作其他域下的资源。

    跨域问题是针对JS和ajax的,html本身没有跨域问题,比如a标签、script标签、甚至form标签(可以直接跨域发送数据并接收数据)

    注意:localhost和127.0.0.1也属于跨域。

    1. 跨域产生的原理

    • 当向不同源的资源发起Ajax请求时, 浏览器会加上Origin字段来标识源
    Accept: */*
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9
    Connection: keep-alive
    Content-Length: 8
    Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    Host: localhost:7070
    Origin: http://localhost:9090   协议+域名+端口
    
    • 服务器会根据Origin字段决定是否同意这次请求

    如果Origin指定的源不在许可范围内, 服务器会返回一个不带有Access-Control-Allow-Origin字段的响应. 浏览器解析时发现缺少了这个字段, 就会报错.

    注意:这种错误不能通过状态码识别, 因为状态码很有可能就是200.

    解决方案一、在服务端添加响应头Access-Control-Allow-Origin

    前面说明了,Ajax跨域失败是因为响应中缺少了响应头Access-Control-Allow-Origin, 那么就想办法加上去.

    修改Django中的views.py文件修改views.py中对应API的实现函数,给返回值加上响应头Access-Control-Allow-Origin,允许其他域通过Ajax请求数据:

    区分简单的请求和复杂的请求

    简单请求:

    满足以下两个条件的请求。

    (1) 请求方法是以下三种方法之一:

    • HEAD
    • GET
    • POST

    (2) HTTP的头信息不超出以下几种字段:

    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

    复杂请求:

    非简单请求就是复杂请求。

    非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

    非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

    预检请求为OPTIONS请求,用于向服务器请求权限信息的。

    预检请求被成功响应后,才会发出真实请求,携带真实数据。

    a. 简单的请求只需要加上"Access-Control-Allow-Origin"

     def myview(_request):  
        response = HttpResponse(json.dumps({"key": "value", "key2": "value"}))
        response["Access-Control-Allow-Origin"] = "*"  #也可换成想要访问的域名
        response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"  
        response["Access-Control-Max-Age"] = "1000"  
        response["Access-Control-Allow-Headers"] = "*"  
        return response 
    

    b. 复杂的请求需要设置两步

    • 服务器端后台接口允许OPTIONS请求,如果是非简单的请求方法,需要设置请求访问的方式
    • 后端增加对应的头部支持
    # DELETE方法下的跨域
    def myview(request):
        if request.method == 'OPTION':
            res = HttpResponse()
            response["Access-Control-Allow-Methods"] = " DELETE"  
            response["Access-Control-Allow-Headers"] = "*"  
            return res
        res = HttpResponse()
        response["Access-Control-Allow-Headers"] = "*"  
        return res
    

    解决方法二、利用JSONP

    JSONP是JSON with Padding的略称。它是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。

    1. JSONP实现流程

    JSONP的实现步骤大致如下(参考了来源中的文章)

    • i. 客户端网页网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制
    function addScriptTag(src) {
      var script = document.createElement('script');
      script.setAttribute("type","text/javascript");
      script.src = src;
      document.body.appendChild(script);
    }
    
    window.onload = function () {
      addScriptTag('http://example.com/ip?callback=foo');
    }
    
    function foo(data) {
      console.log('response data: ' + JSON.stringify(data));
    };                      
    

    请求时,接口地址是作为构建出的脚本标签的src的,这样,当脚本标签构建出来时,最终的src是接口返回的内容

    • ii. 服务端对应的接口在返回参数外面添加函数包裹层
    foo({
      "test": "testData"
    });               
    
    • iii. 由于<script>元素请求的脚本,直接作为代码运行。

    这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。

    注意: 一般的JSONP接口和普通接口返回数据是有区别的,所以接口如果要做JSONP兼容,需要进行判断是否有对应callback关键字参数,如果有则是JSONP请求,返回JSONP数据,否则返回普通数据

    JSONP使用注意

    基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求,所以遇到那种情况,就得参考下面的CORS解决跨域了(所以如今它也基本被淘汰了)。

    2. 基于JS的JSONP的实现

    前面讲了JSONP的实现原理,现在我们可以自己写JS来实现JSONP功能。

    一般情况下,我们希望这个script标签能够动态的调用,而不是像固定在html里面所以没等页面显示就执行了,很不灵活。

    我们可以通过页面的触发事件操作后,通过javascript动态的创建script标签,这样我们就可以灵活调用远程服务。实例如下:

    #项目1中html部分代码
    <script>
        //回调函数
        function bar(data) {
            alert(data);
            console.log(JSON.parse(data))
        }
    </script>
    
    <script>
        //触发事件生成script标签
        $(".ajax_btn").click(function () {
            script_request("http://127.0.0.1:8002/send_ajax/?callback=bar") //请求路径带回调函数名
    
        });
        //定义一个生成script标签的函数
        function script_request(url) {
            var $script=$("<script>");
            $script.attr("src",url);
            $("body").append($script);
            $("body script:last").remove()
    
        }
    </script>
    
    #项目2中的视图函数,即上述script标签请求的路径
    def send_ajax(request):
        import json
        func_name=request.GET.get("callback") #获得回调函数的名字
        dic={"k1":"v1"}
        return HttpResponse("%s('%s')" %(func_name,json.dumps(dic)))
    

    为了更加灵活,上述我们将你自己在客户端定义的回调函数的函数名传送给服务端,服务端则会返回以你定义的回调函数名的方法,将获取的json数据传入这个方法完成回调。

    3. 基于jquery的JSONP的实现: $.getJSON()

    /项目1中html中部分代码
    <script>
        $(".ajax_btn").click(function () {
            $.getJSON("http://127.0.0.1:8002/send_ajax/?callback=?",function (data) {
                alert(data);
                console.log(data)
            })
    
        });
    </script>
    //项目2中的视图函数,即跨域请求的路径
    def send_ajax(request): 
      import json 
      func_name=request.GET.get("callback") #获得回调函数的名字 
      dic={"k1":"v1"} print("ok") 
      return HttpResponse("%s('%s')" %(func_name,json.dumps(dic)))
    

    如上,jQuery框架也当然支持JSONP,可以使用 $.getJSON(url,[data],[callback]) 方法。

    与js实现的方式相比,我们并不要自己生成一个script标签,客户端也并不需要自己定义一个回调函数.

    注意: 在$.getJSON(url,[data],[callback])方法的url的后面必须添加一个callback参数,这样getJSON方法才会知道是用JSONP方式去访问服务,callback后面的那个问号是内部自动生成的一个回调函数名。

    4. 基于jquery的JSONP的实现:dataType: JSONP

    上述这种方法,很方便,不需要我们自己定义回调函数和指定回调函数名,但是,如果说我们想指定自己的回调函数名,或者说服务上规定了固定回调函数名该怎么办呢?

    我们可以使用$.ajax方法来实现。如下例:

    //项目1中html中部分代码
    <script>
       $(".ajax_btn").click(function () {
          $.ajax({
              url:"http://127.0.0.1:8002/send_ajax/",
              dataType:"jsonp",                 //相当于script标签
              jsonp:'callbacks',                //相当于路径中回调函数路径参数键值对的键
              jsonpCallback:"func"              //相当于路径中回调函数路径参数键值对的值,回调函数名
    
          })
    
       });
       //定义回调函数
       function func(arg) {
           alert(arg);
           console.log(arg)
    
       }
    </script>
    
    //项目2中的视图函数,即跨域请求的路径
    def send_ajax(request):
       import json
       func_name=request.GET.get("callbacks")    #获得回调函数的名字
       dic={"k1":"v1"}
       print("ok")
       return HttpResponse("%s('%s')" %(func_name,json.dumps(dic)))
    

    5. 基于jquery的JSONP的实现(三)

    在上小节中jsonp: 'callbacks'就是定义一个存放回调函数的键,jsonpCallback是前端定义好的回调函数方法名,server端接受callback键对应值后就可以在其中填充数据打包返回。

    但是,jsonpCallback参数可以不定义,jquery会自动定义一个随机名发过去,那前端就得用回调函数来处理对应数据了。利用jQuery可以很方便的实现JSONP来进行跨域访问。

    //项目1中html中部分代码
    <script>
        $(".ajax_btn").click(function () {
           $.ajax({
               url:"http://127.0.0.1:8002/send_ajax/",
               dataType:"jsonp",
               jsonp:'callbacks',
               success:function (data) {
                   alert(data);
                   console.log(data)
               }
           })
        });
    </script>
    
    //项目2中的视图函数,即跨域请求的路径
    def send_ajax(request):
        import json
        func_name=request.GET.get("callbacks")    #获得回调函数的名字
        dic={"k1":"v1"}
        print("ok")
        return HttpResponse("%s('%s')" %(func_name,json.dumps(dic)))
    

    解决办法三、使用Django提供的 Django-cors-headers 来处理跨域

    • 从GitHub上面下载Django-cors-headers

    • pip install Django-cors-headers.zip

    • 在settings.py中的中间件中配置 【'corsheaders.middleware.CorsMiddleware',】

    注意: 这个中间件一定要写在CSRF之前,为了方便处理,一般写在最前面.

    • 设置 CORS_ORIGIN_ALLOW_ALL = True,即允许所有的跨域请求,当然,这里也可以设置为False,然后配合 CORS_ORIGIN_WHITELIST 白名单来使用
    INSTALLED_APPS = [
        ...
        'corsheaders',
        ...
     ] 
    
    MIDDLEWARE_CLASSES = (
        ...
        'corsheaders.middleware.CorsMiddleware',
        'django.middleware.common.CommonMiddleware', # 注意顺序
        ...
    )
    #跨域增加忽略
    CORS_ALLOW_CREDENTIALS = True
    CORS_ORIGIN_ALLOW_ALL = True
    CORS_ORIGIN_WHITELIST = (
        '*'
    )
    
    CORS_ALLOW_METHODS = (
        'DELETE',
        'GET',
        'OPTIONS',
        'PATCH',
        'POST',
        'PUT',
        'VIEW',
    )
    
    CORS_ALLOW_HEADERS = (
        'XMLHttpRequest',
        'X_FILENAME',
        'accept-encoding',
        'authorization',
        'content-type',
        'dnt',
        'origin',
        'user-agent',
        'x-csrftoken',
        'x-requested-with',
        'Pragma',
    )
    

    如此,我们的跨域处理即完成,支持所有的请求。

    相关文章

      网友评论

          本文标题:ajax跨域问题(python版本)

          本文链接:https://www.haomeiwen.com/subject/cdlcsctx.html