09.Django基础七之Ajax
一 ajax简介
1.简介
ajax(asynchronous javascript and xml)翻译成中文就是“异步的javascript和xml”。即使用javascript语言与服务器进行异步交互,传输的数据为xml(当然,传输的数据不只是xml,现在更多使用json数据)。
ajax 不是新的编程语言,而是一种使用现有标准的新方法。
ajax 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)
ajax 不需要任何浏览器插件,但需要用户允许javascript在浏览器上执行。
a.同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
b.异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
ajax除了异步的特点外,还有一个就是:浏览器页面局部刷新;(这一特点给用户的感受是在不知不觉中完成请求和响应过程
2.示例
页面输入两个整数,通过ajax传输到后端计算出结果并返回。
html文件名称为ajax_demo1.html,内容如下
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ajax局部刷新实例</title> </head> <body> <input type="text" id="i1">+ <input type="text" id="i2">= <input type="text" id="i3"> <input type="button" value="ajax提交" id="b1"> <script src="/static/jquery-3.2.1.min.js"></script> <script> $("#b1").on("click", function () { $.ajax({ url:"/ajax_add/", //别忘了加双引号 type:"get", data:{"i1":$("#i1").val(),"i2":$("#i2").val()}, //object类型,键值形式的,可以不给键加引号 success:function (data) { $("#i3").val(data); } }) }) </script> </body> </html>
views.py里面的内容:
def ajax_demo1(request): return render(request, "ajax_demo1.html") def ajax_add(request): #time.sleep(10) #不影响页面发送其他的请求 i1 = int(request.get.get("i1")) i2 = int(request.get.get("i2")) ret = i1 + i2 return jsonresponse(ret, safe=false) #return render(request,'index.html') #返回一个页面没有意义,就是一堆的字符串,拿到了这个页面,你怎么处理,你要做什么事情,根本就没有意义
urls.py里面的内容
urlpatterns = [ ... url(r'^ajax_add/', views.ajax_add), url(r'^ajax_demo1/', views.ajax_demo1), ... ]
启动django项目,然后运行看看效果,页面不刷新
3.ajax常见应用情景
搜索引擎根据用户输入的关键字,自动提示检索关键字。
还有一个很重要的应用场景就是注册时候的用户名的查重。
其实这里就使用了ajax技术!当文件框发生了输入变化时,使用ajax技术向服务器发送一个请求,然后服务器会把查询到的结果响应给浏览器,最后再把后端返回的结果展示出来。
a.整个过程中页面没有刷新,只是刷新页面中的局部位置而已!
b.当请求发出后,浏览器还可以进行其他操作,无需等待服务器的响应!
当输入用户名后,把光标移动到其他表单项上时,浏览器会使用ajax技术向服务器发出请求,服务器会查询名为lemontree7777777的用户是否存在,最终服务器返回true表示名为lemontree7777777的用户已经存在了,浏览器在得到结果后显示“用户名已被注册!”。
a.整个过程中页面没有刷新,只是局部刷新了;
b.在请求发出后,浏览器不用等待服务器响应结果就可以进行其他操作;
4.ajax的优缺点
优点:
1.ajax使用javascript技术向服务器发送异步请求;
2.ajax请求无须刷新整个页面;
3.因为服务器响应内容不再是整个页面,而是页面中的部分内容,所以ajax性能高;
5.作业
写一个登陆认证页面,登陆失败不刷新页面,提示用户登陆失败,登陆成功自动跳转到网站首页。
login.html,内容如下:
{% load static %} <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>title</title> </head> <body> <div> 用户名:<input type="text" id="username"> 密码:<input type="text" id="pwd"> {% csrf_token %} <button id="sub">提交</button> <span style="color: red;font-size: 12px;" id="error"></span> </div> <script src="{% static 'jquery.js' %}"></script> <script> $('#sub').click(function () { $.ajax({ url:"{% url 'login' %}", type:'post', data:{username:$('#username').val(),pwd:$('#pwd').val(),csrfmiddlewaretoken:$('[name=csrfmiddlewaretoken]').val()}, success:function (data) { data = json.parse(data); console.log(data,typeof data); if (data['status']){ location.href=data['home_url']; } else { $('#error').text('用户名或者密码错误!') } } }) }) </script> </body> </html>
base.html,内容如下:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>title</title> </head> <body> <h1> 欢迎来到xxx官网 </h1> </body> </html>
urls.py,内容如下
url(r'^login/', views.login,name='login'), url(r'^home/', views.home,name='home'),
views.py,内容如下
def login(request): res_dict = {'status':none,'home_url':none} if request.method == 'get': return render(request,'login.html') else: uname = request.post.get('username') pwd = request.post.get('pwd') user_obj = models.userinfo.objects.filter(name=uname,password=pwd).exists() import json if user_obj: res_dict['status'] = true res_dict['home_url'] = reverse('home') res_json_dict = json.dumps(res_dict) return httpresponse(res_json_dict) #直接回复字典格式是不可以的,必须转换成json字符串,转换成普通字符串也是不行的,因为前端需要对json进行反序列获得这个字典,在通过字典的形式来操作数据。 else: res_dict['status'] = false res_json_dict = json.dumps(res_dict) return httpresponse(res_json_dict) # 如果你就是不使用jsonresponse的话,也可以给httpresponse添加一个参数,content_type='application/json',那么前端ajax拿到数据之后,也是不需要反序列化的,ajax的回调函数就收到的就是一个反序列化之后的一个对象,因为ajax接受到数据后,通过这个data_type或者content_type发现你发送来的是个json格式的数据,那么ajax内容就自动将这个数据反序列化得到了js的数据对象,然后通过对象可以直接操作数据。 # return httpresponse(res_json_dict,data_type='application/json') # return jsonresponse(res_dict) def home(request): return render(request,'base.html')
还有一点注意一下,如果你想通过ajax来删除表格中某条记录,并且ajax里面的url不写死的情况下(url反向解析),那么就需要下面这种方式,实现url里面参数的动态:
还有一个细节要注意:
并且删除一条数据的时候,后端删除成功之后,你通过后端给你的返回值判断后端是否删除成功,如果删除成功,你有两种方式来删除前端页面的对应一行的记录,1:刷新页面,2:如果不让刷新页面,那么你就需要找到你点击的这个按钮的那一行的tr标签,通过dom操作把它删除
ajax里面写$(this)时要注意的问题:还有一点注意,如果你添加某些dom对象的时候,如果你想在不刷新页面的情况下来添加这个对象,那么你要注意,如果这个对象也需要绑定事件的话,你需要用on来给和他相同的标签对象来绑定事件。
在这里补充个事情:
settings配置文件里面加上下面这句话,意思是说,告诉django,如果别人请求我的路径的时候,你不要自己处理别人输入的路径最后面的/了,如果这个值为true,而我们假如写了一个url为url('^index/',views.test),如果用户输入的时127.0.0.1:8000/index的话,django会让浏览器重新再发一次请求,并且在这个路径后面加上/,也就成了127.0.0.1:8000/index/,此时和我们的url就能匹配上了,因为我们的url正则写的就加了/,如果你将下面这个值设置成false,那么django就不会自动帮你做这个事情了,那么用户在输入127.0.0.1:8000/index,没有最后那个斜杠的路径时,就无法和我们的url正则匹配上了,所以就找不到url了,就会报错,但是注意,django只能帮你重定向让浏览器再发一个get请求,如果你是post请求(非get请求),django就没有办法了,他还是帮你重新定向发送get请求,不能满足你的需求,所以如果你用post方法提交数据的时候,就像上面这个ajax里面的那个url写的必须和你后端配置的那个url对应好,所以别忘了如果你后端url上url('^index/',views.test)这个index后面加了/,那么你写ajax往这个路径下提交数据的时候,ajax里面的url参数后面别忘了写上/,让这个url和后端url正则规则对应好。
二 ajax的使用
1.基于jquery的实现
看代码:
<button class="send_ajax">send_ajax</button> <script> $(".send_ajax").click(function(){ $.ajax({ url:"/handle_ajax/", type:"post", data:{username:"chao",password:123}, success:function(data){ console.log(data) }, error: function (jqxhr, textstatus, err) { console.log(arguments); }, complete: function (jqxhr, textstatus) { console.log(textstatus); }, statuscode: { '403': function (jqxhr, textstatus, err) { console.log(arguments); }, '400': function (jqxhr, textstatus, err) { console.log(arguments); } } }) }) </script>
2.基于原生js实现
看代码
var b2 = document.getelementbyid("b2"); b2.onclick = function () { // 原生js var xmlhttp = new xmlhttprequest(); xmlhttp.open("post", "/ajax_test/", true); xmlhttp.setrequestheader("content-type", "application/x-www-form-urlencoded"); xmlhttp.send("username=chao&password=123456"); xmlhttp.onreadystatechange = function () { if (xmlhttp.readystate === 4 && xmlhttp.status === 200) { alert(xmlhttp.responsetext); } }; };
3.ajax-服务器-ajax流程图
4.ajax参数
请求参数:
######################------------data---------################ data: 当前ajax请求要携带的数据,是一个json的object对象,ajax方法就会默认地把它编码成某种格式 (urlencoded:?a=1&b=2)发送给服务端;此外,ajax默认以get方式发送请求。 function testdata() { $.ajax("/test",{ //此时的data是一个json形式的对象 data:{ a:1, b:2 } }); //?a=1&b=2 ######################------------processdata---------################ processdata:声明当前的data数据是否进行转码或预处理,默认为true,即预处理;if为false, 那么对data:{a:1,b:2}会调用json对象的tostring()方法,即{a:1,b:2}.tostring() ,最后得到一个[object,object]形式的结果。 ######################------------contenttype---------################ contenttype:默认值: "application/x-www-form-urlencoded"。发送信息至服务器时内容编码类型。 用来指明当前请求的数据编码格式;urlencoded:?a=1&b=2;如果想以其他方式提交数据, 比如contenttype:"application/json",即向服务器发送一个json字符串: $.ajax("/ajax_get",{ data:json.stringify({ a:22, b:33 }), contenttype:"application/json", type:"post", }); //{a: 22, b: 33} 注意:contenttype:"application/json"一旦设定,data必须是json字符串,不能是json对象 views.py: json.loads(request.body.decode("utf8")) ######################------------traditional---------################ traditional:一般是我们的data数据有数组时会用到 :data:{a:22,b:33,c:["x","y"]}, traditional为false会对数据进行深层次迭代;
响应参数:
datatype: 预期服务器返回的数据类型,服务器端返回的数据会根据这个值解析后,传递给回调函数。 默认不需要显性指定这个属性,ajax会根据服务器返回的content type来进行转换; 比如我们的服务器响应的content type为json格式,这时ajax方法就会对响应的内容 进行一个json格式的转换,if转换成功,我们在success的回调函数里就会得到一个json格式 的对象;转换失败就会触发error这个回调函数。如果我们明确地指定目标类型,就可以使用 data type。 datatype的可用值:html|xml|json|text|script 见下datatype实例
三 ajax请求设置csrf_token
方式1
通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送。
$.ajax({ url: "/cookie_ajax/", type: "post", data: { "username": "chao", "password": 123456, "csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val() // 使用jquery取出csrfmiddlewaretoken的值,拼接到data中 }, success: function (data) { console.log(data); } })
方式2
$.ajaxsetup({ data: {csrfmiddlewaretoken: '{{ csrf_token }}' }, });
方式3(后面再说)
通过获取返回的cookie中的字符串 放置在请求头中发送。
注意:需要引入一个jquery.cookie.js插件。
<script src="{% static 'js/jquery.cookie.js' %}"></script> $.ajax({ headers:{"x-csrftoken":$.cookie('csrftoken')}, #其实在ajax里面还有一个参数是headers,自定制请求头,可以将csrf_token加在这里,我们发contenttype类型数据的时候,csrf_token就可以这样加 })
详述csrf(cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:csrf/xsrf。攻击者通过http请求江数据传送到服务器,从而盗取回话的cookie。盗取回话cookie之后,攻击者不仅可以获取用户的信息,还可以修改该cookie关联的账户信息。
所以解决csrf攻击的最直接的办法就是生成一个随机的csrftoken值,保存在用户的页面上,每次请求都带着这个值过来完成校验。
那么django中csrf认证怎么玩的呢?
官方文档中说到,检验token时,只比较secret是否和cookie中的secret值一样,而不是比较整个token。
我又有疑问了,同一次登录,form表单中的token每次都会变,而cookie中的token不便,django把那个salt存储在哪里才能保证验证通过呢。直到看到源码。
def _compare_salted_tokens(request_csrf_token, csrf_token): # assume both arguments are sanitized -- that is, strings of # length csrf_token_length, all csrf_allowed_chars. return constant_time_compare( _unsalt_cipher_token(request_csrf_token), _unsalt_cipher_token(csrf_token), ) def _unsalt_cipher_token(token): """ given a token (assumed to be a string of csrf_allowed_chars, of length csrf_token_length, and that its first half is a salt), use it to decrypt the second half to produce the original secret. """ salt = token[:csrf_secret_length] token = token[csrf_secret_length:] chars = csrf_allowed_chars pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt)) secret = ''.join(chars[x - y] for x, y in pairs) # note negative values are ok return secret
token字符串的前32位是salt, 后面是加密后的token, 通过salt能解密出唯一的secret。
django会验证表单中的token和cookie中token是否能解出同样的secret,secret一样则本次请求合法。
同样也不难解释,为什么ajax请求时,需要从cookie中拿取token添加到请求头中。
cookies hashing:每一个表单请求中都加入随机的cookie,由于网站中存在xss漏洞而被偷窃的危险。 http refer:可以对服务器获得的请求来路进行欺骗以使得他们看起来合法,这种方法不能够有效防止攻击。 验证码:用户提交的每一个表单中使用一个随机验证码,让用户在文本框中填写图片上的随机字符串,并且在提交表单后对其进行检测。 令牌token:一次性令牌在完成他们的工作后将被销毁,比较安全。 ...等等吧,还有很多其他的。
$.ajax({ url: "/cookie_ajax/", type: "post", headers: {"x-csrftoken": $.cookie('csrftoken')}, // 从cookie取csrftoken,并设置到请求头中 data: {"username": "chao", "password": 123456}, success: function (data) { console.log(data); } })
或者用自己写一个getcookie方法:
function getcookie(name) { var cookievalue = null; if (document.cookie && document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jquery.trim(cookies[i]); // does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookievalue = decodeuricomponent(cookie.substring(name.length + 1)); break; } } } return cookievalue; } var csrftoken = getcookie('csrftoken');
每一次都这么写太麻烦了,可以使用$.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) { if (!csrfsafemethod(settings.type) && !this.crossdomain) { xhr.setrequestheader("x-csrftoken", csrftoken); } } });
注意:
如果使用从cookie中取csrftoken的方式,需要确保cookie存在csrftoken值。
如果你的视图渲染的html文件中没有包含 {% csrf_token %},django可能不会设置csrftoken的cookie。
这个时候需要使用ensure_csrf_cookie()装饰器强制设置cookie。
django.views.decorators.csrf import ensure_csrf_cookie @ensure_csrf_cookie def login(request): pass
更多细节详见:djagno官方文档中关于csrf的内容
四 ajax文件上传
请求头contenttype
contenttype指的是请求体的编码类型,常见的类型共有3种:
1 application/x-www-form-urlencoded(看下图)
这应该是最常见的 post 提交数据的方式了。浏览器的原生