当 Django 处理一个 Request 的过程是首先通过中间件, 然后再通过默认的 URL 方式进行的. 我们可以在 Middleware 这个地方把所有 Request 拦截住, 用我们自己的方式完成处理以后直接返回 Response, 因此了解中间件的构成是非常有必要的.
1, 中间件的概念
Django 默认的 Middleware 如下:
在 Django 项目中的 settings 模块中, 有一个 MIDDLEWARE_CLASSES 变量, 其中每一个元素就是一个中间件, 如下:
- 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',
- ]
每一个中间件都有具体的功能. 中间件可以定义五个方法.
分析源码
我们可以查看上面 middleware 的源码, 我们首先导入 security:
from django.middleware.security import SecurityMiddleware
我们查看源码, 会发现:
同样, 我们再查看一个:
from django.middleware.common import CommonMiddleware
我们查看源码, 会发现:
再来继续:
from django.middleware.csrf import CsrfViewMiddleware
我们查看源码, 发现:
我们是不是发现了一个共同的特点. 也就是都有 process_request 和 process_response. 但是并不是所有的中间件都有 process_request, 但是所有中间件都有 process_response. 而且我们在写中间件的时候, 在 process_response 一定要注意有返回值!!,process_request 没有!!, 如果 process_request 有 return 值的时候, 就不会执行后面的程序了(所以这里可以拦截)
from django.middleware.clickjacking import XFrameOptionsMiddleware
查看源码:
我们从浏览器发出一个请求 Request, 得到一个响应后的内容 HttpResponse, 也就是说, 每一个请求都是先通过中间件中的 process_request 函数, 这个函数返回 None 或者 HttpResponse 对象, 如果返回前者, 继续处理其他中间件, 如果返回前者, 继续处理其他中间件, 如果返回一个 HttpResponse, 就处理终止, 返回到网页上.
中间件不用集成任何类(可以继承 object), 下面一个中间件大概的样子:
- class CommonMiddleware(object):
- def process_request(self, request):
- return None
- def process_response(self, request, response):
- return response
还有 process_view,process_exception 和 process_template_response 函数.
中间件顾名思义, 是介于 request 与 response 处理之间的一道处理过程, 相对比较轻量级, 并且在全局上改变 django 的输入与输出. 因为改变的是全局, 所以需要谨慎实用, 用不好会影响到性能.
Django 中间层的定义
- Middleware is a framework of hooks into Django's request/response processing.
- It's a light, low-level"plugin"system for globally altering Django's input or output.
如果你想修改请求, 例如被传送到 view 中的 HTTPRequest 对象. 或者你想修改 view 返回的 HTTPResponse 对象, 这些都可以通过中间件来实现.
可能你想在 view 执行之前做一些操作, 这种情况就可以用 middleware 来实现.
2, 自定义中间件的方法
中间件一共有五个方法:
- process_request(self, request)
- process_view(self, request, callback, callback_args, callback_kwargs)
- process_template_response()
- process_exception(self, request, exception)
- process_response(self, request, response)
2.1 Request 预处理函数: process_request(self, request)
这个方法的调用时机在 Django 接收到 request 之后, 但仍未解析 URL 以确定应当运行的视图函数. Django 向它传入相应的 Request 对象, 以便在方法中修改.
如果返回 None,Django 将继续处理这个 request, 执行后续的中间件, 然后调用相应的 view.
如果返回 HttpResponse 对象, Django 将不再执行任何除了 process_response 以外的其他的中间件以及相应的 view,Django 将立即返回该 HttpResponse.
2.2 View 预处理函数: process_view(self, request, callback, callback_args, callback_kwargs)
这个方法的调用时机在 Django 执行完 request 预处理函数并确定待执行的 view(即 callback 参数)之后, 但在 view 函数实际执行之前.
request:HttpRequest 对象.
callback:Django 将调用的处理 request 的 python 函数. 这是实际的函数对象本身,
而不是字符串表述的函数名.
args: 将传入 view 的位置参数列表, 但不包括 request 参数(它通常是传入 view 的第一个参数).
kwargs: 将传入 view 的关键字参数字典.
process_view() 应当返回 None 或 HttpResponse 对象. 如果返回 None, Django 将继续处理这个 request , 执行后续的中间件, 然后调用相应的 view.
如果返回 HttpResponse 对象, Django 将不再执行任何其它的中间件 (不论种类) 以及相应的 view,Django 将立即返回.
2.3 Template 模板渲染函数: process_template_response()
默认不执行, 只有在视图函数的返回结果对象中有 render 方法才会执行, 并把对象的 render 方法的返回值返回给用户 (注意不返回视图函数的 return 的结果了, 而是返回视图函数 return 值(对象) 中 render 方法的结果)
2.4 Exception 后处理函数: process_exception(self, request, exception)
这个方法只有在 request 处理过程中出了问题并且 view 函数抛出了一个未捕获的异常才会被调用. 这个钩子可以用来发送错误通知, 将现场相关信息输出到日志文件, 或者甚至尝试从错误中自动恢复.
这个函数的参数除了一贯的 request 对象之外, 还包括 view 函数抛出的实际的异常对象 exception .
process_exception() 应当返回 None 或 HttpResponse 对象. 如果返回 None,Django 将用框架内置的异常处理机制继续处理相应 request. 如果返回 HttpResponse 对象, Django 将使用该 response 对象, 而短路框架内置的异常处理机制.
2.5 Response 后处理函数: process_response(self, request, response)
这个方法的调用时机在 Django 执行 view 函数并生成 response 之后.
该处理器能修改 response 的内容; 一个常见的用途是内容压缩, 如 gzip 所请求的 HTML 页面.
这个方法的参数相当直观: request 是 request 对象, 而 response 则是从 view 中返回的 response 对象.
process_response() 必须返回 HttpResponse 对象. 这个 response 对象可以是传入函数的那一个原始对象(通常已被修改), 也可以是全新生成的.
3, 自定义中间件方法练习
3.1 自定义中间件方法应用 1(关于 process_request 和 process_response 方法)
当用户发起请求的时候会依次经过所有的中间件, 这个时候的请求是 process_request, 最后到达 views 的函数中, views 函数处理后, 在依次穿过中间件, 这个时候是 process_response, 最后返回给请求者.
上述截图中的中间件都是 django 中的, 我们也可以自己定义一个中间件, 我们可以自己写一个类, 但是必须继承 MiddlewareMixin.
(为什么必须继承 MiddlewareMinxin? 当时是因为我们之前查看 settings 里面的中间件的情形, 他们也是这样定义的. 既然都是中间件, 我们就需要按照人家的语法来写)
需要导入:
from django.utils.deprecation import MiddlewareMixin
在 views 中:
- def index(request):
- print("view 函数...")
- return HttpResponse("OK")
在 Mymiddlewares.py 中:
- from django.utils.deprecation import MiddlewareMixin
- from django.shortcuts import HttpResponse
- class Md1(MiddlewareMixin):
- def process_request(self,request):
- print("Md1 请求")
- def process_response(self,request,response):
- print("Md1 返回")
- return response
- class Md2(MiddlewareMixin):
- def process_request(self,request):
- print("Md2 请求")
- #return HttpResponse("Md2 中断")
- def process_response(self,request,response):
- print("Md2 返回")
- return response
结果:
Md1 请求
Md2 请求
view 函数...
Md2 返回
Md1 返回
注意: 如果当请求到达请求 2 的时候直接不符合条件返回, 即 return HTTPResponse("Md2 中断"), 程序将请求直接发给中间件 2 返回, 然后依次返回到请求者, 结果如下:
返回 Md2 中断的页面, 后台打印如下:
Md1 请求
Md2 请求
Md2 返回
Md1 返回
流程图如下:
3.2 自定义中间件方法应用 2(关于 process_view 方法)
Mymiddlewares.py 修改如下:
- from django.utils.deprecation import MiddlewareMixin
- from django.shortcuts import HttpResponse
- class Md1(MiddlewareMixin):
- def process_request(self,request):
- print("Md1 请求")
- #return HttpResponse("Md1 中断")
- def process_response(self,request,response):
- print("Md1 返回")
- return response
- def process_view(self, request, callback, callback_args, callback_kwargs):
- print("Md1view")
- class Md2(MiddlewareMixin):
- def process_request(self,request):
- print("Md2 请求")
- # return HttpResponse("Md2 中断")
- def process_response(self,request,response):
- print("Md2 返回")
- return response
- def process_view(self, request, callback, callback_args, callback_kwargs):
- print("Md2view")
细心的话, 会发现我们增加了 process_view 函数.
views.py 视图函数不用改, 还是这样:
- def index(request):
- print("view 函数...")
- return HttpResponse("OK")
我们运行程式, 结果如下:
Md1 请求
Md2 请求
Md1view
Md2view
view 函数...
Md2 返回
Md1 返回
下面继续使用流程图分析上面过程:
当最后一个中间的 process_request 到达路由关系映射之后, 返回到中间件 1 的 process_view , 然后依次往下, 到达 views 函数, 最后通过 process_response 依次返回到达用户.
那么这样做有什么意义?
我们可以发现, process_view() 里面有很多的参数.
我们捋一遍过程.
首先, 执行中间件 1 的 process_request, 然后执行中间件 2 的 process_request:
- from django.utils.deprecation import MiddlewareMixin
- from django.shortcuts import HttpResponse
- class Md1(MiddlewareMixin):
- def process_request(self,request):
- print("Md1 请求")
- class Md2(MiddlewareMixin):
- def process_request(self,request):
- print("Md2 请求")
然后去 urls.py 去找到 index() 函数:
- urlpatterns = [
- path('index/', views.index),
- ]
然后返回中间件 1 的 process_view 和中间件 2 的 process_view, 将拿到的 views.index 传到 callback, 去执行 process_view().
- from django.utils.deprecation import MiddlewareMixin
- from django.shortcuts import HttpResponse
- class Md1(MiddlewareMixin):
- def process_view(self, request, callback, callback_args, callback_kwargs):
- print("Md1view")
- class Md2(MiddlewareMixin):
- def process_view(self, request, callback, callback_args, callback_kwargs):
- print("Md2view")
我们也可以尝试打印 callback 这个视图函数, 可以看到其结果如下:
- callback =============>
- <function index at 0x103338bf8>
而 process_view 中 callback_args 和 claaback_kwargs 是视图函数的参数.
当执行完 process_view 的时候, 就去视图函数执行其函数, 然后执行 process_response.
注意: process_view 函数可以用来调用视图函数, 我们调用如下:
- from django.utils.deprecation import MiddlewareMixin
- from django.shortcuts import HttpResponse
- class Md1(MiddlewareMixin):
- def process_request(self,request):
- print("Md1 请求")
- def process_response(self,request,response):
- print("Md1 返回")
- return response
- def process_view(self, request, callback, callback_args, callback_kwargs):
- print("Md1 view")
- class Md2(MiddlewareMixin):
- def process_request(self,request):
- print("Md2 请求")
- def process_response(self,request,response):
- print("Md2 返回")
- return response
- def process_view(self, request, callback, callback_args, callback_kwargs):
- print("Md2 view")
- return HttpResponse("123")
结果如下:
Md1 请求
Md2 请求
- Md1 views
- Md2 views
Md2 返回
Md1 返回
但是请注意, 页面返回的是 '123', 而不是我们视图函数里面的 "OK"(为了怕大家混淆, 我这里将 index 的视图函数贴上).
- def index(request):
- return HttpResponse("OK")
如果要返回 OK 的话, 怎么做呢? 我们修改代码如下:
- class Md1(MiddlewareMixin):
- def process_request(self,request):
- print("Md1 请求")
- #return HttpResponse("Md1 中断")
- def process_response(self,request,response):
- print("Md1 返回")
- return response
- def process_view(self, request, callback, callback_args, callback_kwargs):
- # return HttpResponse("hello")
- response=callback(request,*callback_args,**callback_kwargs)
- return response
结果如下:
Md1 请求
Md2 请求
view 函数...
Md2 返回
Md1 返回
注意: process_view 如果有返回值, 会越过其他的 process_view 以及视图函数, 但是所有的 process_response 都还会执行.
3.3 自定义中间件方法应用 3(关于 process_exception 方法)
Mymiddlewares.py 修改如下:
- class Md1(MiddlewareMixin):
- def process_request(self,request):
- print("Md1 请求")
- def process_response(self,request,response):
- print("Md1 返回")
- return response
- def process_view(self, request, callback, callback_args, callback_kwargs):
- # return HttpResponse("hello")
- # response=callback(request,*callback_args,**callback_kwargs)
- # return response
- print("md1 process_view...")
- def process_exception(self, request, exception):
- print("md1 process_exception...")
- class Md2(MiddlewareMixin):
- def process_request(self,request):
- print("Md2 请求")
- def process_response(self,request,response):
- print("Md2 返回")
- return response
- def process_view(self, request, callback, callback_args, callback_kwargs):
- print("md2 process_view...")
- def process_exception(self, request, exception):
- print("md2 process_exception...")
结果如下:
Md1 请求
Md2 请求
- md1 process_view...
- md2 process_view...
view 函数...
Md2 返回
Md1 返回
当没有问题 (异常) 的时候, 我们发现, 和正常的执行没有任何区别.
当 views 出错的时候, process_exception 才会执行, 我们看报错后的执行流程, 如下:
下面我们来模拟一个错误, 执行看流程:
我们给 views.py 里面加一个简单的错误, 代码如下:
- def index(request):
- print("index doing")
- james
- return HttpResponse("index OK")
我们执行程式, 发现其执行流程如下:
Md1 请求
Md2 请求
- md1 process_view...
- md2 process_view...
view 函数...
- md2 process_exception...
- md1 process_exception...
- Traceback (most recent call last):
- File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\site-packages\django\core\handlers\exception.py", line 34,
- in inner
- response = get_response(request)
- File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\site-packages\django\core\handlers\base.py", line 126, in _
- get_response
- response = self.process_exception_by_middleware(e, request)
- File "C:\Users\Administrator\AppData\Local\Programs\Python\Python37\lib\site-packages\django\core\handlers\base.py", line 124, in _
- get_response
- response = wrapped_callback(request, *callback_args, **callback_kwargs)
- File "E:\backup\pycode\web 开发 \ User_model\users\views.py", line 7, in index
- james
- NameError: name 'james' is not defined
Md2 返回
Md1 返回
并且前端调用如下(当然这是 Django 自带默认的错误报错程式):
我们也可以自己设定报错, 我们将 Mymiddlewares.py 修改如下:
- class Md1(MiddlewareMixin):
- def process_request(self,request):
- print("Md1 请求")
- def process_response(self,request,response):
- print("Md1 返回")
- return response
- def process_view(self, request, callback, callback_args, callback_kwargs):
- # return HttpResponse("hello")
- # response=callback(request,*callback_args,**callback_kwargs)
- # return response
- print("md1 process_view...")
- def process_exception(self, request, exception):
- print("md1 process_exception...")
- class Md2(MiddlewareMixin):
- def process_request(self,request):
- print("Md2 请求")
- def process_response(self,request,response):
- print("Md2 返回")
- return response
- def process_view(self, request, callback, callback_args, callback_kwargs):
- print("md2 process_view...")
- def process_exception(self, request, exception):
- print("md2 process_exception...")
- return HttpResponse(exception)
我们再执行程式, 会发现前台是这样的:
后台是这样的:
Md1 请求
Md2 请求
- md1 process_view...
- md2 process_view...
view 函数...
md2 process_exception...
Md2 返回
Md1 返回
我们发现在执行中间件 2 的 process_exception 后, 不会去执行中间件 1 了. 因为中间件 2 将错误处理完之后, 直接去执行 response 了.
4, 中间件应用场景
由于中间件工作在视图函数执行前, 执行后. 所以适合所有的请求 / 一部分请求做批量处理
1, 做 IP 访问频率限制
放在中间件类的列表中, 阻止某些 IP 访问了.
因为某些 IP 访问服务器的频率过高, 所以进行拦截, 比如限制每分钟不能超过 20 次.
2, 缓存
客户端请求来了, 中间件去缓存看看有没有数据, 有直接返回给用户, 没有再去逻辑层, 执行视图函数.
3,URL 访问过滤
如果用户访问的是 login 视图
如果访问的是其他视图(需要检测是不是由 session 已经有了放行, 没有返回 login), 这样就省的在多个视图函数上写装饰器了!
那么如何写呢? 如下:
Django 通过中间件实现登录验证 DEMO
前提: 中间件版的登录验证需要依靠 session, 所以数据库中要有 django_session 表.
urls.py 代码:
- from django.conf.urls import url
- from django.contrib import admin
- from app01 import views
- urlpatterns = [
- url(r'^admin/', admin.site.urls),
- url(r'^login/$', views.login, name='login'),
- url(r'^index/$', views.index, name='index'),
- url(r'^home/$', views.home, name='home'),
- ]
views.py 代码:
- from django.shortcuts import render, HttpResponse, redirect
- def index(request):
- return HttpResponse("this is index")
- def home(request):
- return HttpResponse("this is home")
- def login(request):
- if request.method == 'POST':
- user = request.POST.get("user")
- pwd = request.POST.get("pwd")
- if user == 'james' and pwd == '123':
- # 设置 session
- request.session['user'] = user
- # 获取跳到登录页面之前的 URL
- next_url = request.GET.get('next')
- # 如果有, 就跳转到登录之前的 URL
- if next_url:
- return redirect(next_url)
- # 否则默认跳转到 index 页面
- else:
- return redirect('/index/')
- return render(request, 'login.html')
login.HTML 页面代码:
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <title>
- 登录页面
- </title>
- </head>
- <body>
- <form action="{% url'login'%}" method="post">
- {% csrf_token %}
- <p>
- <label for="user">
- 用户名:
- </label>
- <input type="text" name="user" id="user">
- </p>
- <p>
- <label for="pwd">
- 密码:
- </label>
- <input type="text" name="pwd" id="pwd">
- </p>
- <input type="submit" value="登录">
- </form>
- </body>
- </HTML>
mymiddlewares.py 的代码:
- # 自定义中间件
- from django.utils.deprecation import MiddlewareMixin
- from django.shortcuts import redirect, HttpResponse
- class AuthMD(MiddlewareMixin):
- white_list = ['/login/', ] # 白名单
- black_list = ['/black/', ] # 黑名单
- def process_request(self, request):
- next_url = request.path_info
- print(request.path_info, request.get_full_path())
- # 黑名单的网址限制访问
- if next_url in self.black_list:
- return HttpResponse("This is an illegal URL")
- # 白名单的网址或者登录用户不做限制
- elif next_url in self.white_list or request.session.get("user"):
- return
- else:
- return redirect('/login/?next={}'.format(next_url))
settings.py 代码中添加内容如下:
- 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',
- # 注意这个中间件是自己加的
- 'app01.mymiddleware.AuthMD',
- ]
AuthMD 中间件注册后, 所有的请求都要走 AuthMD 的 process_request 方法. 如果 URL 在黑名单中, 则返回 This is an illegal URL 的字符串; 访问的 URL 在白名单或者 session 中有 user 用户名, 则不作阻拦走正常流程; 正常的 URL 但是需要登录后访问, 让浏览器跳转到登录页面.
注意: AuthMD 中间件中需要 session, 所以 AuthMD 注册的位置要在 session 中间的下方
6, 跨站请求伪造
1, 简介
django 为用户实现防止跨站请求伪造的功能, 通过中间件
django.middleware.csrf.CsrfViewMiddleware 来完成. 而对于 django 中设置防跨站请求伪造功能有分为全局和局部.
全局:
中间件 django.middleware.csrf.CsrfViewMiddleware
局部:
@csrf_protect, 为当前函数强制设置防跨站请求伪造功能, 即便 settings 中没有设置全局中间件.
@csrf_exempt, 取消当前函数防跨站请求伪造功能, 即便 settings 中设置了全局中间件.
注: from django.views.decorators.csrf import csrf_exempt,csrf_protect
2, 方式
方式 1:
- $.ajaxSetup({
- data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
- });
方式 2:
- <form>
- {% csrf_token %}
- </form>
- $.Ajax({
- data:{
- "csrfmiddlewaretoken":$("[name='csrfmiddlewaretoken']").val();
- }})
方法 3:
- <script src="{% static'js/jquery.cookie.js'%}"></script>
- $.Ajax({
- headers:{"X-CSRFToken":$.cookie('csrftoken')},
- })
3, 应用
普通表单
veiw 中设置返回值:
return render_to_response('Account/Login.html',data,context_instance=RequestContext(request))
或者
return render(request, 'xxx.html', data)
HTML 中设置 Token:
- {
- % csrf_token %
- }
- Ajax
对于传统的 form, 可以通过表单的方式将 token 再次发送到服务端, 而对于 Ajax 的话, 使用如下方式:
views.py
- from django.template.context import RequestContext
- # Create your views here.
- def test(request):
- if request.method == 'POST':
- print request.POST
- return HttpResponse('ok')
- return render_to_response('app01/test.html',context_instance=RequestContext(request))
text.HTML
- <!DOCTYPE HTML>
- <HTML>
- <head lang="en">
- <meta charset="UTF-8">
- <title>
- </title>
- </head>
- <body>
- {% csrf_token %}
- <input type="button" onclick="Do();" value="Do it" />
- <script src="/static/plugin/jquery/jquery-1.8.0.js">
- </script>
- <script src="/static/plugin/jquery/jquery.cookie.js">
- </script>
- <script type="text/javascript">
- var csrftoken = $.cookie('csrftoken');
- 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);
- }
- }
- });
- function Do() {
- $.Ajax({
- url: "/app01/test/",
- data: {
- id: 1
- },
- type: 'POST',
- success: function(data) {
- console.log(data);
- }
- });
- }
- </script>
- </body>
- </HTML>
更多: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
- https://www.cnblogs.com/huchong/p/7819296.html
- https://www.cnblogs.com/yuanchenqi/articles/9036479.html
- (自己的学习笔记, 不喜勿喷, 谢谢)
来源: https://www.cnblogs.com/wj-1314/p/10947508.html