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语言中的按位运算符
Nov 26 Python
Python Web框架Flask中使用百度云存储BCS实例
Feb 08 Python
Python基于PycURL自动处理cookie的方法
Jul 25 Python
详解Python迭代和迭代器
Mar 28 Python
详解python之多进程和进程池(Processing库)
Jun 09 Python
Python WXPY实现微信监控报警功能的代码
Oct 20 Python
python excel使用xlutils类库实现追加写功能的方法
May 02 Python
pandas.DataFrame选取/排除特定行的方法
Jul 03 Python
使用python对多个txt文件中的数据进行筛选的方法
Jul 10 Python
Python获取、格式化当前时间日期的方法
Feb 10 Python
python GUI库图形界面开发之PyQt5信号与槽的高级使用技巧装饰器信号与槽详细使用方法与实例
Mar 06 Python
Python xlrd/xlwt 创建excel文件及常用操作
Sep 24 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
浅析PHP页面局部刷新功能的实现小结
2013/06/21 PHP
php用户注册页面利用js进行表单验证具体实例
2013/10/17 PHP
PHP生成指定随机字符串的简单实现方法
2015/04/01 PHP
PHP Opcache安装和配置方法介绍
2015/05/28 PHP
浅谈Yii乐观锁的使用及原理
2017/07/25 PHP
PHP实现动态获取函数参数的方法示例
2018/04/02 PHP
PHP的mysqli_stmt_init()函数讲解
2019/01/24 PHP
基于jquery的tab切换 js原理
2010/04/01 Javascript
JavaScript中的几个关键概念的理解-原型链的构建
2011/05/12 Javascript
JavaScript中prototype为对象添加属性的误区介绍
2013/10/15 Javascript
jQuery的each终止或跳过示例代码
2013/12/12 Javascript
Javascript 浮点运算精度问题分析与解决
2014/03/26 Javascript
Javascript动态引用CSS文件的2种方法介绍
2014/06/06 Javascript
常用jQuery选择器总结
2014/07/11 Javascript
浅谈被jQuery抛弃的函数及替代函数
2015/05/03 Javascript
Javascript中replace()小结
2015/09/30 Javascript
JS实现模拟百度搜索“2012世界末日”网页地震撕裂效果代码
2015/10/31 Javascript
JS实现单击输入框弹出选择框效果完整实例
2015/12/14 Javascript
js日期相关函数dateAdd,dateDiff,dateFormat等介绍
2016/09/24 Javascript
浅谈JavaScript的计时器对象
2016/12/26 Javascript
JS同步、异步、延迟加载的方法
2018/05/05 Javascript
微信小程序实现bindtap等事件传参
2019/04/08 Javascript
vue学习之Vue-Router用法实例分析
2020/01/06 Javascript
[01:06:43]完美世界DOTA2联赛PWL S3 PXG vs GXR 第二场 12.19
2020/12/24 DOTA
python list语法学习(带例子)
2013/11/01 Python
在python中用print()输出多个格式化参数的方法
2019/07/16 Python
python实现随机加减法生成器
2020/02/24 Python
Python Json数据文件操作原理解析
2020/05/09 Python
请编程遍历页面上所有 TextBox 控件并给它赋值为 string.Empty
2015/12/03 面试题
财务总监管理职责范文
2014/03/09 职场文书
群众路线剖析材料(四风问题)
2014/10/08 职场文书
毕业生对母校寄语
2015/02/26 职场文书
2016大学生社会实践单位评语
2015/12/01 职场文书
阿里云Nginx配置https实现域名访问项目(图文教程)
2021/03/31 Servers
Mysql数据库中datetime、bigint、timestamp来表示时间选择,谁来存储时间效率最高
2021/08/23 MySQL
关于JavaScript 中 if包含逗号表达式
2021/11/27 Javascript