Bootstrap

AJAX异步请求

准备知识

在学习AJAX前,我们先了解一下JSON和XML

JSON

定义

JSON(JavaScript Object Notation, JS对象标记),是一种轻量级的数据交换格式。

它基于 ECMAScript (w3c制定的JS规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。
简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

JSON对象和JSON字符串

我们知道python中有一个json模块,通过json.dumps()方法,我们可以将python中的基本数据类型序列化为一种标准格式的字符串,进而可以存储或通过网络传输;通过json.loads()方法,又可以将这些标准格式的字符串反序列化为原数据类型。这种标准格式的字符串就是JSON字符串,

JSON对象是JS对象的子集,它包含JS中的6种数据类型:number, string, Boolean, array, null, object。也就是JSON对象一定是JS对象。注意,JS可以接受单引号,双引号的string类型,但是JSON中只有双引号的string。

JSON字符串的格式很简单,将JSON对象加一对单引号' '包起来就是JSON字符串,比如:JS数字1 转为JSON字符串是’1’ ,JS对象{"name": "Seb"}转换为JSON字符串是’{“name”: “Seb”}` 。注意,对于JS中的单引号string,它会将其转换为双引号string,然后再用单引号包起来。比如:JS字符串'cat'"cat"转换为JSON字符串都是'"cat"'

在JS中,通过JSON.stringify()方法,可以将JSON对象转化为JSON字符串;通过JSON.parse()方法,可以将JSON字符串转化为JSON对象。

XML

XML(extensible markup language, 可扩展标记语言)也是一种数据交换的格式,它比JSON出现的更早,它的格式类似于HTML的标签。

图片1

对比JSON:
这里写图片描述

对比可知,同样的信息,JSON所用的字符要比XML少很多,因而在网络传输方面更具优势。目前,JSON已经成为各大网站交换数据的标准格式。

AJAX

AJAX(Asynchronous JavaScript And XML),异步JS和XML,即使用JS语言与服务器进行异步交互,传输数据格式为XML(不过目前JSON格式已经在大部分领域取代了XML)。AJAX除了支持异步交互,另一个特点就是浏览器页面的局部刷新,由于不需要重载整个页面,不仅提高了性能,还提升了用户体验。

比如,我们平常网站登录或则注册,对于我们输入内容的验证,就是基于AJAX技术,给服务器发送用户输入的数据,服务器将验证的结果用JSON格式的字符串发回响应,前端用JS来解析JSON数据,如果有错误信息,就通过JS在页面添加错误提示;如果验证通过,就跳转至首页。

这里写图片描述

AJAX是基于JS的一门技术,不过JS的语法比较繁琐,而且还要处理不同浏览器的兼容问题,因此,一般我们通过JQuery使用AJAX,JQuery语法简洁,而且解决了浏览器兼容问题。想了解更多JQuery知识,可以参考我的另一篇博文

Ajax的jQuery实现:

下面我们看一下,通过jQuery发送ajax请求的基本形式:
前端:

<form action="{% url 'login' %}" method="post">
    <div class="form-group">
        <label for="username">用户名</label>
        <input type="text" class="form-control" id="username" name="username" required>
    </div>
    <div class="form-group">
        <label for="password">密码</label>
        <input type="password" class="form-control" id="password" name="password" required>
    </div>
    <p>
    <button type="submit" class="btn btn-primary btn-block">登录</button>
    </p>
</form>

<script>
    // 点击提交按钮,ajax发送;验证成功,通过location.href = url来跳转
    $('form').submit(function(e){
        e.preventDefault();  //阻止默认提交
        var username = $("[name='username']").val(); // 注意,jquery筛选一定加引号,否则报错uncaught
        var password = $("[name='password']").val();
        $.ajax({
            url: "{% url 'login' %}",
            type: "POST",
            data: {
                "username": username,
                "password": password,
            },
            success: function(res) {  //res是server端响应
                response = JSON.parse(res); //将json字符串解析为json对象(即JS对象)
                if (response['errors']) {
                    console.log(response['errors']);
                    } else {
                        location.href = "/index/";  //跳转至首页
                    }
</script>

后端:

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password') 
        //如果前端发来的data中key对应的value是列表,要用getlist(key)来获取
        ajax_response = {'user': None, "errors": ""}
        if username == 'egon' and password == '123':
            ajax_response['user']='egon'
        else:
            ajax_response['errors']='用户名或密码错误'
        return HttpResponse(json.dumps(ajax_response))
    if request.method == 'GET':
        return render(request, 'login.html')

参数说明

通过jQuery发送Ajax请求的几个基本参数:

  1. url, 请求的地址
  2. type, 请求的方法:默认是get
  3. data, 请求要携带的数据,是一个json的object对象,类似python中的字典
  4. 基本流程:1. Ajax发送请求 2. server接收响应 3. server处理数据 4. server返回响应 5. Ajax接收响应;如果以上都顺利进行(server返回200 ok 状态码),就会执行success参数对应的函数。可选的还有error, server端错误时执行的函数; complete, 无论是否错误,都执行complete对应的函数; statusCode, 根据状态码执行不同的函数,比如:statusCode: {'403': function(){}, '401': function(){}}


其它参数:

  1. processData, 声明当前的data数据是否进行转码或预处理,默认为true,即预处理。

  2. contentType, 发送信息至服务器时内容编码类型,默认值: application/x-www-form-urlencodedURL编码。注意,在Django中,其request.POST是从请求体request.body中转化过来的,它只识别URL编码的内容,即name=xxx&age=xxx这种格式的内容。因此,如果在后端通过Django获取数据时,request.POST可能为空,这时可以从request.body中去找数据,其中一定有值,除非对方没有发送。

  3. traditional, 一般是我们的data数据有数组时会用到,不过对于数组和字典,更好的处理方式是JSON.stringfy()序列化后再发送:

    $.ajax({
        url: '/test_ajax.html',
        type: 'POST',
        data: {
            'name': 'Ayhan',
            'age': 18,
            'hobby': ['eating', 'sleeping', 'playing'], //数组
        },
        traditional: true, //有它,才能正确发送数组
        succes: function(response) {
            console.log(response);
        }
    })

    说明:

    1. 如果不设置traditional: true,后台拿到的数据是这样的:b'name=Ayhan&age=18&hobby%5B%5D=eating&hobby%5B%5D=sleeping&hobby%5B%5D=playing', %5B%5D表示中括号[],这是因为Ajax发送数据默认带请求头带内容编码"Content-type", "application/x-www-form-urlencoded",表示客户端提交给服务器文本内容的编码方式是URL编码,即除了标准字符外,每字节以双字节16进制前加个“%”表示。
    2. 设置了traditional: true后,后台拿到的数据是这样的:b'name=Ayhan&age=18&hobby=eating&hobby=sleeping&hobby=playing',会对数组进行深层迭代,不过还是需要手动分割信息。
  4. dataType: 'JSON',如果给出这个参数,指定返回的响应必须是json格式;这样服务端返回的json响应不需要JSON.parse解析就可以直接作为json对象使用:

    $.ajax({
            url: '/test_ajax.html',
            type: 'GET',
            data: {},
            dataType: 'JSON', //指定返回的响应必须是JSON字符串格式
            success: function (response) {
                console.log(response.stauts);
                console.log(response.msg);
            }
        })

    后端逻辑:

    from django.http import JsonResponse
    
    def test_ajax(request):
    
        response = {
                'status': 1,
                'msg': 'hello, handsome!',
            }
    
        return JsonResponse(response)

    说明:

    return JsonResponse(response)相当于return HttpResponse(json.dumps(response))

    JsonResponse如果接收列表,会报错,它默认列表属于不规范的数据,没有key,不能包含状态等详细信息。

通过JSON发送数组/列表,字典

Ajax的data中,如果数据是字符串或数字,都可以直接发送。但是对于数组/列表,字典,最好的方式是将数进行JSON.stringfy()序列化发给后端:

var res = {'name': 'Ayhan', 'age': 18, 'hobby': ['eating', 'sleeping', 'playing']};

$.ajax({
    url: '/test_ajax.html/',
    type: 'POST',
    data: JSON.stringify(res), //数据整体序列化
    success: function (response) {
        console.log(response);
    }
})

这样,在Django后台就可以通过request.body中拿到b'{"name":"Ayhan","age":18,"hobby":["eating","sleeping","playing"]}'这样的信息,直接反序列化json.loads(request.body.decode('utf-8')),就可以拿到字典对象。


当然也可以单独序列化数组,后端通过key提取字符串再反序列化为列表json.loads(request.POST.get('hobby'))即可:

$.ajax({
    url: '/test_ajax.html/',
    type: 'POST',
    data: {
        'name': 'Ayhan',
        'age': 18,
        'hobby': JSON.stringify(['eating', 'sleeping', 'playing']) //单独序列化数组
    },
    success: function (response) {
        console.log(response);
    }
})

说明:这种方式对于处理data中包含字典格式的数据也是适用的。

Ajax前置操作及全局设置

beforeSend

在发送Ajax之前,我们可以利用beforeSend参数作一些前置操作,比如为请求设置csrf-token,如果是在模板中,那么直接多发送送一个键值对即可,引擎会渲染出来,参考博客。但是如果是在本地客户端,对于POST, DELETE, PUT等请求方式,会被CSRF拦截,怎么做呢?

通过cookie,在请求头中设置csrf-token键值对。注意,jQuery原生不支持cookie操作,需要导入扩展jquery.cookie.js

下面我们看一下如何操作:

  1. 第一次GET请求服务端时,服务端返回cookie信息

    def server(request):
        from django.middleware.csrf import get_token
        get_token(request)  # Returns the CSRF token
        return render(request, 'server.html')
  2. 以后客户端访问时,在请求头中带着这个cookie信息就可以了(前提需要引入扩展jquery.cookie.js)。

    //方式一:直接设置请求头headers
    $.ajax({
        url: requestUrl,
        type: 'delete',
        data: JSON.stringify(ids),
        dataType: 'JSON',
        headers: {'X-CSRFToken': $.cookie('csrftoken')}, 
    })
    
    //方式二:beforSend参数,在发送请求前,执行一些操作
    $.ajax({
        url: requestUrl,
        type: 'delete',
        data: JSON.stringify(ids),
        dataType: 'JSON',
        beforeSend: function (xhr) {
            // 请求头中设置一次csrf-token
            xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
        },
    })

    说明:不论是哪种方式,本质都是通过setRequestHeader方法为 XMLHttpRequest对象设置请求头。

ajaxSetup

虽然通过如上方式,可以解决CSRF问题,但是,每次都要手动设置。其实我们可以利用ajaxSetup来作全局设置,每次发送Ajax前,执行一些特定操作:

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection  
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); -- 正则匹配
}

$.ajaxSetup({
    beforeSend: function (xhr, settings) {
        // 全局Ajax中添加请求头X-CSRFToken,用于跨过CSRF验证
        if (!csrfSafeMethod(settings.type)) {
            xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
        }
    }
});

这样,只需将这段代码放到发送Ajax请求前,就可以根据请求方式自动设置CSRFToken的cookie信息。

Ajax跨域

Ajax无法处理跨域请求

当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求

出于安全原因,所有浏览器都遵循同源策略(same-origin policy,具体请自行搜索),它限制Ajax处理跨域请求:当Ajax中url参数是外部域名时,尽管Ajax可以将请求发送出去,但是服务端返回的响应会被浏览器阻止。下面我们来模拟下这种情况:

  1. 在Django中新建两个web项目:

    项目A提供API(URL):http://127.0.0.1:9000/get_data.html,它返回一些字符串:

    from django.shortcuts import HttpResponse
    
    def get_data(request):
        return HttpResponse('来自火星的遥远电波')


    项目B中的域名是:http://127.0.0.1:8000/index.html/,访问它将返回欢迎页面,并在页面加载完后,对API发起跨域Ajax请求:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="/static/plugins/jquery-3.2.1.js"></script>
        <script>
            $(function () {
                var API = 'http://127.0.0.1:9000/get_data.html';
    
                $.ajax({
                    url: API,
                    type: 'GET',
                    success: function (response) {
                        console.log(response)
                    }
                })
            })
    
        </script>
    </head>
    <body>
    <h1>欢迎访问主页</h1>
    <hr>
    </body>
    </html>


  2. 在chrome浏览器中访问项目B的域名:http://127.0.0.1:8000/index.html/,右键查看Console(控制台)信息:

    1.png


  3. 如果查看项目A运行的状态信息,确实收到了一次GET请求,并且响应200状态码,说明请求被成功处理并返回了响应:

    2.png


因此,在浏览器中,Ajax可以将跨域请求发送出去,并且服务端也处理了请求,只是响应被浏览器阻止了。那么如果解决呢?

解决方案

下面给出三种解决方案:requests通过后台服务器发送,JSONP与COSRS还是通过浏览器。

requests

通过后台requests模块发送跨域请求,Ajax再从后台请求数据数据,这样Ajax还是从同源地址获取数据,略。

JSONP
回顾标签的src属性和<script>标签

还是上面的栗子,如果我们在项目A页面中提供一个API:"http://127.0.0.1:9000/static/img/ayhan_huang.jpg",在页面中通过<img>标签的src属性来请求这个API:

<img src="http://127.0.0.1:9000/static/img/ayhan_huang.jpg">

这时,网页可以正常显示这张图片:

3.png


类似的还有<script>, 标签,比如我们通过CDN引入网络上的CSS或JS文件:<script src="https://cdn.bootcss.com/jquery/3.2.1/core.js"></script>

<iframe>标签,请求另一个网站的内容并嵌套在当前窗口:<iframe src="http://blog.csdn.net/Ayhan_huang/article/details/78220032" style="width: 200px; height: 300px"></iframe>

这些标签都具有src属性,总结:src属性的标签一般不受同源策略的限制。


基于以上分析,下面我们不通过Ajax,而是直接用<script>标签对API发起访问:

<script src="http://127.0.0.1:9000/get_data.html"></script>

查看chrome Console信息:

4.png

错误提示:’来自火星的遥远电波’未定义

说明,我们通过<script>标签引入的内容,会作为JS代码执行,这里是收到服务器A返回的字符串后,将其视作JS代码来执行,因此发生未定义错误。知道这一点后,只要想办法让服务器A返回的字符串符合JS代码的语法,就可以顺利执行!比如,如果服务器A返回的响应是"alert('来自火星的遥远电波')",那么页面中收到该字符串响应后,执行并弹出窗口:

5.png


函数整合

为了拿到并操作跨域的数据,我们根据以上思路用回调函数整合一下:

  1. 本地客户端:

    <script>
    
        function func(ars) {
            console.log(arg);
        }
    
    </script>
    <script src="http://127.0.0.1:9000/get_data.html"></script>
  2. 远程服务端:

    def get_data(request):
        return HttpResponse("func('来自火星的遥远电波')")

    说明:客户端定义函数func,服务端发送的数据用"func()"包起来,这样客户端通过<script>src属性获取到服务端的响应后,就会将响应数据作为参数,执行func函数。


通过这种方式,需要约定远程服务端返回的响应字符串具有某种格式,比如上面的"func('XXX')",以便作为JS代码在本地执行。因此可以,1. 我们可以将外层包的函数名作为URL的查询字符串发送过去,服务端从请求中提取函数名,并用函数名包裹数据,返回响应。2. 另外,我们还希望<script>标签是实时生成的,用完即删除。基于这两点,我们可以将上面的代码重新改造:

  1. 本地客户端:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="/static/plugins/jquery-3.2.1.js"></script>
    </head>
    <body>
    <h1> 欢迎访问主页 </h1>
    <hr>
    
    <button id="send-jsonp">点我发送JSONP</button>
    
    <script>
    
        //回调函数,执行完删除script标签
        function func(arg) {
            console.log(arg);
            document.head.removeChild(tag);
        }
    
        //创建script标签并赋值src属性
        function jsonp(url) {
            tag = document.createElement('script');
            tag.src = url;
            document.head.appendChild(tag);
            console.log(tag)
        }
    
        //回调函数作为url查询字符串传递:?callback=func
        var API = 'http://127.0.0.1:9000/get_data.html?callback=func';
        var btn = document.getElementById('send-jsonp');
    
        btn.onclick = function () {
            jsonp(API);
        };
    
    </script>
    </body>
    </html>
  2. 远程服务端:

    from django.shortcuts import HttpResponse
    
    def get_data(request):
        func_name = request.GET.get('callback')
        print(func_name)
        return HttpResponse("{func_name}('来自火星的遥远电波')".format(func_name=func_name))
JSONP

JSONP正是利用了上述原理:本质就是生成<script>标签,通过src发起GET请求,而不是通过Ajax发送请求。从效果上来说,它成功拿到了数据,页面也没有刷新,并且不受同源策略的限制。通过jQuery,我们可以不用手动实现上面的过程,更方便的通过JSONP发送请求。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/plugins/jquery-3.2.1.js"></script>
</head>
<body>
<h1> 欢迎访问主页 </h1>
<hr>

<button id="send-jsonp">点我发送JSONP</button>

<script>

    var btn = document.getElementById('send-jsonp');

    btn.onclick = function () {
        $.ajax({
            url: 'http://127.0.0.1:9000/get_data.html',
            type: 'GET',
            dataType: 'JSONP',
            success: function (response) {
                console.log(response);
            }
        })
    };

    /*
    //也可以单独定义回调函数,通过jsonpCallback参数指定;该函数名最终会拼接到url查询字符串后面
    function func(response) {
        console.log(response);
    }

    btn.onclick = function () {
        $.ajax({
            url: 'http://127.0.0.1:9000/get_data.html',
            type: 'GET',
            dataType: 'JSONP',
            jsonpCallback: 'func',
        })
    };
    */

</script>
</body>
</html>


远程服务器逻辑不变:

from django.shortcuts import HttpResponse

def get_data(request):
    func_name = request.GET.get('callback')
    print(func_name)
    return HttpResponse("{func_name}('来自火星的遥远电波')".format(func_name=func_name))

说明:

  1. JSONP的本质决定了只支持GET请求,即使通过jQuery改为POST方式,还是被转为GET请求。

  2. 虽然形式是通过Ajax发送,但是内部并不是Ajax。

  3. success: function () {}内部会将回调函数function添加到url后面的查询字符串(不是明文添加,远程打印出来是回掉函数名是jQuery321019246026086630175_1508238544570这样的形式,每次都会变)。

  4. 远程还是必须通过查询字符串获取函数名request.GET.get('callback'),将数据包起来。


实例

找了一个江西卫视的节目单的API:http://www.jxntv.cn/data/jmd-jxtv2.html,浏览器访问结果如下:

7.png

尽管内容乱码,但是发现数据结构可以看作是list()函数包裹的字符串,因此,我们利用JSONP,定义回调函数list(),来请求这个API:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/plugins/jquery-3.2.1.js"></script>
</head>
<body>
<h1> 欢迎访问主页 </h1>
<hr>

</div>

<script>

    function list(response) {
        console.log(response);
    }

    var url = 'http://www.jxntv.cn/data/jmd-jxtv2.html';

    $.ajax({
        url: url,
        type: 'GET',
        dataType: 'JSONP',
        jsonpCallback: 'list',
    })

</script>
</body>
</html>

查看打印结果:

7.png

所有数据已经全部获取到了。

CORS

现在的浏览器可以支持主动设置从而允许跨域请求,即Cross-Origin Resource Sharing 跨域资源共享,其本质是设置响应头,使得浏览器允许跨域请求。它和JSONP一样,也是通过浏览器发送请求,不同的是JSONP绕过了同源策略,而CORS则是主动解决问题。之前我们通过Ajax发送跨域http请求时,浏览器给出的错误信息是:

Failed to load http://127.0.0.1:9020/get_data.html: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8000' is therefore not allowed access.

其实就是远程服务器响应头中缺少信息'Access-Control-Allow-Origin'及其值'http://127.0.0.1:8000'。因此通过CORS解决同源策略,本地客户端不用做任何操作,只需修改远程服务器的后台逻辑,添加以上请求头信息即可。

还是上面的例子,现在客户端可以直接通过Ajax发送请求:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/plugins/jquery-3.2.1.js"></script>
</head>
<body>
<h1> 欢迎访问主页 </h1>
<hr>
<button id="magic-btn">Send CORS</button>
</div>

<script>

    var API = 'http://127.0.0.1:9020/get_data.html';

    $('#magic-btn').click(function () {
        $.ajax({
            url: API,
            type: 'GET',
            success: function (response) {
                console.log(response);
            }
        })
    )}
</script>
</body>
</html>


远程服务端修改,设置响应头:

def get_data(request):
    response = HttpResponse('来自火星的遥远电波')
    response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8000'
    return response


跨域的其它知识

  1. 简单请求和非简单请求:

    • 简单请求:一次请求
    • 非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
  2. 如何判断:

    条件:
        1、请求方式:HEAD、GET、POST
        2、请求头信息:
            Accept
            Accept-Language
            Content-Language
            Last-Event-ID
            Content-Type 对应的值是以下三个中的任意一个
                                    application/x-www-form-urlencoded
                                    multipart/form-data
                                    text/plain

    注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求

  3. 预检:

    - 请求方式:OPTIONS
    - “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
    - 如何“预检”
         => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
            Access-Control-Request-Method
         => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
            Access-Control-Request-Headers
    def get_data(request):
        if request.method == 'OPTIONS': # 预检
            response = HttpResponse() # 预检一般不需要返回数据
            response['Access-Control-Allow-Origin'] = '*' # 允许的域名,* 是所有
            response['Access-Control-Allow-Methods'] = 'PUT' # 设置允许的请求方式
            response['Access-Control-Allow-Headers'] = 'key' # 允许的请求头
            return response
        else:
            response = HttpResponse('来自火星的遥远电波')
            return response
  4. 跨域cookie

    在跨域请求中,默认情况下,HTTP Authentication信息,Cookie头以及用户的SSL证书无论在预检请求中或是在实际请求都是不会被发送。

    如果想要发送:

    • 浏览器端:XMLHttpRequest的withCredentials为true
    • 服务器端:Access-Control-Allow-Credentials为true
    • 注意:服务器端响应的 Access-Control-Allow-Origin 不能是通配符 *
    $.ajax({
        url: API,
        type: 'PUT',
        dataType: 'text',
        headers: {'k1': 'v1'}, //设置了自定义请求头,即是复杂请求。
        xhrFields: {withCredentials: true},
        success: function (data) {
            console.log(data);
        }
    });
     response['Access-Control-Allow-Credentials'] = 'true' 
  5. 补充说明:

    1. 不到迫不得已,不要允许复杂请求,服务器压力倍增;
    2. 尽量不发复杂请求,可以选择将参数放到url


另外,也可以通过安装这个插件来处理CORS请求:https://github.com/ottoyiu/django-cors-headers

遇到的问题

我测试是从8000端口往9000端口发送请求。对于CORS,如果从8000端口往非9000端口发送请求,如果不设置响应头,错误信息如下(设置了CORS响应头后即可正常通信):

Failed to load http://127.0.0.1:8050/get_data.html: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8000' is therefore not allowed access.

但是如果从8000端口往9000端口发请求,则无论如何设置CORS都不行(安装插件也是),浏览器错误提示也多了一个重定向:

Failed to load http://127.0.0.1:9000/get_data.html: Redirect from 'http://127.0.0.1:9000/get_data.html' to 'http://127.0.0.1:9000/get_data.html/' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8000' is therefore not allowed access.

我浏览器是chrome v61。如果大家有这方面的经验,欢迎指教,谢谢!


补充,经过多次测试,同样的条件用微软的Edge浏览器,竟然可以通信。。。然后在chrome浏览器下,换用了非9000端口(试了9001、9020都没毛病),也可以通信。搞不明白,chrome和9000端口有什么仇,

总之,我的内心是崩溃的。。。


总结

JSONP 和 CORS都是通过浏览器, requests模块通过后台,因此服务器压力大。

通过CORS,客户端不需要作任何改变。并且支持更多请求方式,是未来的趋势。

JSONP兼容性比CORS好,老式浏览器对CORS支持可能不太好。

ajax的JS实现

XMLHttpRequest

ajax的所有操作都是基于XMLHttpRequest对象,用于在后台与服务器交换数据。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

创建XMLHttpRequest 对象

var xmlHttp = new XMLHttpRequest();

所有的现代浏览器都内建了XMLHttpRequest 对象,但是IE7以前的版本有所不同。为了照顾浏览器的兼容性问题,可以用以下方式来创建XMLHttpRequest对象:
方式一:判断浏览器是否支持XMLHttpRequest 对象

var xmlhttp;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }

方式二:基于异常捕捉的方式

function createXMLHttpRequest() {
        var xmlHttp;
        // 适用于大多数浏览器,以及IE7和IE更高版本
        try{
            xmlHttp = new XMLHttpRequest();
        } catch (e) {
            // 适用于IE6
            try {
                xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                // 适用于IE5.5,以及IE更早版本
                try{
                    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e){}
            }
        }            
        return xmlHttp;
    }

使用流程

建立连接

var url = “/ajax_handler”;
var params = “lorem=ipsum&name=binny”;

open(method, url, async):

var xmlHttp = createXMLHttpRequest();
xmlHttp.open("GET", url+"?"+params, true); 

method请求方法,GET/POST;url, 请求的服务端路径;async参数可以不给,默认是true,执行异步请求

发送请求

xmlHttp.send(null);

对于GET方式来说,没有请求体,参数是放在在url的”?”后面,即查询字符串;出于兼容性考虑,如果send()方法没有参数,建议给出null。对于POST方式,参数要放在send()方法中,这个下面再说。

接收响应

onreadystatechange 事件
该事件在XMLHttpRequest对象的状态发生变化时被调用。XMLHttpRequest有五种状态:
- 0:创建了XMLHttpRequest对象;
- 1:open()方法建立连接;
- 2:send()方法发送数据;
- 3:读取服务器响应开始;
- 4:读取服务器响应结束;
事件会在状态1 - 4 时触发:
readyState属性,获取对象状态
该属性用来获取XMLHttpRequest对象的状态,下面的alert会执行4次,并显示每次的状态:

xmlHttp.onreadystatechange = function() {
            alert(xmlHttp.readyState);
        };

status属性,获取响应状态码
服务器响应状态码如果是200,表示请求被成功处理,这通常也是我们所关心的。通过XMLHttpRequest对象的status属性,可以获取服务器的状态码
responseText属性,获取响应内容

xmlHttp.onreadystatechange = function() {
            if(xmlHttp.readyState == 4 && xmlHttp.status == 200) {
                alert(xmlHttp.responseText);    
            }
        };

在XMLHttpRequest中使用POST方法

不同于GET方式,POST请求时,没有查询字符串,参数放到send()方法内,也就是请求体。
而且,需要设置请求头:

var url = "/ajax_handler";
var params = "lorem=ipsum&name=binny";

var xmlHttp = new XMLHttpRequest();
xmlHttp .open("POST", url, true);

//设置请求头 setRequestHeader(key, value)
xmlHttp .setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// 注意 :form表单会默认"Content-type"键值对不设定,会导致Web服务器忽略请求体的内容,因此必须加上上面这行
// 表示客户端提交给服务器文本内容的编码方式是URL编码,即除了标准字符外,每字节以双字节16进制前加个“%”表示
xmlHttp .setRequestHeader("Content-length", params.length);
xmlHttp .setRequestHeader("Connection", "close");

xmlHttp .send(params);

xmlHttp .onreadystatechange = function() {
    if(http.readyState == 4 && http.status == 200) {
        alert(http.responseText);
    }
}

为什么要设置请求头

因为客户端与服务端的通信使用HTTP协议,HTTP协议规定,客户端发出的请求,必须有请求头,用来告诉
服务器客户端要的资源及相关的参数。XMLHttpRequest对象遵循HTTP协议,因此需要发送请求头给服务器。
但是 XMLHttpRequest默认的情况下有些参数可能没有说明在HTTP头里,比如提交form表单时,是没有”Content-type”这个键和值的。因此我们需要通过XMLHttpRequest提供的setRequestHeader 方法,来手动添加。

发送二进制数据

参考Django中static & media的简单配置及图片上传实践

;