09.Django基礎七之Ajax
- 2019 年 10 月 3 日
- 筆記
一 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 提交數據的方式了。瀏覽器的原生