【缓存】
- 定义:缓存是一类可以更快的读取数据的介质统称,也指其他可以加快数据读取的存储方式。一般用来存储临时数据,常用介质的是读取速度很快的内存。
- 意义:视图渲染有一定成本,数据库的频繁查询过高,所以对于低频变动的页面可以考虑使用缓存技术,减少实际渲染次数,用户拿到响应的时间成本会更低。
- 优化思想:
- 缓存场景:(缓存的地方,数据变动频率较少)
- 博客列表页
- 电商商品详情页
【Django中设置缓存】
- 数据库缓存:将缓存的数据存储在数据库中
**尽管存储介质没有更换,但是当把一次负责查询的结果直接存储到表里,比如多个条件的过滤查询结果,可避免重复进行复杂查询,提升效率
# 数据库缓存配置(file: settings.py)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table', # 将缓存数据存入哪张表,表名可自定义
'TIMEOUT': 300, # 缓存保存时间,单位为秒,默认值为300
'OPTIONS': {
'MAX_ENTRIES': 300, # 缓存最大数据条数
'CULL_FREQUENCY': 2, # 缓存条数达到最大值时,删除1/x的缓存数据
}
}
}
- 本地内存缓存:数据缓存到服务器内存中
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake'
}
}
- 文件系统缓存:将缓存的数据存储到本地文件中
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache', # 这个是文件夹的路径
# 'LOCATION': 'c:\test\cache', 此行为windows下示例
}
}
【使用缓存>>整体缓存策略】
*在使用层面上,分为两个维度,整体缓存和局部缓存。
- 在视图函数中使用缓存:
from django.views.decorators.cache import cache_page
@cache_page(30) # 缓存保存时间,单位秒,可修改
def my_view(request):
...
- 在路由中使用缓存:
from django.views.decorators.cache import cache_page
urlpatterns = [
path('foo/', cache_page(60)(my_view)), # 第二个参数中my_view是视图函数
]
【局部缓存策略】
>先引入cache对象:
- 方式一:使用caches['CACHE配置key']导入具体对象
from django.core.cache import caches
cache1 = caches['myalias']
cache2 = caches['myalias_2']
- 方式二:相当于直接引入CACHES配置项中的default项
from django.core.cahce import cache
>缓存api的使用方法:
- cache.set(key, value, timeout):存储缓存
- key:缓存的key,字符串类型
- value:Python对象
- timeout:缓存存储时间(s),默认为CACHES中的TIMEOUT值
- 返回值:None
- cache.get(key):获取缓存
- key:缓存的key
- 返回值:key的具体值,如果没有数据,则返回None
- cache.add(key, value):存储缓存,只在key不存在时生效
- 返回值:True(存储成功)/ False(存储失败)
- cache.get_or_set(key, value, timeout):如果为获取到数据,则执行set操作
- 返回值:value
- cache.set_many(dict, timeout):批量存储缓存
- dict:key和value的字典
- timeout:存储时间(s)
- 返回值:插入不成功的key的数组
- cache.get_many(key_list):批量获取缓存数据
- key_list:包含key的数组
- 返回值:取到的key和value的字典
- cache.delete(key):删除key的缓存数据
- 返回值:None
- cache.delete_many(key_list):批量删除
- 返回值:None
【浏览器缓存策略】
【强缓存】
不会向服务器发送请求,直接从缓存中读取资源。
- 响应头Expires:缓存过期时间,用来指定资源到期时间,是服务器端的具体的时间点
样例:Expires: Thu, 02 Apr 2030 05:14:08 GMT
- 响应头Cache-Control:在HTTP/1.1中,Cache-Control主要用于控制网页缓存(如:'Cache-Control: max-age=120'表示请求创建时间后的120秒,缓存失败)
*目前服务器都会带着这两个头同时响应给浏览器,浏览器优先使用Cache-Control
【协商缓存】
强缓存中的数据一旦过期,还需要跟服务器进行通信,从而获取最新数据。
*如果强缓存的数据是一些静态文件、大图片等,考虑到大图片这类比较花费带宽且不易变化的数据,强缓存时间到期后,浏览器会去跟服务器协商,当前缓存是否可用。如果可用,服务器不必返回数据,浏览器继续使用原来缓存的数据;如果文件不可用,则返回最新数据。
- 响应头Last-Modified和请求头If-Modified-Since:
- Last-Modified为文件的最近修改时间,浏览器第一次请求静态文件时,服务器如果返回Last-Modified响应头,则代表该资源为需协商的缓存
- 当缓存到期后,浏览器将获取到的Last-Modified值作为请求头If-Modified-Since的值,与服务器发请求协商,服务端返回304响应码(响应头为空),代表缓存继续使用,200响应码带包缓存不可用(响应体为最新资源)
- 响应头ETag和请求头If-None-Match:
- ETag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,ETag就会重新生成
- 缓存到期后,浏览器将ETag响应头的值作为If-None-Match请求头的值,给服务器发请求协商。服务器接到请求头后,比对文件标识,不一致则认为资源不可用,返回200响应码(响应体为最新资源);可用则返回304响应码
【中间件】
定义:中间件是Django请求/响应处理的钩子框架。它是一个轻量级的、低级的“插件”系统,用于全局改变Django的输入(请求)或输出(响应)。
*中间件以类的形式体现
*每个中间件组件负责做一些特定的功能(如:Django包含一个中间件组件AuthenticationMiddleware,它会使用会话将用户与请求关联起来)
【编写中间件】
中间件类必须继承django.utils.deprecation.MiddlewareMixin类。
中间件类必须实现下列五个方法中的一个或多个:
- process_request(self, request):执行路由之前被调用,在每个请求上调用,返回None或HttpResponse对象
- process_view(self, request, callback, callback_args, callback_kwargs):调用视图之前被调用,在每个请求上调用,返回None或HttpResponse对象
- process_response(self, request, response):所有响应返回浏览器被调用,在每个请求上调用,返回HttpResponse对象
- process_exception(self, request, exception):当处理过程中抛出异常时调用,返回一个HttpResponse对象
- process_template_response(self, request, response):在视图函数执行完毕且视图返回的对象中包含render方法时被调用;该方法需要返回实现了render方法的响应对象
**中间件的大多数方法在返回None时表示忽略当前操作进入下一项事件,当返回HttpResponse对象时表示此请求结束,直接返回给客户端
【注册中间件】
settings.py中需要注册自定义的中间件:
# file: settings.py
MIDDLEWARE = [
...,
'middleware.mymiddleware.MyMW'
# 在middleaware文件夹中mymiddleware.py文件中MyMW类
]
**配置为数组,中间件被调用时以“先上到下”再“由下到上”的顺序调用!
【中间件执行总流程】
【CSRF:跨站伪造请求攻击】
某些恶意网站上包含链接、表单按钮或者JavaScript,它们会利用登录过的用户在浏览器中的认证信息试图在你的网站上完成某些操作,这就是跨站请求伪造(Cross-Site Request Forgey)
【CSRF防范】
- Django采用“比对暗号”机制来防范攻击:
Cookies中存储暗号1,模板中表单里藏着暗号2,用户只有在本网站下提交数据,暗号2才会随表单提交给服务器,Django对比两个暗号,对比成功,则认为是合法请求,否则是违法请求(403响应码)
- 配置步骤:
- settings.py中确认MIDDLEWARE中django.middleware.csrf.CsrfViewMiddleware是否打开(要打开)
- 模板中,form标签下添加:{% csrf_token %}
样例:
<form action="/test_csrf" method="post">
{% csrf_token %}
<input type="text" name="username">
<input type="submit" value="提交">
</form>
**如果某个视图不需要Django进行csrf保护,可以用装饰器关闭对此视图的检查,如:
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def my_view(request):
return HttpResponse('Hello world')
【分页】
定义:在web页面有大量数据需要显示,为了阅读方便在每个页面中只显示部分数据。
优点:方便阅读;减轻数据提取量,减轻服务器压力
- Django提供了Paginator类可以方便的实现分页功能
- Paginator类位于'django.core.paginator'模块中
【Paginator对象】
负责分页数据整体的管理。
对象的构造方法为:paginator = Paginator(object_list, per_page)
- 参数:
- object_list:需要分页数据的对象列表
- per_page:每页数据个数
- 返回值:Paginator的对象
- 属性:
- count:需要分页数据的对象总数
- num_pages:分页后的页面总数
- page_range:从1开始的range对象,用于记录当前页码数
- per_page:每页数据的个数
【Paginator方法】
paginator对象.page(number):
- 参数number:页码信息(从1开始)
- 返回值:当前第number页对应的页信息
- 如果提供的页码不存在,则抛出InvalidPage异常
【Paginator异常exception】
InvalidPage:总的异常基类,包含以下两个异常子类:
- PageNotAnInteger:当向page()传入一个不是整数的值时抛出
- EmptyPage:当向page()提供一个有效值,但是那个页面上没有任何对象时抛出
【Page对象】
负责具体某一页的数据的管理。
构造方法:page = paginator.page(页码)
*Paginator对象的page()方法返回Page对象
- 属性:
- object_list:当前页上所有数据对象的列表
- number:当前页的序号,从1开始
- paginator:当前page对象相关的Paginator对象
【Page方法】
- has_next():如果有下一页返回True
- has_previous():如果有上一页返回True
- has_other_pages():如果有上一页或下一页返回True
- next_page_number():返回下一页的页码,如果下一页不存在,抛出InvalidPage异常
- previous_page_number():返回上一页的页码,如果上一页不存在,抛出InvalidPage异常
样例:
1、路由配置:
path('test_page', views.test_page)
2、视图函数:
def tets_page(request):
#/test_page?page=1
page_num = request.Get.get('page', 1)
all_data = ['a', 'b', 'c', 'd', 'e']
# 初始化paginator
paginator = Paginator(all_data, 2)
# 初始化具体页码的page对象
c_page = paginator.page(int(page_num))
return render(request, 'test_page.html', locals())
3、模板层test_page.html:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>分页</title>
</head>
<body>
{% for p in c_page %}
<p>
{{ p }}
</p>
{% endfor %}
{% if c_page.has_previous %}
<a href="/test_page?page={{ c_page.previous_page_number }}">上一页</a>
{% else %}
上一页
{% endif %}
{% for p_num in paginator.page_range %}
{% if p_num == c_page.number %}
{{ p_num }}
{% else %}
<a href="/test_page?page={{ p_num }}">{{ p_num }}</a>
{% endif %}
{% endfor %}
{% if c_page.has_next %}
<a href="/test_page?page={{ c_page.next_page_number }}">下一页</a>
{% else %}
下一页
{% endif %}
</body>
</html>
结果: