一、中间件
1.1 中间件的使用
- 编写类
- 在settings中注册
1.编写类
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
# request是请求相关所有的数据
pass
def process_view(self, request, view, *args, **kwargs):
# request是请求相关所有的数据; view是视图函数; 路由参数*args, **kwargs
pass
def process_response(self, request, response):
# request是请求相关所有的数据
# response是试图函数返回的那个对象(封装了要返回到用户浏览器的所有数据)
return response
2.在settings中注册
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'utls.my_mw.My1Middleware',
'utls.my_mw.My2Middleware',
'utls.my_mw.My3Middleware',
]
# 动态导入 + 反射
注意:
python执行顺序是从上往下执行, 所以,定义的中间件越靠前,就越先执行。如果自己定义的中间件放在最前面,则优先于系统定义的中间件执行,可能会导致某些操作出错,例如:
'utls.my_mw.My1Middleware',
'django.contrib.sessions.middleware.SessionMiddleware',
如果在系统定义的session之前定义自己的中间件,那么在中间件中,如果使用到了request.session
,那么就会报错。
3. 测试:
a.正常请求:
- 请求从浏览器发出,首先经过wsgi进行封装,才进入Django程序
- 一个请求需要经过所有的中间件,每一个中间件可以看做一个类,首先执行类中的
process_request
方法。 - 执行完最后一个中间件的
process_request
方法后,才会进入路由匹配。 - 路由匹配完成,又从第一个中间件开始,执行所有中间件的
process_view
方法 - 执行完成所有中间件的
process_view
方法后,才会进入视图函数,得到HttpResponse,再返回。 - 返回时,也需要执行所有中间件的
process_response
方法,然后才返回给路由器。
# my_wm.py
from django.utils.deprecation import MiddlewareMixin
class My1Middleware(MiddlewareMixin):
def process_request(self, request):
print('1111 process_request')
def process_view(self, request, view, *args, **kwargs):
print('1111 process_view')
def process_response(self, request, resposne):
print('1111 process_response')
return resposne
class My2Middleware(MiddlewareMixin):
def process_request(self, request):
print('2222 process_request')
def process_view(self, request, view, *args, **kwargs):
print('2222 process_view')
def process_response(self, request, resposne):
print('2222 process_response')
return resposne
class My3Middleware(MiddlewareMixin):
def process_request(self, request):
print('3333 process_request')
def process_view(self, request, view, *args, **kwargs):
print('3333 process_view')
def process_response(self, request, resposne):
print('3333 process_response')
return resposne
# views.py
class MineView(View):
def get(self, request, v1):
print('进入视图函数,执行函数')
return HttpResponse('11')
# urls.py
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('mine/<int:v1>/', views.MineView.as_view()),
]
b非正常请求:
请求被某个process_request
拦截
- 请求从浏览器发出,首先经过wsgi进行封装,才进入Django程序
- 请求经过某个
process_request
方法,被拦截,直接返回当前中间件的process_response
方法 - 从该中间件向前执行所有的
process_response
方法,返回给路由器结果。
b1.在process_request
中,请求被阻塞
例如,当访问某个链接,规定好,需要携带token=12345才允许访问
修改process_request
class My2Middleware(MiddlewareMixin):
def process_request(self, request):
if request.path_info == "/x3/":
token = request.GET.get('token')
if token == "12345":
return
else:
return HttpResponse("无权访问")
如果直接访问 /x3就会提时无权访问
注意点:此时还没有定义/x3/这个路由,但是仍然不报错,因为请求不会到达路由匹配,所以即使不定义路由也没关系
如果访问/x3/?token=12345,才会执行相应的函数(前提是定义好了x3视图函数,并且在urls添加了)
b2请求被某个process_view
拦截
- 请求从浏览器发出,首先经过wsgi进行封装,才进入Django程序
- 一个请求需要经过所有的中间件,每一个中间件可以看做一个类,首先执行类中的
process_request
方法。 - 执行完最后一个中间件的
process_request
方法后,才会进入路由匹配。 - 路由匹配完成,又从第一个中间件开始,执行所有中间件的
process_view
方法 - 执行某个
process_view
方法,被拦截,直接跳到最后一个中间件,执行process_response
- 返回时,也需要执行所有中间件的
process_response
方法,然后才返回给路由器。
关于自定义prcess_response
,一般用于对请求要返回的数据进行修改,条件headers,对response加密返回等。
def process_response(self, request, response):
response["city"] = "CD"
return response
4.不常用的中间件方法
中间件在类型定义中,除了常见的process_request
、process_view
、process_response
外,还有process_exception
、process_template_response
process_exception,视图函数有异常,处理出现异常时
process_template_response,对于视图函数返回内容渲染扩展。
- 在视图函数中如果返回的对象内部有一个render方法且可以被调用执行
- process_template_response返回response参数(返回值)
- 在自定义的MyReponse的render方法中必须返回HttpRespose
1.2 源码*
1.关于请求
最开始实例化Handler对象,执行一次__init__
方法,然后监听8000端口,有请求到来,就执行对象中的__call__
方法
from wsgiref.simple_server import make_server
class Handler:
def __init__(self):
# 做一些初始化动作
self.name = "xxx"
def __call__(self,environ, start_response):
# 根据初始化的动作,去执行...
# ...
start_response('200 OK', [('Content-Type', 'text/html')])
return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ]
if __name__ == '__main__':
obj = Handler() # 执行
httpd = make_server('127.0.0.1', 8000, obj) # 有请求到来时,执行 obj(environ, start_response)
httpd.serve_forever()
2. 启动Django项目,默认执行WSGIHandler.init
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_middleware()
def load_middleware(self, is_async=False):
### 定义3个列表
# 用于存放process_view
self._view_middleware = []
# 用于存放 process_template_response
self._template_response_middleware = []
# 用于存放 process_exception
self._exception_middleware = []
# 暂不考虑异步操作,get_response = self._get_response
get_response = self._get_response_async if is_async else self._get_response
#闭包,handler=函数,handler() -> get_resposne()
handler = convert_exception_to_response(get_response)
handler_is_async = is_async
# 获取settings的中间件,反转
for middleware_path in reversed(settings.MIDDLEWARE):
## 根据字符串导入模块。找到utls.my_wm 下的类 MyMiddleware
middleware = import_string(middleware_path)
...
#省略异步操作
...
try:
# 再次封装,如果handelr不是异步,adapted_handler = handler
adapted_handler = self.adapt_method_mode(
middleware_is_async,
handler,
handler_is_async,
debug=settings.DEBUG,
name="middleware %s" % middleware_path,
)
# 类()->实例化类
# mw_instance = middlerware(handler)
mw_instance = middleware(adapted_handler)
...
# handler = handler
handler = adapted_handler
# 去每个中间件找,有process_view,就在列表最前面,添加该方法
if hasattr(mw_instance, "process_view"):
self._view_middleware.insert(
0,
self.adapt_method_mode(is_async, mw_instance.process_view),
)
# 去每个中间件找,有process_template_response,就在列表最前面,添加该方法
if hasattr(mw_instance, "process_template_response"):
...
if hasattr(mw_instance, "process_exception"):
...
handler = convert_exception_to_response(mw_instance)
handler_is_async = middleware_is_async
# Adapt the top of the stack, if needed.
handler = self.adapt_method_mode(is_async, handler, handler_is_async)
# We only assign to this when initialization is complete as it is used
# as a flag for initialization being complete.
self._middleware_chain = handler
层层封装,每个get_resposne都是下一个中间件对象,调用函数,get_resposne(),等同于执行对象的__call__方法,而每个中间件的__call__方法都包含有process_request
,get_response
,process_resoponse
直到执行到最后一个中间件的_get_resposne()方法,进行路由匹配+执行视图函数
3.请求到来WSGIHandler.call
def __call__(self, environ, start_response):
...
# 封装request
request = self.request_class(environ)
# 中间件+路由匹配+视图函数 ->得到HttpResponse对象
response = self.get_response(request)
...
return response
核心就是 response = self.get_response(request)
def get_response(self, request):
...
# 从init可以得到 self._middleware_chain = handler
# 1.将所有中间件的process_request方法按照注册顺序,从前往后执行了一遍
#2.执行self._get_response --->路由匹配
#3.将所有中间件的process_response方法按照注册顺序,从后往前执行了一遍
response = self._middleware_chain(request)
...
return response
def _get_response(self, request):
response = None
# 解析请求,获取路径和参数
callback, callback_args, callback_kwargs = self.resolve_request(request)
# 这里就在循环所有的process_view方法
for middleware_method in self._view_middleware:
response = middleware_method(
request, callback, callback_args, callback_kwargs
)
# 如果view方法有返回,那么就直接结束,不再执行后面的
if response:
break
if response is None:
# 如果返回值为空,再次封装一下callback,
# 1.判断异步,异步操作的处理,暂时先不管。
# 2. django内部数据库支持的事务
wrapped_callback = self.make_view_atomic(callback)
try:
# 执行视图函数
response = wrapped_callback(request, *callback_args, **callback_kwargs)
# 视图函数出错,就会找中间件中定义的process_exception方法,去执行
except Exception as e:
response = self.process_exception_by_middleware(e, request)
if response is None:
raise
# 检查返回结果,
self.check_response(response, callback)
#如果返回结果含有render函数,且render是可执行的,那么就会执行process_template_response方法
if hasattr(response, "render") and callable(response.render):
for middleware_method in self._template_response_middleware:
response = middleware_method(request, response)
# Complain if the template response middleware returned None
# (a common error).
self.check_response(
response,
middleware_method,
name="%s.process_template_response"
% (middleware_method.__self__.__class__.__name__,),
)
try:
response = response.render()
except Exception as e:
response = self.process_exception_by_middleware(e, request)
if response is None:
raise
return response
- 先执行所有的process_request
- 执行self._get_response
-路由匹配
-中间件process_view
-视图函数
-中间件的process_exception
-中间件的process_template_response
# 核心
# handler = SecurityMiddleware对象
# __call__
# process_request
# get_reponse = SessionMiddleware对象
# process_response
# __call__
# process_request
# get_reponse = CommonMiddleware对象
# process_response
# __call__
# process_request
# get_reponse = MyMiddleware对象
# process_response
从源码看出执行流程:
- 每一个对象执行
__call__
,都是在执行三种方法 - 先执行
process_request
- 然后执行
get_response
,每个对象的get_response
都是下一个定义的中间件的对象 - 执行对象,又执行对象的
__call__
,循环下去直到执行到最后一个对象的get_response
- 最后一个对象执行
get_response
是执行_get_response
,执行视图函数+process_view
+异常处理 - 全部完成后,又从最后的
process_response
开始往前直行
二、COOKIE
Cookie本质上就是存储在浏览器上的一堆键值对
from django.urls import path
from django.shortcuts import HttpResponse
def x1(request):
# 包含:响应体、响应头、状态码等信息
obj = HttpResponse("x1", status=201, reason="OK")
# 设置响应头
obj['city'] = "ChengDu"
# 设置cookie
# max_age=10: 设置超时时间为10s的cookie
# path="/": 设置作用域为当前域名所有路径
obj.set_cookie("v1", "root", max_age=10, path="/")
obj.set_cookie("v2", "hello")
# 设置签名的cookie
obj.set_signed_cookie("name", "szr")
return obj
def x2(request):
print(request.COOKIES)
xx = request.get_signed_cookie("name") # 用户不能自己修改cookie的内容,但凡有一点修改,都不能校验通过。
print(xx)
# 获取cookie
# print(request.COOKIES.get('v2'))
return HttpResponse("x2")
urlpatterns = [
path('x1/', x1, name='x1'),
path('x2/', x2, name='x2'),
]
三、Session
Session的使用一般依赖于Cookie,将一些数据不再发送值浏览器,而是保存的后端的服务器上。
3.1 使用
Session到底要存储位置,默认存在数据库。
INSTALLED_APPS = [
'django.contrib.admin',
...
# 'django.contrib.sessions', # sessions功能的APP django_session
...
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', # process_request/process_response
...
]
# global_settings.py
############
# SESSIONS #
############
# Session存储位置吗,默认db
SESSION_ENGINE = "django.contrib.sessions.backends.db"
# 如果存储到文件中,文件的路径。
SESSION_ENGINE = "django.contrib.sessions.backends.file"
SESSION_FILE_PATH = None
# 存储到缓存
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
# 存储到缓存 + 数据库
SESSION_ENGINE = "django.contrib.sessions.backends.cache_db"
SESSION_CACHE_ALIAS = "default"
# 存储到cookie
SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
# class to serialize session data
SESSION_SERIALIZER = "django.contrib.sessions.serializers.JSONSerializer"
# -------------------------------其他一些可设置
# Cookie name. This can be whatever you want.
SESSION_COOKIE_NAME = "sessionid"
# Age of cookie, in seconds (default: 2 weeks).
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2
# A string like "example.com", or None for standard domain cookie.
SESSION_COOKIE_DOMAIN = None
# Whether the session cookie should be secure (https:// only).
SESSION_COOKIE_SECURE = False
# The path of the session cookie.
SESSION_COOKIE_PATH = "/"
# Whether to use the HttpOnly flag.
SESSION_COOKIE_HTTPONLY = True
# Whether to set the flag restricting cookie leaks on cross-site requests.
# This can be 'Lax', 'Strict', 'None', or False to disable the flag.
SESSION_COOKIE_SAMESITE = "Lax"
# Whether to save the session data on every request.
SESSION_SAVE_EVERY_REQUEST = False
# Whether a user's session cookie expires when the web browser is closed.
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
- 请求时,生成Session、Cookie。
- 再次请求,默认携带cookie,根据Cookie中的凭证,去找到Session中原本存储的数据。
3.2 引擎配置
- 数据库引擎
SESSION_ENGINE = "django.contrib.sessions.backends.db"
INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
>>>python manage.py makemigrations
>>>python manage.py migrate
- 文件
# 如果存储到文件中,文件的路径。
SESSION_ENGINE = "django.contrib.sessions.backends.file"
SESSION_FILE_PATH = None
INSTALLED_APPS = [
#'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
3.3中间件
1.创建对象
在启动django程序时,会自动创建 SessionMiddlewared对象。
class MiddlewareMixin:
def __init__(self, get_response):
self.get_response = get_response
class SessionMiddleware(MiddlewareMixin):
def __init__(self, get_response):
super().__init__(get_response)
# "django.contrib.sessions.backends.db" "django.contrib.sessions.backends.file"
engine = import_module(settings.SESSION_ENGINE)
# db.SessionStore file.SessionStore
self.SessionStore = engine.SessionStore
2 请求到来 *
def process_request(self, request):
# 1.去Cookie中读取凭证 sessionid="xxxx"
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
# 2.实例化
request.session = self.SessionStore(session_key)
def x1(request):
request.session['id'] = 1
request.session['city'] = 'cd' #调用类中的__setitem__
return HttpResponse("x1")
3 请求结束*
# 将缓存中的数据保存,
#如果session引擎是文件,就保存文件中。如果是db,就保存数据库中
request.session.save()
...
# 将session写入response中
response.set_cookie(
settings.SESSION_COOKIE_NAME,
request.session.session_key,
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
总结
- 使用角度:
- 中间件
- Cookie
- Session:Cookie + 中间件
- 源码流程:
- 中间件、cookie、session