一 Ajax簡介 1.簡介 AJAX(Asynchronous Javascript And XML)翻譯成中文就是“非同步的Javascript和XML”。即使用Javascript語言與伺服器進行非同步交互,傳輸的數據為XML(當然,傳輸的數據不只是XML,現在更多使用json數據)。 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 提交數據的方式了。瀏覽器的原生