Django之Ajax
1.Ajax简介
AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML,现在更多使用json数据)。
AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)
AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。
a.同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
b.异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
AJAX除了异步的特点外,还有一个就是:浏览器页面局部刷新;(这一特点给用户的感受是在不知不觉中完成请求和响应过程
示例
通过ajax访问登陆页面,将获取到的用户名跟密码提交到后台进行校验,校验后跳转到home页面
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{#<form action="" method="post">#}
{# 用户名:#}
{# <input type="text" id="username">#}
{# 密码:#}
{# <input type="password" id='password'>#}
{# <button id="sub">提交</button>#}
{# <span class="error"></span>#}
{#</form>#}
<h1>用户登录</h1>
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
<button id="sub">提交</button>
<span class="error"></span>
</body>
<script src = "https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
$('#sub').click(function () {
$.ajax({
url:'{% url 'login' %}',
type:'post',
data:{username:$('#username').val(),password:$('#password').val()},
success:function (res) {
if(res==='1'){
$('.error').text('登陆成功');
location.href='/home/'
}else{
$('.error').text('用户名或密码错误')
}
}
})
})
</script>
</html>
urls.py路由分发
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login/', views.login,name='login'),
url(r'^home/', views.home),
]
views.py视图函数
def login(request):
if request.method == 'GET':
return render(request,'login.html')
else:
print(request.POST)
uname = request.POST.get('username')
pwd = request.POST.get('password')
if uname == 'alex' and pwd == 'alex123':
return HttpResponse('1')
else:
return HttpResponse('0')
def home(request):
return HttpResponse('home')
启动项目后查看运行效果,发现页面不刷新,实现了局部刷新
Ajax常见应用场景
搜索引擎根据用户输入的关键字,自动提示检索关键字.注册时候的用户名查重就使用了Ajax技术,当文件框发生了输入变化,使用Ajax技术向服务区发送一个请求,然后服务器会把查询到的结果响应给浏览器,最后把后端返回的结果展示出来
a.整个过程中页面没有刷新,只是刷新了页面中的局部位置而已
b.当请求发出后,浏览器还可以进行其他操作,无需等待服务器的响应
当输入用户名后,把光标移动到其他表单项上时,浏览器会使用Ajax技术向服务器发出请求,服务器查询名为lemontree7777777的用户是否存在,最终服务器返回true表示名为lemontree7777777的用户已经存在了,浏览器在得到结果后显示'用户名已经被注册'
整个过程页面没有刷新,只是局部刷新了,在请求发出后,浏览器不用等待服务器响应结果就可以进行其他操作
Ajax的优缺点
优点:
1.Ajax使用javascript技术向服务器发送异步请求
2.Ajax请求无需刷新整个页面
3.服务器响应内容不再是整个页面,而是整个页面中的部分内容,所以Ajax的性能高
缺点:
1.Ajax并不适合于所有场景,很多时候还是要使用同步交互
2.Ajax虽然提高了用户体验.但是无形中向服务器发送的请求次数增多了,导致服务器的压力增大
3.Ajax是在浏览器中使用JavaScript技术完成的,所以还需要处理浏览器兼容问题
2.Ajax请求设置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。
部分源码
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:一次性令牌在完成他们的工作后将被销毁,比较安全。
...等等吧,还有很多其他的。
设置csrf_token
方式1
通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送
#form表单类型
<form action="" method="post">
{% csrf_token %}
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
<button id="sub">提交</button>
<span class="error"></span>
</form>
#非form表单类型
<h1>用户登录</h1>
{% csrf_token %}
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
<button id="sub">提交</button>
<span class="error"></span>
$('#sub').click(function(){
$.ajax({
url: '{% url "login" %}',
type: 'post',
data: {username: $('#username').val(), password: $('#password').val(), csrfmiddlewaretoken:$('[name=csrfmiddlewaretoken]').val()},
success: function (res) {
console.log(res);
if (res === '1'){
$('.error').text('登陆成功');
location.href = '/home/'
} else {
$('.error').text('用户名或密码错误')
}
}
})
})
方式2:
# csrfmiddlewaretoken:'{{ csrf_token }}
$('#sub').click(function(){
$.ajax({
url: '{% url "login" %}',
type: 'post',
data: {
username: $('#username').val(),
password: $('#password').val(), csrfmiddlewaretoken:'{{ csrf_token }}'},
success: function (res) {
console.log(res);
if (res === '1'){
$('.error').text('登陆成功');
location.href = '/home/'
} else {
$('.error').text('用户名或密码错误')
}
}
})
})
方式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就可以这样加
})
$('#sub').click(function () {
$.ajax({
url: '{% url 'login' %}',
type: 'post',
data: {username: $('#username').val(), password: $('#password').val()},
headers: {'X-CSRFToken': $.cookie('csrftoken'),},
success: function (res) {
if (res === '1') {
$('.error').text('登陆成功');
location.href = '/home/'
} else {
$('.error').text('用户名或密码错误')
}
}
})
})
form表单设置csrf_token
<form action="" method="post">
{% csrf_token %}
// form表单里面加上这个标签,模板渲染之后就是一个input标签,type=hidden name=csrfmiddlewaretoken value='asdfasdfasdf'
用户名: <input type="text" name="username">
密码: <input type="password" name="password">
<input type="submit">
</form>
3.Ajax文件上传
基于form表单的文件上传
模板部分
<form action="" method="post" enctype="multipart/form-data"> #上面说的其他两种contenttype都是键值的形式发送数据,这种form_data的格式一般是把大数据一段一段隔开的
用户名 <input type="text" name="user">
头像 <input type="file" name="avatar">
#如果不用form_data格式来发,那么默认的是urlencoded的格式,这个标签的数据会组成avatar:文件名字来进行发送
<input type="submit">
</form>
视图部分
def index(request):
print(request.body) # 原始的请求体数据
print(request.GET) # GET请求数据
print(request.POST) # POST请求数据
print(request.FILES) # 上传的文件数据
return render(request,"index.html")
unload函数
def upload(request):
if request.method == 'GET':
return render(request,'upload.html')
else:
print(request.POST)
username = request.POST.get('user')
file_obj = request.FILES.get('file_obj')
#获得文件数据对象
print('>>>',file_obj,type(file_obj))
#>>> 1.txt <class'django.core.files.uploadedfile.InMemoryUploadedFile'>一个文件对象,可以理解为一个文件句柄
file_name = file_obj.name #1.txt
print(file_name)
# 将数据写到文件里面,需要名字,需要数据
with open(file_name,'wb') as f:
#直接把文件名字放这里,那么文件将直接生成在django的整个项目目录下,因为django配置的系统搜索的根路径就是咱们的项目文件夹路径,那个BASE_DIR,一般我们需要自己建立一个文件夹专门存放上传的文件
#所以需要我们自己来拼接一个路径放到这里,os.path.join(settings.BASE_DIR,'media','img',file_name)
# f.write() #不能一下写进去,占用的内容太多,要一点一点写
for data in file_obj: #读数据
f.write(data)
#每次读取的data不是固定长度的,和读取其他文件一样,每次读一行,识别符为
,遇到这几个符号就算是读了一行
for chunks in file_obj.chunks():
f.write(chunks)
#chunks()默认一次返回大小为经测试为65536B,也就是64KB,最大为2.5M,是一个生成器
通过js寻找文件对象
基于Ajax的文件上传
upload.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
文件: <input type="file" name="file">
<button id="sub">提交</button>
<span class="error"></span>
</form>
</body>
<script src = "https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
$('#sub').click(function () {
var formdata = new FormData();
var uname = $('#username').val();
var pwd = $('#password').val();
var file_obj = $('[type=file]')[0].files[0]; //js获取文件对象
var csrfmiddlewaretoken = {{ csrf_token }};
formdata.append('username',uname);
formdata.append('password',pwd);
formdata.append('file',file_obj);
formdata.append('csrfmiddlewaretoken',csrfmiddlewaretoken);
$.ajax({
url: '{% url "login" %}',
type: 'post',
data:formdata,
processData:false, //必须写
contentType:false, //必须写
success: function (res) {
console.log(res);
if (res === '1') {
$('.error').text('登陆成功');
location.href = '/home/'
} else {
$('.error').text('用户名或密码错误')
}
}
})
})
</script>
</html>
views.py
def upload(request):
if request.method == 'GET':
return render(request,'upload.html')
else:
print(request.POST)
print(request.FILES)
uname = request.POST.get('username')
pwd = request.POST.get('password')
file_obj = request.FILES.get('file') #文件对象
print(file_obj.name) #文件名称
with open(file_obj.name,'wb') as f:
for i in file_obj:
f.write(i)
return HttpResponse('okk')
4.关于json
1.什么是json?
-
JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)
-
JSON 是轻量级的文本数据交换格式
-
JSON 独立于语言 *
-
JSON 具有自我描述性,更易理解
-
JSON 使用 JavaScript 语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。
json数据类型和python数据类型的对比:
object和python的dict类型是差不多的,但是要求里面必须是双引号,string和list、tuple等也是一样的,都是双引号。python中的datetime等时间日期类型是不能进行json序列化的,因为json没有对应的格式,上面的这几种数据类型虽然进行json.dumps序列化之后都是个字符串,但是也是有格式的
前端ajax拿到后端返回的一个python的json模块序列化之后的一个json字符串,那么js通过自己的json接口,将接受到的json字符串来反序列化为js自己语言能够识别的数据类型,然后再进行操作。
相当于我有一个json方法,你有一个json方法,你给我发数据必须是json字符串的格式,那么你就需要将你的数据类型序列化为json的字符串,那么序列化的时候,就把你的数据序列化为了符合json标准的字符串,然后我接收到这个字符串之后,我通过我的json方法,将数据转换为我的语言支持的数据类型。在进行反序列化的时候,如果你的字符串不符合json的格式,那么反序列化的时候就会报错,所以只要你是通过json序列化成的字符串,都是能够json反序列化的,因为json序列化的时候,就把你的数据改为了符合json标准的字符串形式,例如:里面的单引号,序列化后变成了双引号。
合格的json对象
["one", "two", "three"]
{ "one": 1, "two": 2, "three": 3 } #这就是一个json的object类型,符合json的标准格式,就可以通过dumps来进行序列化
{"names": ["张三", "李四"] }
[ { "name": "张三"}, {"name": "李四"} ]
普通字符串和json字符串,在进行序列化的时候的区别
import json
# s = "{'name':'chao','age':18}"
#普通字符串,每加引号的没问题,加了引号的,必须是双引号才能使用json.loads()。
s = '{"name":"chao","age":18}' #json字符串,里面必须是双引号
ret = json.loads(s)
print(ret)
print(ret['name'])
js的stringify与parse方法
JavaScript中关于JSON对象和字符串转换的两个方法:
JSON.parse(): 用于将一个 JSON 字符串转换为 JavaScript 对象
JSON.parse('{"name":"chao"}');
JSON.parse('{name:"chao"}') ; // 错误
JSON.parse('[18,undefined]') ; // 错误
JSON.stringify(): 用于将 JavaScript 值转换为 JSON 字符串。
JSON.stringify({"name":"chao"})
前后端使用json
方式一
jsontest.htnl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户登录</h1>
{% csrf_token %}
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
<button id="sub">提交</button>
<span class="error"></span>
</body>
<script src = "https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
$('#sub').click(function () {
var uname = $('#username').val();
var pwd = $('#password').val();
$.ajax({
url:'{% url "jsontest" %}',
type:'post',
data:{username:uname,password:pwd,csrfmiddlewaretoken:'{{ csrf_token }}'},
success:function (res) {
console.log(res,typeof res);
var res = JSON.parse(res);
if(res.status === 1000){
location.href='/home/'
}else{
$('.error').text(res.msg)
}
}
})
})
</script>
</html>
views.py
import json
def jsontest(request):
'''
状态码:
1000:登陆成功
1001:登陆失败
:param request:
:return:
'''
if request.method == 'GET':
return render(request,'jsontest.html')
else:
uname = request.POST.get('username')
pwd = request.POST.get('password')
ret_data = {'status':None,'msg':None}
print('>>>>',request.POST)
if uname == 'alex' and pwd == 'alex123':
ret_data['status'] = 1000
ret_data['msg'] = '登陆成功'
else:
ret_data['status'] = 1001
ret_data['msg'] = '登陆失败'
ret_data_json = json.dumps(ret_data,ensure_ascii=False)
return HttpResponse(ret_data_json)
方式二
jsontest.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户登录</h1>
{% csrf_token %}
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
<button id="sub">提交</button>
<span class="error"></span>
</body>
<script src = "https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
$('#sub').click(function () {
var uname = $('#username').val();
var pwd = $('#password').val();
$.ajax({
url:'{% url "jsontest" %}',
type:'post',
data:{username:uname,password:pwd,csrfmiddlewaretoken:'{{ csrf_token }}'},
success:function (res) {
console.log(res,typeof res);
if(res.status === 1000){
location.href='/home/'
}else{
$('.error').text(res.msg)
}
}
})
})
</script>
</html>
views.py
import json
def jsontest(request):
'''
状态码:
1000:登陆成功
1001:登陆失败
:param request:
:return:
'''
if request.method == 'GET':
return render(request,'jsontest.html')
else:
uname = request.POST.get('username')
pwd = request.POST.get('password')
ret_data = {'status':None,'msg':None}
print('>>>>',request.POST)
if uname == 'alex' and pwd == 'alex123':
ret_data['status'] = 1000
ret_data['msg'] = '登陆成功'
else:
ret_data['status'] = 1001
ret_data['msg'] = '登陆失败'
ret_data_json = json.dumps(ret_data,ensure_ascii=False)
return HttpResponse(ret_data_json,content_type='application/json')
方式三
jsonresponse
jsontest.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户登录</h1>
{% csrf_token %}
用户名:<input type="text" id="username">
密码:<input type="password" id='password'>
<button id="sub">提交</button>
<span class="error"></span>
</body>
<script src = "https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
$('#sub').click(function () {
var uname = $('#username').val();
var pwd = $('#password').val();
$.ajax({
url:'{% url "jsontest" %}',
type:'post',
data:{username:uname,password:pwd,csrfmiddlewaretoken:'{{ csrf_token }}'},
success:function (res) {
console.log(res,typeof res);
if(res.status === 1000){
location.href='/home/'
}else{
$('.error').text(res.msg)
}
}
})
})
</script>
</html>
views.py
from django.http import JsonResponse
def jsontest(request):
'''
状态码:
1000:登陆成功
1001:登陆失败
:param request:
:return:
'''
if request.method == 'GET':
return render(request,'jsontest.html')
else:
uname = request.POST.get('username')
pwd = request.POST.get('password')
ret_data = {'status':None,'msg':None}
print('>>>>',request.POST)
if uname == 'alex' and pwd == 'alex123':
ret_data['status'] = 1000
ret_data['msg'] = '登陆成功'
else:
ret_data['status'] = 1001
ret_data['msg'] = '登陆失败'
return JsonResponse(ret_data)
#非字典类型的数据需要给JsonResponse加上 safe=False 参数