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中实现定制类的特殊方法总结
Sep 28 Python
python对指定目录下文件进行批量重命名的方法
Apr 18 Python
python excel使用xlutils类库实现追加写功能的方法
May 02 Python
Python读写文件基础知识点
Jun 10 Python
python os模块在系统管理中的应用
Jun 22 Python
Python3实现建造者模式的示例代码
Jun 28 Python
python反爬虫方法的优缺点分析
Nov 25 Python
python使用正则表达式匹配txt特定字符串(有换行)
Dec 09 Python
解决import tensorflow导致jupyter内核死亡的问题
Feb 06 Python
python3 实现mysql数据库连接池的示例代码
Apr 17 Python
Python机器学习之基于Pytorch实现猫狗分类
Jun 08 Python
python数字图像处理之图像的批量处理
Jun 28 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
解析VS2010利用VS.PHP插件调试PHP的方法
2013/07/19 PHP
Django中的cookie与session操作实例代码
2017/08/17 PHP
PHP7.1实现的AES与RSA加密操作示例
2018/06/15 PHP
Laravel框架实现的使用smtp发送邮件功能示例
2019/03/12 PHP
解决Laravel5.2 Auth认证退出失效的问题
2019/10/14 PHP
javascript setTimeout和setInterval 的区别
2009/12/08 Javascript
给jqGrid数据行添加修改和删除操作链接(之一)
2011/11/04 Javascript
jquery选择器大全 全面详解jquery选择器
2014/03/06 Javascript
JavaScript中合并数组的N种方法
2014/09/16 Javascript
js读写json文件实例代码
2014/10/21 Javascript
javascript中数组array及string的方法总结
2014/11/28 Javascript
Underscore.js常用方法总结
2015/02/28 Javascript
jquery+ajax实现注册实时验证实例详解
2015/12/08 Javascript
JQuery中Ajax()的data参数类型实例分析
2015/12/15 Javascript
浅析JavaScript中作用域和作用域链
2016/12/06 Javascript
webpack实现热更新(实施同步刷新)
2017/07/28 Javascript
AngularJS 购物车全选/取消全选功能的实现方法
2017/08/14 Javascript
详解webpack与SPA实践之开发环境搭建
2017/12/18 Javascript
360doc网站不登录就无法复制内容的解决方法
2018/01/27 Javascript
深入浅析vue-cli@3.0 使用及配置说明
2019/05/08 Javascript
vue中keep-alive、activated的探讨和使用详解
2020/07/26 Javascript
python修改注册表终止360进程实例
2014/10/13 Python
使用Python脚本来控制Windows Azure的简单教程
2015/04/16 Python
对python 判断数字是否小于0的方法详解
2019/01/26 Python
TensorFlow dataset.shuffle、batch、repeat的使用详解
2020/01/21 Python
基于matplotlib中ion()和ioff()的使用详解
2020/06/16 Python
Python如何给你的程序做性能测试
2020/07/29 Python
Python爬虫使用bs4方法实现数据解析
2020/08/25 Python
详解python with 上下文管理器
2020/09/02 Python
Pycharm自带Git实现版本管理的方法步骤
2020/09/18 Python
HTML5中使用postMessage实现两个网页间传递数据
2016/06/22 HTML / CSS
伦敦高达60%折扣的钻石珠宝商:Purely Diamonds
2018/06/24 全球购物
空字符串(“”)和null的区别
2012/11/13 面试题
优秀员工自荐信范文
2013/10/05 职场文书
老干部座谈会主持词
2015/07/03 职场文书
golang日志包logger的用法详解
2021/05/05 Golang