AJAX异步、sweetalert、Cookie和Session初识
一、AJAX的异步示例 1. urls.py
from django.conf.urls import url from apptest import views urlpatterns = [ url(r'^atest/', views.atest), url(r'^ajax1/', views.ajax1), url(r'^ajax2/', views.ajax2), ]
2. atext.HTML
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta http-equiv="content-type" charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <div> <input type="text" id="i1"> <button id="b1">按钮1</button> </div> <div> <input type="text" id="i2"> <button id="b2">按钮2</button> </div> <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script type="text/javascript"> // 点击按钮1给ajax1发送get请求 $("#b1").click(function () { $.ajax({ url: '/ajax1/', type: 'get', success: function (res) { $("#i1").val(res) } }) }); // 点击按钮2给ajax2发送get请求 $("#b2").click(function () { $.ajax({ url: '/ajax2/', type: 'get', success: function (res) { $("#i2").val(res) } }) }); </script> </body> </html>
3.views.py
def atest(request): return render(request, 'atest.html') def ajax1(request): import time time.sleep(3) # 模仿网络堵塞的时候 return HttpResponse('ajax1') def ajax2(request): return HttpResponse('ajax2')
4.结果解析 在页面中首先点击按钮1,然后立刻点击按钮2,由于按钮1的网络堵塞(我用time模块模拟的堵塞),服务端会延迟几秒才返回响应, 如果AJAX的请求是同步的,那么按钮2的请求必须要等到按钮1响应后才会被处理,而从我们这个简单的实例中可以看出, 按钮1没有得到响应的时候,按钮2已经得到响应结果了,因此可以看出AJAX的请求是异步的。 二、Sweetalert示例 1、Bootstrap-sweetalert 项目下载 https://github.com/lipis/bootstrap-sweetalert 使用方法 https://lipis.github.io/bootstrap-sweetalert/ 使用步骤 1. 下载插件 2. 解压 --> 拿到dist目录下的内容 3. 拷贝到Django项目的static文件夹下 4. 在HTML页面上导入sweetalert.css和sweetalert.js 2、示例(此处只是实现了删除的Sweetalert) 1.index页面代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index</title> {% load static %} <link rel="stylesheet" href="{% static 'bootstrap-3.3.7/css/bootstrap.css' %}"> <link rel="stylesheet" href="{% static 'sweetalert/sweetalert.css' %}"> <link rel="stylesheet" href="{% static 'font-awesome-4.7.0/css/font-awesome.css' %}"> <style> div.sweet-alert.showSweetAlert h2 { margin-top: 30px; } </style> </head> <body> <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3" style="margin-top: 70px"> <a href="/logout/">注销</a> <table class="table table-bordered table-striped"> <thead> <tr> <th>序号</th> <th>出版社名称</th> <th>出版社地址</th> <th>操作</th> </tr> </thead> <tbody> {% for publisher in publisher_list %} <tr pid="{{ publisher.id }}"> <td>{{ forloop.counter }}</td> <td>{{ publisher.pname }}</td> <td>{{ publisher.addr }}</td> <td> <button class="btn btn-warning"> <i class="fa fa-pencil"></i> 编辑 </button> <button class="btn btn-danger delete-btn"> <span class="glyphicon glyphicon-remove" aria-hidden="true"></span> 删除 </button> </td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> {% csrf_token %} <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script src="{% static 'bootstrap-3.3.7/js/bootstrap.min.js' %}"></script> <script src="{% static 'sweetalert/sweetalert.js' %}"></script> <script> $('.delete-btn').click(function () { var deleteId = $(this).parent().parent().attr('pid'); console.log(deleteId,typeof deleteId); swal({ title: "确定要删除吗?", text: "删除后无法找回", type: "warning", // success/info/warning/error showCancelButton: true, confirmButtonClass: "btn-danger", confirmButtonText: "删除", cancelButtonText: "取消", showLoaderOnConfirm: true, // 点击确认按钮之后会有一个加载的动画 closeOnConfirm: false }, function () { // 当点击确认按钮的时候会执行这个匿名函数 $.ajax({ url: '/delete_publisher/', type: 'post', data: {id: deleteId, csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val()}, success: function (res) { if (res.code === 0){ // 删除成功 // 在页面上删除当前行数据 var $currentTr = $('[pid=' + deleteId + ']'); // 更新序号 // 1. 先找到当前行后面所有的tr var nextAllTr = $currentTr.nextAll('tr'); // 2. 每一个tr的第一个td 依次 -1 nextAllTr.each(function () { var num = $(this).children().eq(0).text()-1; // 这时候的this是进入循环的那个tr $(this).children().eq(0).text(num); }); // 删掉当前行 $currentTr.remove(); // 弹框提示 swal('删除成功', '准备跑路吧!', 'success'); }else { // 删除失败 swal('删除失败', res.err_msg, 'error'); } } }) }); }) </script> </body> </html>
2.views.py
# 首页 def index(request): publisher_list = Publisher.objects.all() return render(request, 'index.html', {'publisher_list': publisher_list}) # 删除出版社 def delete_publisher(request): delete_id = request.POST.get('id') res = {'code': 0} try: Publisher.objects.filter(id=delete_id).delete() except Exception as e: res['code'] = 1 res['err_msg'] = str(e) return JsonResponse(res)
三、Cookie 1、介绍 1.Cookie的由来 大家都知道HTTP协议是无状态的。 无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。 一句有意思的话来描述就是人生只如初见,对服务器来说,每次的请求都是全新的。 状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。 2.什么是Cookie Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。 简单点说,Cookie就是服务端给浏览器的小纸条!保存在浏览器端的键值对。 3.Cookie的原理 cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。 2、Django中操作Cookie(cookie是基于响应对象进行操作的) 1.设置Cookie rep = HttpResponse(...) rep = render(request, ...) rep.set_cookie(key,value, max_age=秒) rep.set_signed_cookie(key, value, max_age=秒, salt='加密盐',...) 参数: key, 键 value='', 值 max_age=None, 超时时间(常用浏览器使用这个) expires=None, 超时时间(这个参数是针对IE浏览器的) path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问 domain=None, Cookie生效的域名 secure=False, https传输 httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖) 2.获取Cookie set_cookie取值: request.COOKIES --> 是一个大字典 request.COOKIES['key'] 或者 request.COOKIES.get('key', '设置取不到值时默认使用的值') set_signed_cookie取值: request.get_signed_cookie(key='', salt='set_signed_cookie的salt的值', default=获取不到值的时候默认使用的值, max_age=None) 参数: default: 默认值 salt: 加密盐 max_age: 后台控制过期时间 3.删除Cookie def logout(request): rep = redirect("/login/") rep.delete_cookie("user") # 删除用户浏览器上之前设置的usercookie值 return rep 3、Cookie登录验证示例 1.index页面代码就是上面的代码 2.login页面代码
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta http-equiv="content-type" charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Login</title> {% load static %} <link rel="stylesheet" href="{% static 'bootstrap-3.3.7/css/bootstrap.min.css' %}"> </head> <body> <div class="container"> <div class="row"> <div class="col-md-4 col-md-offset-4" style="margin-top: 70px"> <h2 class="text-center">欢迎登录</h2> <form action="" method="post"> {% csrf_token %} <div class="form-group"> <label for="exampleInputUser">用户名</label> <input type="text" class="form-control" id="exampleInputUser" placeholder="username" name="username"> </div> <div class="form-group"> <label for="exampleInputPassword1">密码</label> <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password" name="password"> </div> <div class="checkbox"> <label> <input type="checkbox" name="remember" value="seven"> 七天记住密码 </label> </div> <button type="submit" class="btn btn-success btn-block">登录</button> <p style="color: red;">{{ error_msg }}</p> </form> </div> </div> </div> <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script src="{% static 'bootstrap-3.3.7/js/bootstrap.min.js' %}"></script> </body> </html>
3.views.py
from django.shortcuts import render, HttpResponse, redirect from functools import wraps from django import views from app01.models import * from django.http import JsonResponse # Create your views here. # 用装饰器做cookie登录验证 def check_login(func): @wraps(func) def inner(request, *args, **kwargs): # 先做cookie验证,如果cookie中有我登录的信息,则可以访问指定的页面 if request.COOKIES.get('xm') == 'sb': rep = func(request, *args, **kwargs) return rep else: # 否则,让用户去登录 # 拿到当前访问的url return_url = request.path_info return redirect('/login/?returnUrl={}'.format(return_url)) return inner # 登录 class LoginView(views.View): def get(self, request): return render(request, 'login.html') def post(self, request): username = request.POST.get('username') pwd = request.POST.get('password') is_rem = request.POST.get('remember', None) is_ok = UserInfo.objects.filter(name=username, password=pwd) if is_ok: # 判断是否从其他页面跳转到登录页面,拿到return_url # 如果取不到returnUrl,默认跳转到index页面 return_url = request.GET.get('returnUrl', '/index/') rep = redirect(return_url) # rep就是响应对象 # 判断是否记住密码 if is_rem: # 是就保存七天 rep.set_cookie('xm', 'sb', max_age=60*60*24*7) else: # 不是就不保存 rep.set_cookie('xm', 'sb') # 告诉浏览器在自己本地保存一个键值对 return rep else: return render(request, 'login.html', {'error_msg': '用户名或者密码错误'}) # 首页 @check_login def index(request): publisher_list = Publisher.objects.all() return render(request, 'index.html', {'publisher_list': publisher_list}) # 删除出版社 def delete_publisher(request): delete_id = request.POST.get('id') res = {'code': 0} try: Publisher.objects.filter(id=delete_id).delete() except Exception as e: res['code'] = 1 res['err_msg'] = str(e) return JsonResponse(res) @check_login def home(request): return HttpResponse('home') # 注销 def logout(request): rep = redirect('/login/') rep.delete_cookie('xm') return rep
4、Cookie的属性
name:一个cookie的名称 value:一个cookie的值 domain:可以访问此cookie的域名 path:表示 cookie 影响到的路径,如 path=/。如果路径不能匹配时,浏览器则不发送这个Cookie Size:此cookie大小 httpOnly:如果在COOKIE中设置了httpOnly属性,则通过程序(JS脚本)将无法读取到COOKIE信息,防止XSS攻击产生。只有在http请求头中会有此cookie信息 secure:当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效 expires/Max-Age:设置cookie超时时间。如果设置的值为一个时间,则当到达该时间时此cookie失效。不设置的话默认是session,意思是cookie会和session一起失效,当浏览器关闭(并不是浏览器标签关闭,而是整个浏览器关闭)后,cookie失效
四、Session 1、介绍 cookied的缺点: Cookie保存在浏览器端,不安全 Cookie的长度不能超过4096字节 Session: Session是用于保持状态的基于 Web服务器的方法。Session 允许通过将对象存储在 Web服务器的内存中在整个用户会话过程中保持任何对象。 通俗来说,Session就是保存在服务器端的键值对,Session的键作为Cookie的值存在浏览器中,那么即使被截取了,也只是取到了Session的键, 浏览器发送请求时,服务端就可以根据cookie的传过来的值,找到Session的键,从而去服务器的数据库(文件等)找到对应的值。 注意: Cookie和Session其实是共通性的东西,不限于语言和框架。 2-1、Session的原理 用户名密码通过验证后: 1.生成一个随机字符串(不同用户的随机字符串是不同的) 2.在后端定义一个大字典,字典的key就是上面的随机字符串,值是你设置的值 3.在服务器端的数据库(或者文件等)存储上面定义的字典(值存储的时候,session会自动帮我们加密) 4.把随机字符串当成cookie的值给浏览器返回(浏览器收到的cookie默认键值对是: {"sessionid": 随机字符串}) 5.如果使用python的ORM,这个大字典会默认保存在django_session这张表中
1、session设置值的过程 request.session['name'] = 'xiaoming' # 随机生成一个字符串作为key,key对应的值又是一个字典,字典里面的内容才是你request.session设置的具体值 # session数据 d = { 'qwerioiuytr45421224ew': {'name': 'xiaoming'} } # 过程 import json # 1. 字典序列化成字符串 s1 = json.dumps({'name': 'xiaoming'}) # 2. 加密算法加密一下,得到 10e1dasdasdadasfsgdvf2rji0d2dqdsa # 3. 存到数据库 # 数据库 # session_key session_data expire_date # qwerioiuytr45421224ew 10e1dasdasdadasfsgdvf2rji0d2dqdsa # 然后把这个随机字符串qwerioiuytr45421224ew作为cookie的值发送给浏览器 2、session取值的过程 # request.session.get('name') # 1. 先从请求携带的Cookie数据中拿到随机字符串 :qwerioiuytr45421224ew # 2. 利用session_key去数据库查询,拿到session_data: 10e1dasdasdadasfsgdvf2rji0d2dqdsa # 3. 解密 # 4. 反序列化 --> 得到一个保存当前这次请求的session数据的字典 # 5. 字典.get('name')
2-2、Session安全性比Cookies高
首先cookie是保存在浏览器中的,如果被破解获取到了,是有可能被恶意程序利用的。 那么既然cookie的内容可能会被获取到,那存在cookie中的sessionid(也就是session的key)也是会被获取到并加以利用的, 因此实际上HTTP本身就是不安全,只要是存在cookie中的数据都可以获取到。 那为什么说session比cookie安全呢? 因为session的真正数据是存储在数据库中,那么就算sessionid被获取了,但是session中的数据也不会被恶意程序获取,相对来说确实比cookie安全一点。 例如这样的场景:用户登录后,将用户名作为通信标识。 cookie中是这样设置的:{"username": "zzz"},如果被获取了,那么用户名也被获取到了 session中是这样设置的:先随机生成字符串作为key返回给cookie,前端的cookie是:{"sessionId": "随机字符串"},然后真正的 数据 {"username": "zzz"} 加密后保存在数据库。 如果cookie被获取了,只是获取到了随机key,真正的数据还是没获取到,相对来说安全一些。
3、Django中Session相关方法 常用: # 获取Session中数据 request.session['k1'] request.session.get('k1',None) # 设置Session的数据 request.session['k1'] = 123 request.session.setdefault('k1',123) # 存在则不设置 # 删除指定的Session数据 del request.session['k1'] # 会话session的key request.session.session_key # 将所有Session失效日期小于当前日期的数据删除 request.session.clear_expired() # 删除当前会话的所有Session数据 request.session.delete() # 删除当前的会话数据并删除会话的Cookie。 request.session.flush() 这用于确保前面的会话数据不可以再次被用户的浏览器访问 例如,django.contrib.auth.logout() 函数中就会调用它。 # 设置会话Session和Cookie的超时时间 request.session.set_expiry(value) * 如果value是个整数,session会在指定的秒数后失效。 * 如果value是个datatime或timedelta,session就会在这个时间后失效。 * 如果value是0,用户关闭浏览器session就会失效。 * 如果value是None,session会依赖全局session失效策略。 注意: 失效不等于删除,失效是那个session和cookie不能使用了,但是数据库仍存着session的值, 而删除则是从数据库中把session的值删除了 不常用: # 获取所有的键、值、键值对 request.session.keys() request.session.values() request.session.items() request.session.iterkeys() request.session.itervalues() request.session.iteritems() # 检查会话session的key在数据库中是否存在 request.session.exists("session_key") 4、Session版的登录验证
from django.shortcuts import render, HttpResponse, redirect from functools import wraps from django import views from app01.models import * from django.http import JsonResponse # Create your views here. # 用装饰器做session登录验证 def check_login(func): @wraps(func) def inner(request, *args, **kwargs): # session版 # 验证 session_user = request.session.get('user', None) if session_user: rep = func(request, *args, **kwargs) return rep else: # 否则,让用户去登录 # 拿到当前访问的url return_url = request.path_info return redirect('/login/?returnUrl={}'.format(return_url)) return inner # 登录 class LoginView(views.View): def get(self, request): return render(request, 'login.html') def post(self, request): # session版 username = request.POST.get('username') pwd = request.POST.get('password') is_rem = request.POST.get('remember', None) # 不推荐使用get,因为get取不到值会报错 user_obj = UserInfo.objects.filter(name=username, password=pwd).first() if user_obj: return_url = request.GET.get('returnUrl', '/index/') request.session['user'] = user_obj.name # 判断是否记住密码 if is_rem: # 是就保存七天 request.session.set_expiry(60*60*24*7) else: # 不是的话关闭浏览器就失效 request.session.set_expiry(0) return redirect(return_url) else: return render(request, 'login.html', {'error_msg': '用户名或者密码错误'}) # 首页 @check_login def index(request): publisher_list = Publisher.objects.all() return render(request, 'index.html', {'publisher_list': publisher_list}) # 注销 def logout(request): # request.session.delete() # 删除session request.session.flush() # 删除session并让cookie失效 return redirect('/login/')
5、Django中的Session配置 在setting.py里面设置的关于session的全局变量配置 1. 数据库Session SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认把session存在数据库) 2. 缓存Session SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎(把session存在缓存中) SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置 3. 文件Session SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎(把session存在文件中) SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 4. 缓存+数据库 SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎(把session存在数据库和缓存中) 5. 加密Cookie Session SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎(把session存在浏览器中,相当于加盐的cookie) 其他公用设置项: SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认) SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认) SESSION_SAVE_EVERY_REQUEST = False # 每次请求都刷新Session的有效期,默认修改之后才保存(默认) 注意: SESSION_COOKIE_AGE设置的是全局的session失效日期, request.session.set_expiry(value)设置的是某个具体的请求的session失效日期(局部) 不常用: SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认) SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认) SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认) SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认) 五、使用装饰器 1、FBV中加装饰器 首先定义一个装饰器 然后直接在要被装饰的函数上一行添加装饰器 例如: # 装饰器函数 def check_login(func): @wraps(func) def inner(request, *args, **kwargs): # 先做cookie验证,如果cookie中有我登录的信息,则可以访问指定的页面 if request.COOKIES.get('xm') == 'sb': rep = func(request, *args, **kwargs) return rep else: # 否则,让用户去登录 # 拿到当前访问的url return_url = request.path_info return redirect('/login/?returnUrl={}'.format(return_url)) return inner # 使用装饰 @check_login def home(request): return HttpResponse('home') 2、CBV中加装饰器相关 # CBV示例 class UserinfoView(views.View): def get(self, request): return HttpResponse('OK') def post(self, request): return redirect('/index/') 先观察CBV的函数和装饰器函数,明显可以看到,装饰器函数的参数和CBV的函数的参数不一致, CBV的参数中多了一个self,那么传到装饰器中使用的时候,装饰器中inner的参数request就代表CBV中函数的self, 后续CBV的request就会被装饰器的*args接收,此时是没有问题的,但是,在装饰器使用request的时候就出问题了, 因为装饰器中inner的参数request就代表CBV中函数的self,而self明显就没有request的那些方法,所以就会报错。 Django提供了解决这一问题的方法: 1.方法一 # 直接加在CBV视图的get或post方法上 from django.utils.decorators import method_decorator class UserinfoView(views.View): @method_decorator(check_login) def get(self, request): return HttpResponse('OK') def post(self, request): return redirect('/index/') 2.方法二 # 加在dispatch方法上 from django.utils.decorators import method_decorator class UserinfoView(views.View): @method_decorator(check_login) def dispatch(self, request, *args, **kwargs): return super(UserinfoView, self).dispatch(request, *args, **kwargs) def get(self, request): return HttpResponse('OK') def post(self, request): return redirect('/index/') 因为CBV中首先执行的就是dispatch方法,所以这么写相当于给get和post方法都加上了登录校验 3.方法三 直接加在视图类上,但method_decorator必须传 name 关键字参数 # 如果get方法和post方法都需要登录校验的话就写两个装饰器。 from django.utils.decorators import method_decorator @method_decorator(check_login, name="get") @method_decorator(check_login, name="post") class UserinfoView(views.View): def dispatch(self, request, *args, **kwargs): return super(UserinfoView, self).dispatch(request, *args, **kwargs) def get(self, request): return HttpResponse('OK') def post(self, request): return redirect('/index/') 六、补充 1、CSRF Token相关装饰器在CBV只能加到dispatch方法上,或者加在视图类上然后name参数指定为dispatch方法。 如果是FBV则直接添加 @csrf_exempt 即可 注意: csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。 csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。 2、示例 from django.views.decorators.csrf import csrf_exempt, csrf_protect from django.utils.decorators import method_decorator class UserinfoView(views.View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(UserinfoView, self).dispatch(request, *args, **kwargs) def get(self, request): return HttpResponse('OK') def post(self, request): return redirect('/index/') 或者 @method_decorator(csrf_exempt, name='dispatch') class UserinfoView(views.View): def dispatch(self, request, *args, **kwargs): return super(UserinfoView, self).dispatch(request, *args, **kwargs) def get(self, request): return HttpResponse('OK') def post(self, request): return redirect('/index/')