Django Middleware简介
1 前言
Django使用非常熟练了,各种API接口不在话下,全都搞定。为方便定位问题在每个API接口的的开始和返回的地方都加上了log打印,记录入参和返回值。
但是这样有一个问题,需要每个API接口都要写一遍,非常的不Pythonic,有没有更好的方法呢?
如果大家对装饰器熟悉的话,会想到这个方法。写一个log_wrapper,在每个API的函数上写上@log_wrapper,这样看起来比较美观了。但是有一个问题,如果那个接口忘记使用这个装饰器了,日志就无法记录了。
在Django中有没有更好的解决方案呢,答案是肯定的。
2 Django HTTP处理流程
2.1 HTTP处理流程
我们先来熟悉下Django的HTTP处理流程,详细的处理流程请看下图。
每个HTTP请求过来,先经过request middleware处理,如果没有异常,则交由URLConf处理(即urls.py文件)来匹配URL,然后到view middleware处理,最后到具体的view业务函数。
HTTP请求中间处理的每个步骤,如果发生异常,直接返回response,而不是到下一个流程中。可以看到图中每个步骤都可以返回response。
2.2 Request和Response对象
在Django中,一个 HTTP 请求,首先被转化成一个 HttpRequest 对象,然后该对象被传递给 Request middleware处理,如果该middleware返回了Response,则生成一个Response对象,里面包含所有的HTTP 响应元素。
HttpRequest对象的属性
Attribute |
Description |
path |
请求页面的全路径,不包括域名—例如, "/music/bands/the_beatles/"。 |
method |
请求中使用的HTTP方法的字符串表示。全大写表示。例如: if request.method == 'GET': |
GET |
包含所有HTTP GET参数的类字典对象。参见QueryDict 文档。 |
POST |
包含所有HTTP POST参数的类字典对象。参见QueryDict 文档。 服务器收到空的POST请求的情况也是有可能发生的。也就是说,表单form通过HTTP POST方法提交请求,但是表单中可以没有数据。因此,不能使用语句if request.POST来判断是否使用HTTP POST方法;应该使用if request.method == "POST" (参见本表的method属性)。 注意: POST不包括file-upload信息。参见FILES属性。 |
REQUEST |
为了方便,该属性是POST和GET属性的集合体,但是有特殊性,先查找POST属性,然后再查找GET属性。借鉴PHP’s $_REQUEST。 例如,如果GET = 强烈建议使用GET and |
COOKIES |
包含所有cookies的标准Python字典对象。Keys和values都是字符串。参见第12章,有关于cookies更详细的讲解。 |
FILES |
包含所有上传文件的类字典对象。FILES中的每个Key都是<input
注意:只有在请求方法是POST,并且请求页面中<form>有enctype="multipart/form-data"属性时FILES才拥有数据。否则,FILES 是一个空字典。 |
META |
包含所有可用HTTP头部信息的字典。
META 中这些头加上前缀HTTP_最为Key, 例如:
|
user |
是一个django.contrib.auth.models.User if request.user.is_authenticated(): 只有激活Django中的AuthenticationMiddleware时该属性才可用 关于认证和用户的更详细讲解,参见第12章。 |
session |
唯一可读写的属性,代表当前会话的字典对象。只有激活Django中的session支持时该属性才可用。 参见第12章。 |
raw_post_data |
原始HTTP POST数据,未解析过。 |
Response对象
Attribute |
Description |
path |
请求页面的全路径,不包括域名—例如, "/music/bands/the_beatles/"。 |
method |
请求中使用的HTTP方法的字符串表示。全大写表示。例如: if request.method == 'GET': |
GET |
包含所有HTTP GET参数的类字典对象。 |
status_code |
HTTP 状态码 |
content |
返回的内容 |
其他内容略 |
Django的Response返回类型比较多,基本都是继承了HTTPResponse这个类。
3
Django中间件介绍和分析
根据上面的HTTP处理流程图,有一个想法,把每个请求和返回的日志记录加到middleware中,这样每个HTTP的请求和返回都可以记录到。并且不影响业务函数的编写和美观。
该如何使用middleware呢?
我们来看下Django的中间件是如何处理HTTP请求的。如下图。
上面中间件不是每个项目必须的,在Django中是可选的,配置在settings.py文件的MIDDLEWARE配置中。可以根据需要进行增加和删除。
3.1
Middleware使用
在Django中,middleware的配置在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',
]
Middleware调用是有序和分层的,request是从上到下,response则相反。
我们现在看下django自带的中间件是什么样子的。
查看django源码: site-packages\django\middleware\common.py中的CommonMiddleware的类。
发现继承了MiddlewareMixin类。
class CommonMiddleware(MiddlewareMixin): response_redirect_class = http.HttpResponsePermanentRedirect
def process_request(self, request): """
Check for denied User-Agents and rewrite the URL based on
settings.APPEND_SLASH and settings.PREPEND_WWW
"""
# Check for denied User-Agents
if 'HTTP_USER_AGENT' in request.META:
for user_agent_regex in settings.DISALLOWED_USER_AGENTS:
if user_agent_regex.search(request.META['HTTP_USER_AGENT']):
raise PermissionDenied('Forbidden user agent')
# Check for a redirect based on settings.PREPEND_WWW host = request.get_host()
must_prepend = settings.PREPEND_WWW and host and not host.startswith('www.')
redirect_url = ('%s://www.%s' % (request.scheme, host)) if must_prepend else '' # Check if a slash should be appended
if self.should_redirect_with_slash(request):
path = self.get_full_path_with_slash(request)
else:
path = request.get_full_path()
# Return a redirect if necessary
if redirect_url or path != request.get_full_path():
redirect_url += path
return self.response_redirect_class(redirect_url) def should_redirect_with_slash(self, request):
"""
Return True if settings.APPEND_SLASH is True and appending a slash to
the request path turns an invalid path into a valid one.
"""
if settings.APPEND_SLASH and not request.get_full_path().endswith('/'):
urlconf = getattr(request, 'urlconf', None)
return (
not is_valid_path(request.path_info, urlconf) and
is_valid_path('%s/' % request.path_info, urlconf)
)
return False def get_full_path_with_slash(self, request):
"""
Return the full path of the request with a trailing slash appended.
Raise a RuntimeError if settings.DEBUG is True and request.method is
POST, PUT, or PATCH.
""" new_path = request.get_full_path(force_append_slash=True)
if settings.DEBUG and request.method in ('POST', 'PUT', 'PATCH'):
raise RuntimeError(
"You called this URL via %(method)s, but the URL doesn't end "
"in a slash and you have APPEND_SLASH set. Django can't "
"redirect to the slash URL while maintaining %(method)s data. "
"Change your form to point to %(url)s (note the trailing "
"slash), or set APPEND_SLASH=False in your Django settings." % {
'method': request.method,
'url': request.get_host() + new_path,
}
)
return new_path def process_response(self, request, response):
"""
Calculate the ETag, if needed.
When the status code of the response is 404, it may redirect to a path
with an appended slash if should_redirect_with_slash() returns True.
"""
# If the given URL is "Not Found", then check if we should redirect to
# a path with a slash appended. if response.status_code == 404:
if self.should_redirect_with_slash(request):
return self.response_redirect_class(self.get_full_path_with_slash(request)) if settings.USE_ETAGS:
if not response.has_header('ETag'):
set_response_etag(response)
if response.has_header('ETag'):
return get_conditional_response(
request,
etag=unquote_etag(response['ETag']),
response=response,
) return response
查看Mixin类,有两个函数:
self.process_request(request)
self.porcess_response(request, response)
这两个函数:
class MiddlewareMixin(object):
def __init__(self, get_response=None):
self.get_response = get_response
super(MiddlewareMixin, self).__init__() def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
if not response:
response = self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response
有了这些信息,我们来实现自己的middleware
3.2 自定义日志middleware
在apps/common/文件夹下创建middleware.py文件
from django.utils.deprecation import MiddlewareMixin
from rest_framework.response import Response
from django.http import JsonResponse
import logging jira_log = logging.getLogger('debug') class Reponse(MiddlewareMixin): def process_response(self, request, response):
if isinstance(response, Response) and response.get('content-type') == 'application/json' and \
isinstance(response.data, dict):
response.data['msg'] = '成功'
response.content = response.rendered_content
# self._convert_data(response, 200)
if isinstance(response, JsonResponse):
jira_log.debug(response.content)
return response @staticmethod
def process_request(request):
if request.method == 'POST':
jira_log.debug(request.body)
if request.method == 'GET':
jira_log.debug(request.GET)
setattr(request, '_dont_enforce_csrf_checks', True)
可以看到,我们自己实现了process_request 、process_response 两个函数,增加了日志打印。这样每个请求和返回都有日志记录。
2017-12-13 14:12:04,520 [middleware.py: 39][Thread-7 ][debug ]DEBUG request: b'{"a":1, "b":2}'
2017-12-13 14:12:04,530 [middleware.py: 31][Thread-7 ][debug ]DEBUG response {'status': 200, 'msg': '成功'}
2017-12-13 14:12:21,071 [middleware.py: 41][Thread-8 ][debug ]DEBUG request: <QueryDict: {}>
2017-12-13 14:12:21,074 [middleware.py: 31][Thread-8 ][debug ]DEBUG response {'status': 200, 'msg': '成功'}
2017-12-13 14:12:44,432 [middleware.py: 41][Thread-9 ][debug ]DEBUG request: <QueryDict: {}>
2017-12-13 14:12:44,434 [middleware.py: 33][Thread-9 ][debug ]DEBUG response b'{"status": 200, "msg": "\\u6210\\u529f 1"}'
3.3 使用自定义的middleware
在middleware的配置中添加自定义middleware类即可。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', # 自定义的middleware,记录日志,
'apps.common.middleware.Reponse',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
PS:中间放置的位置是顺序是有要求的,request请求是按照从上到下的顺序处理。放置的位置要根据中间件的功能和实际要求来分析。
3.4 实测日志记录
发送几个请求来查看日志的记录情况:
2017-12-13 14:12:04,520 [middleware.py: 39][Thread-7 ][debug ]DEBUG request: b'{"a":1, "b":2}' 2017-12-13 14:12:04,530 [middleware.py: 31][Thread-7 ][debug ]DEBUG response {'status': 200, 'msg': '成功'} 2017-12-13 14:12:21,071 [middleware.py: 41][Thread-8 ][debug ]DEBUG request: <QueryDict: {}> 2017-12-13 14:12:21,074 [middleware.py: 31][Thread-8 ][debug ]DEBUG response {'status': 200, 'msg': '成功'} 2017-12-13 14:12:44,432 [middleware.py: 41][Thread-9 ][debug ]DEBUG request: <QueryDict: {}> 2017-12-13 14:12:44,434 [middleware.py: 33][Thread-9 ][debug ]DEBUG response b'{"status": 200, "msg": "\\u6210\\u529f 1"}'
日志能准确的记录请求的参数和返回的内容。当然也可以记录更详细的情况,这个可以根据需要进行添加。
4 中间件之process_view
Process_view处理也是middleware的一个函数,专门用来处理view,访问顺序在request之后,业务view函数之前。
process_view(self, request, callback, callback_args, callback_kwargs)
在middleware文件中的的代码进行更改:增加process_view代码,并打印日志
发现middleware的处理顺序为request-view-response。
如果有多个middleware,则日志打印顺序为:
- Request1
- Request2
- Request3
- middlewareView1
- middlewareView2
- middlewareView3
- API view
- Response3
- Response2
- Response1
有兴趣的同学可以自己写下看看。
5 Middleware应用
根据Django官方定义,middleware是框架的钩子。
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.
了解了middleware的原理,我们可以做许多事情。
5.1 规范化response
很多项目使用了rest-framework框架,框架对于异常的处理有两点与我们规范不一致:
l status_code是400,与我们定义的规范不符合,
l 处理成功时,返回json中的msg字段值为:成功。
l 数据都放到data字段中
如果我们手动处理,则每个人都要来适配这样的格式。
感谢成宇为大家实现了response的格式化的middleware。简化了大家的工作。
例如在众测平台,就使用middleware来规范response输出,把不符合规范的格式整理为:
{ "msg": "成功",
"status": 200,
"data": {
"data": "our data"
}
}
5.2 用来记录响应时间
可以根据请求和响应时间,记录接口的性能。
5.3 中间件其他作用
- 可以用来整理请求,统一添加某个字段。
- 用来整理返回,统一添加某个字段。
- 还可以动态添加访问黑名单,判断IP地址是否可以访问。
- 收集访问来源类型,是PC还是手机浏览器。
这些都可以和具体的业务解耦,全局的配置。
当然有些功能在nginx配置比较好。例如IP黑名单。
6 参考资料
编号 |
资料名称 |
链接 |
1 |
Django官方文档 |
https://docs.djangoproject.com/en/2.0/ref/ |
2 |
Request和response对象 |
http://blog.csdn.net/liu_yanna/article/details/50174851 |
3 |
Django底层剖析之一次请求到响应的整个流程 |
https://www.cnblogs.com/wanghzh/p/5831992.html |
Django Middleware简介的更多相关文章
- Django 中间件简介
Django 中间件简介 django 中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法. ...
- Python学习(三十)—— Django框架简介
转载自:http://www.cnblogs.com/liwenzhou/p/8296964.html Django框架简介 一.MVC框架和MTV框架(了解即可) MVC,全名是Model View ...
- Django MiddleWare初识
一.Django 中间件介绍 中间件是一个用来处理Django的请求和响应的框架级别的钩子.它是一个轻量.低级别的插件系统,用于在全局范围内改变Django的输入和输出.每个中间件组件都负责做一些特定 ...
- Django 中CSRF中间件 'django.middleware.csrf.CsrfViewMiddleware',
1.Django中CSRF中间件的工作原理及form表单提交需要添加{% csrf_token %}防止出现403错误 CSRF # 表示django全局发送post请求均需要字符串验证功能:防止跨站 ...
- Django框架简介及模板Template,filter
Django框架简介 MVC框架和MTV框架 MVC,全名是Model View Controller,是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model).视图(View) ...
- Django框架简介与使用注意事项
一.Django框架简介 MVC框架和MTV框架 MVC框架 MVC,全名是Model View Controller,是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model). ...
- Django middleware (中间件)
关于中间价: django 中的中间件其实就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法. 在django项目的settings中,有一个 MIDDLE ...
- python 之 Django框架(Django框架简介、视图装饰器、request对象、Response对象)
12.33 Django框架简介: MVC,全名是Model View Controller,是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model).视图(View)和控制器( ...
- django middleware介绍
Middleware Middleware是一个镶嵌到django的request/response处理机制中的一个hooks框架.它是一个修改django全局输入输出的一个底层插件系统. 每个中间件 ...
随机推荐
- 聊聊Vue.js的template编译
写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:https://github.com/a ...
- B站标题/子标题/url爬取示例(requests+re)
#coding:utf-8 __author__ = "zhoumi" 3 import requests import re import urllib ''' 本文档目的在于获 ...
- Java经典编程题50道之三十七
有n个人围成一圈,顺序排号.从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位. public class Example37 { public static v ...
- git命令 高级
Git 分支 - 分支的删除 git删除本地分支 git branch -D br git删除远程分支 git push origin :br (origin 后面有空格) clone服务器上的资源 ...
- Linux上查看用户名和组并把特定用户放到特定的组之下
cat /etc/passwd //查看所有的用户信息 cat /etc/passwd|grep 用户名 //查看某一个用户的信息 cat /etc/group ...
- 批标准化(Batch Norm)
BN作用: 加速收敛 控制过拟合,可以少用或不用Dropout和正则 降低网络对初始化权重不敏感 允许使用较大的学习率 一.如何加速收敛? 通过归一化输入值/隐藏单元值,以获得类似的范围值,可加速学习 ...
- JPA实体的常用注解
@Entity 标注于实体类上,通常和@Table是结合使用的,代表是该类是实体类@Table 标注于实体类上,表示该类映射到数据库中的表,没有指定名称的话就表示与数据库中表名为该类的简单类名的表名相 ...
- 中小研发团队架构实践之微服务MSA
一.MSA简介 1.1.MSA是什么 微服务架构MSA是Microservice Architecture的简称,它是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相通讯.互相配合, ...
- jsencrypt参数前端加密c#解密
写程序时一般是通过form表单或者ajax方式将参数提交到服务器进行验证,如何防止提交的请求不被抓包后串改,虽然无法说绝对安全却给非法提交提高了难度,本篇采用jsencypt在前端进行加密的并且用C# ...
- 第3章 PCI总线的数据交换
PCI Agent设备之间,以及HOST处理器和PCI Agent设备之间可以使用存储器读写和I/O读写等总线事务进行数据传送.在大多数情况下,PCI桥不直接与PCI设备或者HOST主桥进行数据交换, ...