django从请求到响应的过程深入讲解


Posted in Python onAugust 01, 2018

django启动

我们在启动一个django项目的时候,无论你是在命令行执行还是在pycharm直接点击运行,其实都是执行'runserver'的操作,而ruserver是使用django自带的的web server,主要用于开发和调试中,而在正式的环境中,一般会使用nginx+uwsgi模式。

无论是哪种方式,当启动一个项目,都会做2件事:

  • 创建一个WSGIServer类的实例,接受用户的请求。
  • 当一个用户的http请求到达的时,为用户指定一个WSGIHandler,用于处理用户请求与响应,这个Handler是处理整个request的核心。

WSGI

WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,也不用于与程序交互的API,更不是代码,而只是定义了一个接口,用于描述web server如何与web application通信的规范。
当客户端发送一次请求后,最先处理请求的实际上是 web 服务器就是我们经常说的 nginx、Apache 这类的 web 服务器,然后web服务器再把请求交给web应用程序(如django)处理,这中间的中介就是WSGI,它把 web 服务器和 web 框架 (Django) 连接起来。

django从请求到响应的过程深入讲解

简单介绍一下WSGI的一些内容,它规定应用是可调用对象(函数/方法),然后它接受2个固定参数:一个是含有服务器端的环境变量,另一个是可调用对象,这个对象用来初始化响应,给响应加上status code状态码和httpt头部,并且返回一个可调用对象。可以看个简单的例子

# 这段代码来自python核心编程
def simplr_wsgi_app(environ, start_response):
 # 固定两个参数,django中也使用同样的变量名
 status = '200 OK'
 headers = [{'Content-type': 'text/plain'}]
 # 初始化响应, 必须在返回前调用
 start_response(status, headers)
 # 返回可迭代对象
 return ['hello world!']

django中,实现同样逻辑的是通过WSGIHandler这个类,下面我们也会重点介绍它!

如果对WSGI与uWSGI有兴趣的,推荐大家看这篇文章,WSGI & uwsgi ,大赞!

中间件基本概念

顾名思义,中间件是位于Web服务器端和Web应用之间的,它可以添加额外的功能。当我们创建一个django项目(通过pycharm),它会自动帮我们设置一些必要的中间件。

MIDDLEWARE_CLASSES = [
 'django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

中间件要么对来自用户的数据进行预处理,然后发送给应用;要么在应用将响应负载返回给用户之前,对结果数据进行一些最终的调整。通俗一点,在django中,中间能够帮我们准备好request这个对象,然后应用可以直接使用request对象获取到各类数据,也帮我们将response添加头部,状态码等。

数据流

当django接受到一个请求时,会初始化一个WSGIHandler,可以在项目下的wsgi.py文件进行跟踪,你就会发现这一个类。

class WSGIHandler(base.BaseHandler):
 def __call__(self, environ, start_response):
 pass

这个类遵循WSGI应用的规定,它接受2个参数:一个是含有服务器端的环境变量,另一个是可调用对象,返回一个可迭代对象。

这个handler控制了从请求到响应的整个过程,主要流程:

django从请求到响应的过程深入讲解

在网上看到另外一张图,更为完整:

django从请求到响应的过程深入讲解

大致几个步骤:

1. 用户通过浏览器请求一个页面 

2. 请求到达Request Middlewares,中间件对request做一些预处理或者直接response请求 

3. URLConf通过urls.py文件和请求的URL找到相应的View 

4. View Middlewares被访问,它同样可以对request做一些处理或者直接返回response 

5. 调用View中的函数 

6. View中的方法可以选择性的通过Models访问底层的数据 

7. 所有的Model-to-DB的交互都是通过manager完成的 

8. 如果需要,Views可以使用一个特殊的Context 

9. Context被传给Template用来生成页面  

    a.Template使用Filters和Tags去渲染输出  

    b.输出被返回到View  

    c.HTTPResponse被发送到Response Middlewares  

    d.任何Response Middlewares都可以丰富response或者返回一个完全不同的response  

    e.Response返回到浏览器,呈现给用户 

中间类中的顺序与方法

django 的中间件类至少含有以下四个方法中的一个:
process_request、 process_view、process_exception、process_response
WSGIHandler通过load_middleware将这个些方法分别添加到_request_middleware、_view_middleware、_response_middleware 和 _exception_middleware四个列表中。

并不是每个中间件都有这4个方法,如果不存在某个方法,那么在加载的过程中,这个类就被跳过。

for middleware_path in settings.MIDDLEWARE_CLASSES:
 ···
 if hasattr(mw_instance, 'process_request'):
 request_middleware.append(mw_instance.process_request)
 if hasattr(mw_instance, 'process_view'):
 self._view_middleware.append(mw_instance.process_view)
 if hasattr(mw_instance, 'process_template_response'):
 self._template_response_middleware.insert(0, mw_instance.process_template_response)
 if hasattr(mw_instance, 'process_response'):
 self._response_middleware.insert(0, mw_instance.process_response)
 if hasattr(mw_instance, 'process_exception'):
 self._exception_middleware.insert(0, mw_instance.process_exception)

我们可以从源码看出,process request 和 process  response的执行加载顺序正好是相反,在循环中,process_request是被append到列表的末尾,而process_request是被insert到最前面的。

django从请求到响应的过程深入讲解

(可能有些情况Comment中间件在Session前面,了解加载的顺序就好了)

process_request

举几个中间件的例子

class CommonMiddleware(object):
# 伪代码
 def process_request(self, request):

 # 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')
 host = request.get_host()

 if settings.PREPEND_WWW and host and not host.startswith('www.'):
 host = 'www.' + host
 pass

CommonMiddleware的process_request主要是判断用户代理是否符合要求以及在完善URL,如增加www或者末尾加/。

class SessionMiddleware(object):
 def process_request(self, request):
 session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
 request.session = self.SessionStore(session_key)

SessionMiddleware的process_request是把session_key从cookies中取出来然后放到request.session中。

class AuthenticationMiddleware(MiddlewareMixin):
 def process_request(self, request):
 assert hasattr(request, 'session'), (
  "The Django authentication middleware requires session middleware "
  "to be installed. Edit your MIDDLEWARE%s setting to insert "
  "'django.contrib.sessions.middleware.SessionMiddleware' before "
  "'django.contrib.auth.middleware.AuthenticationMiddleware'."
 ) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
 request.user = SimpleLazyObject(lambda: get_user(request))

在前面提过,中间件的加载是按照一定顺序(正反序),
AuthenticationMiddleware的process_request方法基于session中间件被加载过了,然后通过request的session,将用户取出来放入到request.user 。

process_request 应该返回 None 或者 HTTPResponse 对象。当返回 None 时,WSGI handler 会继续加载 process_request 里面的方法,如果是后一种情况,那么Handlers会直接加载_response_middleware的列表,然后直接response。

解析 url

当_request_middleware列表中的 process_request 被遍历完,会得到一个经过处理的request对象(加入了request.session,request.user等属性)。

django将按顺序进行对url进行正则匹配,如果匹配不成功,就会抛出异常。如果request的中间件返回None,那么Django会去解析用户请求的URL。

在setting中有一个ROOT_URLCONF,它指向urls.py文件,根据这个文件可以生产一个urlconf,本质上,他就是url与视图函数之间的映射表,然后通过resolver解析用户的url,找到第一个匹配的view。

process_view

经过url的匹配,会获得视图函数以及相关参数。在调用view函数之前,django会先加载_view_middleware中的各个process_view方法。

逐个默认的中间件看了一遍,只看到csrf有这个方法

# 伪代码
class CsrfViewMiddleware(object):

 def process_view(self, request, callback, callback_args, callback_kwargs):

 if getattr(request, 'csrf_processing_done', False):
  return None

 try:
  csrf_token = _sanitize_token(
  request.COOKIES[settings.CSRF_COOKIE_NAME])
  # Use same token next time
  request.META['CSRF_COOKIE'] = csrf_token
 except KeyError:
  csrf_token = None
 if getattr(callback, 'csrf_exempt', False):
  return None
 pass

这个方法的作用是判断cookiers中是否存在csrf的字段,如果不存在,会直接抛出异常,如果存在,返回None。
view中间件和requst中间件一样,必须返回None或一个httpResponse,如果返回一个httpresponse,那么Handlers会直接加载_response_middleware的列表,然后返回HttpResponse,那么Handlers会直接加载_response_middleware的列表,然后直接response

执行view逻辑

view函数需要满足:

  1. 基于函数(FBV)或者基于类的(CVB)的视图。
  2. 接受的参数第一个必须为request,并且需要返回一个response对象。

如果视图函数抛出一个异常,Handler 将会循环遍历_exception_middleware 列表,如果有一个异常被抛出,后面的 process_exception 将不会被执行。

process_response

在这个阶段,我们得到了一个 HTTPResponse 对象,这个对象可能是 process_view 返回的,也可能是视图函数返回的。现在我们将循环访问响应中间件。这是中间件调整数据的最后的机会。举个例子:

class XFrameOptionsMiddleware(object):

 def process_response(self, request, response):
 # Don't set it if it's already in the response
 if response.get('X-Frame-Options') is not None:
  return response

 # Don't set it if they used @xframe_options_exempt
 if getattr(response, 'xframe_options_exempt', False):
  return response

 response['X-Frame-Options'] = self.get_xframe_options_value(request,
         response)
 return response

XFrameOptionsMiddleware将X-Frame-Options加入到response当中,防止网站被嵌套、被劫持。

class CsrfViewMiddleware(object):
 def process_response(self, request, response):
 if getattr(response, 'csrf_processing_done', False):
  return response

 if not request.META.get("CSRF_COOKIE_USED", False):
  return response

 # Set the CSRF cookie even if it's already set, so we renew
 # the expiry timer.
 response.set_cookie(settings.CSRF_COOKIE_NAME,
    request.META["CSRF_COOKIE"],
    max_age=settings.CSRF_COOKIE_AGE,
    domain=settings.CSRF_COOKIE_DOMAIN,
    path=settings.CSRF_COOKIE_PATH,
    secure=settings.CSRF_COOKIE_SECURE,
    httponly=settings.CSRF_COOKIE_HTTPONLY
    )
 # Content varies with the CSRF cookie, so set the Vary header.
 patch_vary_headers(response, ('Cookie',))
 response.csrf_processing_done = True
 return response

CsrfViewMiddleware在response中设置csrf cookies

最后

当response的中间件加载完,系统在返回之前会调用WSGI服务器端传过来的start_response方法对象,初始化响应,然后进行response响应。

总结

本文重点在于:

  1. django启动时,启动了一个WSGIserver以及为每个请求的用户生成一个handler。
  2. 理解WSGI协议,并且WSGIHandler这个类控制整个请求到响应的流程,以及整个流程的基本过程。
  3. 中间件的概念,以及每一个process_request, process_response, process_view, process_exception方法在哪个步骤发挥着什么样的作用。
  4. 中间价的执行时有顺序的,request与view是按照顺序去执行的,而response和exception是反序的,这一步实在WSGIHandler在加载到它的各个列表的时候完成的。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

参考博客:

  1,Django教程笔记:六、中间件middleware
  2,做python Web开发你要理解:WSGI & uwsgi
  3,从请求到响应 django 都做了哪些处理  
  4,django从请求到返回都经历了什么

Python 相关文章推荐
Python开发WebService系列教程之REST,web.py,eurasia,Django
Jun 30 Python
Python标准库之collections包的使用教程
Apr 27 Python
Python简单的制作图片验证码实例
May 31 Python
python先序遍历二叉树问题
Nov 10 Python
Django分页查询并返回jsons数据(中文乱码解决方法)
Aug 02 Python
把pandas转换int型为str型的方法
Jan 29 Python
Python利用matplotlib做图中图及次坐标轴的实例
Jul 08 Python
Python OpenCV视频截取并保存实现代码
Nov 30 Python
如何基于Python实现电子邮件的发送
Dec 16 Python
解决Keras中循环使用K.ctc_decode内存不释放的问题
Jun 29 Python
python json.dumps() json.dump()的区别详解
Jul 14 Python
python利用文件时间批量重命名照片和视频
Feb 09 Python
python3.6的venv模块使用详解
Aug 01 #Python
从请求到响应过程中django都做了哪些处理
Aug 01 #Python
Python WSGI的深入理解
Aug 01 #Python
Django进阶之CSRF的解决
Aug 01 #Python
python3利用venv配置虚拟环境及过程中的小问题小结
Aug 01 #Python
mvc框架打造笔记之wsgi协议的优缺点以及接口实现
Aug 01 #Python
python爬虫自动创建文件夹的功能
Aug 01 #Python
You might like
自己动手做一个SQL解释器
2006/10/09 PHP
JS中字符问题(二进制/十进制/十六进制及ASCII码之间的转换)
2008/11/03 Javascript
javascript form 验证函数 弹出对话框形式
2009/06/23 Javascript
js仿百度有啊通栏展示效果实现代码
2013/05/28 Javascript
javascript使用shift+click实现选择和反选checkbox的方法
2015/05/04 Javascript
jQuery动态效果显示人物结构关系图的方法
2015/05/07 Javascript
jquery实现红色竖向多级向右展开的导航菜单效果
2015/08/31 Javascript
js右下角弹出提示框示例代码
2016/01/12 Javascript
node模块机制与异步处理详解
2016/03/13 Javascript
JS IOS/iPhone的Safari浏览器不兼容Javascript中的Date()问题如何解决
2016/11/11 Javascript
BootStrap树状图显示功能
2016/11/24 Javascript
js基于myFocus实现轮播图效果
2017/02/14 Javascript
浅谈SpringMVC中post checkbox 多选框value的值(隐藏域方式)
2018/01/08 Javascript
浅谈vue项目重构技术要点和总结
2018/01/23 Javascript
p5.js入门教程和基本形状绘制
2018/03/15 Javascript
详解webpack loader和plugin编写
2018/10/12 Javascript
Angular使用ControlValueAccessor创建自定义表单控件
2019/03/08 Javascript
Vue路由守卫及页面登录权限控制的设置方法(两种)
2020/03/31 Javascript
jQuery插件simplePagination的使用方法示例
2020/04/28 jQuery
处理JavaScript值为undefined的7个小技巧
2020/07/28 Javascript
jQuery实现可以扩展的日历
2020/12/01 jQuery
[02:56]《DAC最前线》之国外战队抵达上海备战亚洲邀请赛
2015/01/28 DOTA
python 生成器协程运算实例
2017/09/04 Python
python利用rsa库做公钥解密的方法教程
2017/12/10 Python
Pandas之Dropna滤除缺失数据的实现方法
2019/06/25 Python
Python人工智能之路 之PyAudio 实现录音 自动化交互实现问答
2019/08/13 Python
Python逐行读取文件内容的方法总结
2020/02/14 Python
pytorch掉坑记录:model.eval的作用说明
2020/06/23 Python
欧缇丽英国官方网站:Caudalie英国
2016/08/17 全球购物
北美个性化礼品商店:Things Remembered
2018/06/12 全球购物
Under Armour安德玛意大利官网:美国高端运动科技品牌
2020/01/16 全球购物
农民工创业典型事迹
2014/01/25 职场文书
《月亮湾》教学反思
2014/04/14 职场文书
学生会宣传部部长竞选演讲稿
2014/04/25 职场文书
党员贯彻十八大精神思想汇报范文
2014/10/25 职场文书
2015年父亲节活动总结
2015/02/12 职场文书