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完全新手教程
Feb 08 Python
使用python实现拉钩网上的FizzBuzzWhizz问题示例
May 05 Python
Python socket编程实例详解
May 27 Python
Python实现的文本简单可逆加密算法示例
May 18 Python
基于scrapy的redis安装和配置方法
Jun 13 Python
对numpy数据写入文件的方法讲解
Jul 09 Python
Pandas中DataFrame的分组/分割/合并的实现
Jul 16 Python
Python hashlib模块加密过程解析
Nov 05 Python
使用pandas的box_plot去除异常值
Dec 10 Python
python、PyTorch图像读取与numpy转换实例
Jan 13 Python
执行Python程序时模块报错问题
Mar 26 Python
python中pow函数用法及功能说明
Dec 04 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取进制余数函数代码
2012/01/19 PHP
php 的加密函数 md5,crypt,base64_encode 等使用介绍
2012/04/09 PHP
深入php处理整数函数的详解
2013/06/09 PHP
ueditor 1.2.6 使用方法说明
2013/07/24 PHP
Yii框架调试心得--在页面输出执行sql语句
2014/12/25 PHP
PHP开发之归档格式phar文件概念与用法详解【创建,使用,解包还原提取】
2017/11/17 PHP
Javascript Math对象
2009/08/13 Javascript
MooTools 页面滚动浮动层智能定位实现代码
2011/08/23 Javascript
jQuery实现菜单感应鼠标滑动动画效果的方法
2015/02/28 Javascript
vue.js指令v-model实现方法
2016/12/05 Javascript
微信小程序实现卡片层叠滑动效果
2019/06/21 Javascript
解决layer弹出层自适应页面大小的问题
2019/09/16 Javascript
jQuery鼠标滑过横向时间轴样式(代码详解)
2019/11/01 jQuery
Vuex中的Mutations的具体使用方法
2020/06/01 Javascript
原生js实现自定义难度的扫雷游戏
2021/01/22 Javascript
详解Python中的循环语句的用法
2015/04/09 Python
Scrapy-redis爬虫分布式爬取的分析和实现
2017/02/07 Python
Python中常见的异常总结
2018/02/20 Python
python寻找list中最大值、最小值并返回其所在位置的方法
2018/06/27 Python
浅谈python锁与死锁问题
2020/08/14 Python
python实现启动一个外部程序,并且不阻塞当前进程
2020/12/05 Python
css3实现超立体3D图片侧翻倾斜效果
2014/04/16 HTML / CSS
基于HTML5的WebSocket的实例代码
2018/08/15 HTML / CSS
html5 利用重力感应实现摇一摇换颜色可用来做抽奖等等
2014/05/07 HTML / CSS
基于HTML5代码实现折叠菜单附源码下载
2015/11/27 HTML / CSS
canvas中普通动效与粒子动效的实现代码示例
2019/01/03 HTML / CSS
康帕斯酒店预订:Compass Hospitality(支持中文)
2018/08/23 全球购物
医药工作者的求职信范文
2013/09/21 职场文书
公司前台辞职报告
2014/01/19 职场文书
大学中国梦演讲稿
2014/04/23 职场文书
三八妇女节趣味活动方案
2014/08/23 职场文书
校长个人总结
2015/03/03 职场文书
乡镇司法所2015年度工作总结
2015/10/14 职场文书
Python如何导出导入所有依赖包详解
2021/06/08 Python
Python中的 enumerate和zip详情
2022/05/30 Python
MySQL深分页问题解决思路
2022/12/24 MySQL