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的标签。

AJAX异步请求_第1张图片

对比JSON:
这里写图片描述

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

AJAX

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

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

AJAX异步请求_第2张图片

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请求:

    
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
        <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(控制台)信息:

    AJAX异步请求_第3张图片


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

    2.png


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

解决方案

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

requests

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

JSONP

回顾标签的src属性和

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


基于以上分析,下面我们不通过Ajax,而是直接用

  • 远程服务端:

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

    说明:客户端定义函数func,服务端发送的数据用"func()"包起来,这样客户端通过

  • 你可能感兴趣的:(javascript)