原生AJAX

Ajax主要就是使用 【XmlHttpRequest】对象来完成请求的操作,该对象在主流浏览器中均存在(除早起的IE),Ajax首次出现IE5.5中存在(ActiveX控件)

1、XmlHttpRequest对象介绍 (不兼容IE老版本)

x = new XMLHttpRequest()

XmlHttpRequest对象的主要方法:

格式说明:

返回值  方法名(参数,...)

a. void open(String method,String url,Boolen async)
   用于创建请求
       method: 请求方式(字符串类型),如:POST、GET、DELETE...
       url:    要请求的地址(字符串类型)
       async:  是否异步(布尔类型)
b. void send(String body)
    用于发送请求
        body: 要发送的数据(字符串类型)
c. void setRequestHeader(String header,String value)
    用于设置请求头
        header: 请求头的key(字符串类型)
        vlaue:  请求头的value(字符串类型)
d. String getAllResponseHeaders()
    获取所有响应头
        响应头数据(字符串类型)
e. String getResponseHeader(String header)
    获取响应头中指定header的值
        header: 响应头的key(字符串类型)
        响应头中指定的header对应的值
f. void abort()

XmlHttpRequest对象的主要属性:

a. Number readyState
   状态值(整数)
      0-未初始化,尚未调用open()方法;
      1-启动,调用了open()方法,未调用send()方法;
      2-发送,已经调用了send()方法,未接收到响应;
      3-接收,已经接收到部分响应数据;
      4-完成,已经接收到全部响应数据;
b. Function onreadystatechange
   当readyState的值改变时自动触发执行其对应的函数(回调函数)
c. String responseText
   服务器返回的数据(字符串类型)
d. XmlDocument responseXML
   服务器返回的数据(Xml对象)
e. Number states
   状态码(整数),如:200、404...
f. String statesText
   状态文本(字符串),如:OK、NotFound...
<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/jquery-2.2.3.min.js"></script>
    <script src="/static/jquery.cookie.js"></script>
</head>
        <button value="XML_SEND" onclick="xmlSend();">XMLHttpRequest()</button>
    </div>
    <script>
        function xmlSend(){
            let xml = new XMLHttpRequest();                            //创建XMLHttpResquest对象
            xml.open('POST','/ajax_original/');                         //设置请求地址
            xml.setRequestHeader('X-CSRFtoken',$.cookie('csrftoken') ); //设置请求头
            {#xml.send();                                                 //发起请求#}
            xml.onreadystatechange = function () {                     //状态改变
                console.log('xml 实例readyState状态改变:',xml.readyState);
                if (xml.readyState === 4) {
                    console.log('收到返回值字符串:',xml.responseText);
                    console.log('转换为对象:',JSON.parse(xml.responseText));
                    console.log('服务端返回status状态码:',xml.status);
                    console.log('服务端返回statusText文本:',xml.statusText);
            //测试发送body数据,需要设置请求头Content-Type(告知服务器数据格式,服务器用对应的方式解析)
            xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            xml.send('name=name1; pwd=123');
    </script>
</body>
</html>

 django-views.py函数处理

from django.shortcuts import HttpResponse
from django.views import View
import json
# Create your views here.
class AjaxOrnginal(View):
    def get(self,request):
        return render(request, 'ajax_templates/original_xmlHttpRequest.html')
    def post(self, request):
        print(request.POST)
        ret = {'code': True, 'data': None}
        # return HttpResponse(json.dumps(ret))
        return HttpResponse(json.dumps(ret),status=404, reason="Oh Fuck ! EveryThing is None")

 2、兼容IE老版本XmlHttpRequest对象 

<h1>XMLHttpRequest - Ajax请求</h1> <input type="button" onclick="XmlGetRequest();" value="Get发送请求" /> <input type="button" onclick="XmlPostRequest();" value="Post发送请求" /> <script src="/statics/jquery-1.12.4.js"></script> <script type="text/javascript"> function GetXHR(){ var xhr = null; if(XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject("Microsoft.XMLHTTP"); return xhr; function XhrPostRequest(){ var xhr = GetXHR(); // 定义回调函数 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ // 已经接收到全部响应数据,执行以下操作 var data = xhr.responseText; console.log(data); // 指定连接方式和地址----文件方式 xhr.open('POST', "/test/", true); // 设置请求头 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8'); // 发送请求 xhr.send('n1=1;n2=2;'); function XhrGetRequest(){ var xhr = GetXHR(); // 定义回调函数 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ // 已经接收到全部响应数据,执行以下操作 var data = xhr.responseText; console.log(data); // 指定连接方式和地址----文件方式 xhr.open('get', "/test/", true); // 发送请求 xhr.send(); </script> </body>

"伪"AJAX

由于HTML标签的iframe标签具有局部加载内容的特性,所以可以使用其来伪造Ajax请求。

        <form action="/ajax_original/" method="post" target="ifra">
            <iframe name="ifra" src="https://www.baidu.com"></iframe>
            <input type="text" name="username">
            <input type="text" name="email">
            <input type="submit" value="伪Ajax提交">
        </form>

ifram对象为一个小的新html标签窗口,获取ifram对象的内容需要从这个子窗口document对象

$("#ifra").contents().find('body').text()

试用场景:

普通字符串、字典数据:优先使用用Ajax,其次XMLHttpRequest,再次考虑iframe

上传文件的三种方法

1、原生XMLHttpRequest ---- 上传文件

依赖: new FormData(); 对象,

html前端:XMLHttpRequest() 
<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title>Title</title>
        <script src="/static/jquery-2.2.3.min.js"></script>
    <script src="/static/jquery.cookie.js"></script>
</head>
    <div class="upload_div">
            <input type="file" value="上传" class="file" id="original_upload_input" name="file1">
    </div>
        <input type="button" onclick="upLoadFile();" value="上传">
    </div>
    <script>
        function upLoadFile(){
            let file_obj = document.getElementById('original_upload_input').files[0];
            let fd = new FormData();
            fd.append('username', 'root');
            fd.append('file1', file_obj);
            let xhr = new XMLHttpRequest();
            xhr.open('post', '/xhr_upload/');
            xhr.setRequestHeader('X-CSRFtoken',$.cookie('csrftoken') ); //设置请求头
            xhr.onreadystatechange = function () {                     //状态改变
                console.log('xml 实例readyState状态改变:',xhr.readyState);
                if (xhr.readyState === 4) {
                    console.log('收到返回值字符串:',xhr.responseText);
            xhr.send(fd);
    </script>
</body>
</html>

django views函数后台处理

from django.shortcuts import render
from django.shortcuts import HttpResponse
from django.views import View
import json
# Create your views here.
class XhrUpload(View):
    def get(self,request):
        return render(request, 'ajax_templates/original_upload.html')
    def post(self, request):
        file = request.FILES.get('file1')
        with open(file.name, 'wb') as f:
            for item in file.chunks():
                f.write(item)
        return HttpResponse('upload ok')

2、jQuery ---- 上传文件

仍然使用new FormData(); 对象

需增加特殊设置:

 processData:false,  // tell jQuery not to process the data:不要转义数据
contentType:false, // tell jQuery not to set contentType:不要设置请求头
            let file_obj = document.getElementById('original_upload_input').files[0];
            // 仍然要依赖于FormData()对象,并且要设置processData和contentType参数
            let fd = new FormData();
            fd.append('username', 'root');
            fd.append('file1', file_obj);
            //使用ajax 上传数据
            $.ajax({
                url:'/xhr_upload/',
                type: 'POST',
                data:fd,
                headers:{'X-CSRFtoken': $.cookie('csrftoken')},
                processData:false,  // tell jQuery not to process the data:不要转义数据
                contentType:false,  // tell jQuery not to set contentType:不要设置请求头
                success:function (arg,a1,a2) {
                    console.log(arg);   //返回字符串
                    console.log(a1);    //状态文本
                    console.log(a2);    //XHR对象

 3、ifram 上传文件

  优势:可以将上传后的返回值信息做成图片预览等功能。

<form action="/xhr_upload/" method="post" enctype="multipart/form-data" target="ifram1">
     {% csrf_token %}
<iframe id="ifram1" name="ifram1"></iframe>
<input type="file" name="file1">
<input type="submit" onclick="iframSubmit();" value="Form提交(target=ifram1)">
</form>

ajax 从iframe 加载成功后 获取返回值:$('#iframe标签').load(function(){xxxxxx; ......; })

        function iframSubmit() {
            //上传成功,iframe标签加载返回值成功,从标签中获取元素
            $('#ifram1').load(function () {
                //获取返回值,
                let recv = $('#ifram1').contents().find('body').text();
                console.log(recv);

  django views.py函数处理

class XhrUpload(View):
    def get(self,request):
        return render(request, 'ajax_templates/original_upload.html')
    def post(self, request):
        file = request.FILES.get('file1')
        with open(file.name, 'wb') as f:
            for item in file.chunks():
                f.write(item)
        ret = HttpResponse('upload ok')
        ret['X-Frame-Options'] = 'ALLOW'
        return ret

  图片预览版本iframe:

步骤说明:当iframe标签加载成功.onload(function(){})时,获取iframe标签中 的返回值。返回值中包含图片uri, 通过组合'/' + uri 获取图片路径,创建img标签,在浏览器中增加此标签实现图片自动get展示

<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/jquery-2.2.3.min.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <style>
        .hide{
            display: block;}
    </style>
</head>
        <form action="/iframe_upload/" method="post" enctype="multipart/form-data" target="ifram1">
            {% csrf_token %}
            <iframe id="ifram1" name="ifram1" class="hide"></iframe>
            <input type="file" name="file1">
            <input type="submit" onclick="iframSubmit();" value="Form提交(target=ifram1)">
        </form>
    </div>
    <div id="preview_div"></div>
    <script>
            //上传成功,iframe标签加载返回值成功,从标签中获取元素
            $('#ifram1').load(function () {
                //获取返回值,返回的是json.dumps()序列化的字符串也可以获取
                let recv = $('#ifram1').contents().find('body').text();
                let data = JSON.parse(recv);
                console.log(data);
                //获取返回的图片URI,并生成图片标签
                let imgTag = document.createElement('img');
                imgTag.src = "/" + data.uri;
                //清空已有img图片标签
                $('#preview_div').empty();
                //添加dom元素
                $('#preview_div').append(imgTag);
    </script>
</body>
</html>

  如需选择文件后自动上传,js绑定onchange事件实现上传:

<meta charset="UTF-8"> <title>Title</title> <script src="/static/jquery-2.2.3.min.js"></script> <script src="/static/jquery.cookie.js"></script> <style> .hide{ display: none;} </style> </head> <form id="upload_form" action="/iframe_upload/" method="post" enctype="multipart/form-data" target="ifram1"> {% csrf_token %} <iframe id="ifram1" name="ifram1" class="hide"></iframe> <input type="file" name="file1" onchange="upLoadFile()"> <input type="submit" onclick="iframSubmit();" value="Form提交(target=ifram1)"> </form> </div> <div id="preview_div"></div> <script> //选择文件后自动上传 function upLoadFile(){ $('#upload_form').submit() //上传成功,iframe标签加载返回值成功,从标签中获取元素 $('#ifram1').load(function () { //获取返回值,返回的是json.dumps()序列化的字符串也可以获取 let recv = $('#ifram1').contents().find('body').text(); let data = JSON.parse(recv); console.log(data); //获取返回的图片URI,并生成图片标签 let imgTag = document.createElement('img'); imgTag.src = "/" + data.uri; //清空已有img图片标签 $('#preview_div').empty(); //添加dom元素 $('#preview_div').append(imgTag); </script> </body>
input 标签绑定onchange事件;选择文件后自动上传

  ifram 优势:在所有浏览器都支持,不存在兼容性问题。而XMLHTTPRequest低版本IE浏览器不支持。

   应用案例(点击更换头像):

  说明:input标签 属性title='xxxx' 为设置鼠标移入的时候显示的提示语设置

  原理:input标签100%占用div;并且透明度为0 即肉眼不可见。img标签和<input type="file">标签重叠在底层显示。

<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/jquery-2.2.3.min.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <style>
        .hide{
            display: none;}
        .preview_div{
            position: relative;
            overflow: hidden;
            display: inline-block;
        .head_portrait{
            width: 108px;
        .head_img {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
            opacity: 0;
            z-index: 2;
    </style>
</head>
        <form id="upload_form" action="/iframe_upload/" method="post" enctype="multipart/form-data" target="ifram1">
            {% csrf_token %}
            <iframe id="ifram1" name="ifram1" class="hide"></iframe>
            <div id="preview_div" class="preview_div">
                <img id="head_portrait" class="head_portrait" src="/static/images/default.jpg" >
                <input class="head_img" type="file" name="file1" title="点击上传头像" onchange="upLoadFile()" >
            </div>
            <input type="submit" onclick="iframSubmit();" value="Form提交(target=ifram1)">
        </form>
    </div>
    <script>
        //选择文件后自动上传
        function upLoadFile(){
            $('#upload_form').submit()
        //上传成功,iframe标签加载返回值成功,从标签中获取元素
        $('#ifram1').load(function () {
            //获取返回值,返回的是json.dumps()序列化的字符串也可以获取
            let recv = $('#ifram1').contents().find('body').text();
            let data = JSON.parse(recv);
            console.log(data);
            // 在原有图片上面更新
            let uri = "/" + data.uri;
            $('#head_portrait').attr('src', uri);
    </script>
</body>
</html>

  -----------------------------------------------------------

 图片验证码案例

  验证码图片生成

  工具:pip3 install pillow 

utils/check_code.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import random
# pip3 install pillow
from PIL import Image, ImageDraw, ImageFont, ImageFilter
_letter_cases = "abcdefghjkmnpqrstuvwxy"  # 小写字母,去除可能干扰的i,l,o,z
_upper_cases = _letter_cases.upper()  # 大写字母
_numbers = ''.join(map(str, range(3, 10)))  # 数字
init_chars = ''.join((_letter_cases, _upper_cases, _numbers))
def create_validate_code(size=(120, 30),
                         chars=init_chars,
                         img_type="GIF",
                         mode="RGB",
                         bg_color=(255, 255, 255),
                         fg_color=(0, 0, 255),
                         font_size=18,
                         font_type="Monaco.ttf",
                         length=4,
                         draw_lines=True,
                         n_line=(1, 2),
                         draw_points=True,
                         point_chance=2):
    @todo: 生成验证码图片
    @param size: 图片的大小,格式(宽,高),默认为(120, 30)
    @param chars: 允许的字符集合,格式字符串
    @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG
    @param mode: 图片模式,默认为RGB
    @param bg_color: 背景颜色,默认为白色
    @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF
    @param font_size: 验证码字体大小
    @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf
    @param length: 验证码字符个数
    @param draw_lines: 是否划干扰线
    @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效
    @param draw_points: 是否画干扰点
    @param point_chance: 干扰点出现的概率,大小范围[0, 100]
    @return: [0]: PIL Image实例
    @return: [1]: 验证码图片中的字符串
    width, height = size  # 宽高
    # 创建图形
    img = Image.new(mode, size, bg_color)
    draw = ImageDraw.Draw(img)  # 创建画笔
    def get_chars():
        """生成给定长度的字符串,返回列表格式"""
        return random.sample(chars, length)
    def create_lines():
        """绘制干扰线"""
        line_num = random.randint(*n_line)  # 干扰线条数
        for i in range(line_num):
            # 起始点
            begin = (random.randint(0, size[0]), random.randint(0, size[1]))
            # 结束点
            end = (random.randint(0, size[0]), random.randint(0, size[1]))
            draw.line([begin, end], fill=(0, 0, 0))
    def create_points():
        """绘制干扰点"""
        chance = min(100, max(0, int(point_chance)))  # 大小限制在[0, 100]
        for w in range(width):
            for h in range(height):
                tmp = random.randint(0, 100)
                if tmp > 100 - chance:
                    draw.point((w, h), fill=(0, 0, 0))
    def create_strs():
        """绘制验证码字符"""
        c_chars = get_chars()
        strs = ' %s ' % ' '.join(c_chars)  # 每个字符前后以空格隔开
        font = ImageFont.truetype(font_type, font_size)
        font_width, font_height = font.getsize(strs)
        draw.text(((width - font_width) / 3, (height - font_height) / 3),
                  strs, font=font, fill=fg_color)
        return ''.join(c_chars)
    if draw_lines:
        create_lines()
    if draw_points:
        create_points()
    strs = create_strs()
    # 图形扭曲参数
    params = [1 - float(random.randint(1, 2)) / 100,
              1 - float(random.randint(1, 10)) / 100,
              float(random.randint(1, 2)) / 500,
              0.001,
              float(random.randint(1, 2)) / 500
    img = img.transform(size, Image.PERSPECTIVE, params)  # 创建扭曲
    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)  # 滤镜,边界加强(阈值更大)
    return img, strs

  django views.py 后端使用如上check_code.py 生成图片

from django.shortcuts import render
from django.shortcuts import HttpResponse
from django.views import View
import json
import os
from io import BytesIO
from utils.check_code import create_validate_code
# Create your views here.
# 生成图片验证码并将对应字符串存储到session中
class CheckCodeImage(View):
    def get(self, request):
        # from io import BytesIO ,开辟内存空间,用来存储字节流
        stream = BytesIO()
        # 使用自定义函数创建验证码图片&字符串
        img, code_str = create_validate_code()
        # 将图片保存到内存的字节流中
        img.save(stream, 'PNG')
        # 将验证码字符串保存到session中
        request.session['CheckCode'] = code_str
        # 将图片字节流返回给客户端:HttpResponse()可以返回字节
        return HttpResponse(stream.getvalue())
# 登录请求校验验证码
class LoginCaptch(View):
    def get(self, request):
        return render(request, 'ajax_templates/login_iframe_captcha.html')
    def post(self, request):
        if request.session['CheckCode'] == request.POST.get('CheckCode'):
            print('验证码正确')
        else:
            print('验证码错误:',request.session['CheckCode'].upper(),request.POST.get('CheckCode'))
        return render(request, 'ajax_templates/login_iframe_captcha.html')

  urls.py

    url(r'iframe_login_captch/', ajax_views.LoginCaptch.as_view()),
    url(r'^check_code.html$', ajax_views.CheckCodeImage.as_view()),

  login_iframe_captcha.html 前端展示验证码图片

  点击图片本身,自动刷新验证码:请求url增加? 浏览器检测到url变化,重新发起请求,后端处理不变。

  login_iframe_captcha.html

<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .login{
            display: inline-block;
    </style>
</head>
    <form action="/iframe_login_captch/" method="post">
        {% csrf_token %}
        <div class="login">
            <div class="form-group">
                <label for="username">用户名</label>
                <input type="text" class="form-control" id="username" name="username" placeholder="请输入用户名" >
            </div>
            <div class="form-group">
                <label for="password">密码</label>
                <input type="password" class="form-control" id="password" name="pwd" placeholder="请输入密码" >
            </div>
            <div class="form-group">
                <label for="captcha">验证码</label>
                <input type="text" class="form-control" id="captcha"  name="CheckCode" placeholder="验证码" >
            </div>
            <button type="submit">登 陆</button>
        </div>
    </form>
        <img src="/check_code.html" onclick="changeCheckCode(this)">
    </div>
    <script>
        //点击图片改变图片验证码: 请求url增加? 浏览器检测到url变化,重新发起请求,后端处理不变。
        function changeCheckCode(ths) {
            ths.src = ths.src + "?";
    </script>
</body>
</html>

---------------------------------------------------------------------