分析Python的Django框架的运行方式及处理流程


Posted in Python onApril 08, 2015

之前在网上看过一些介绍Django处理请求的流程和Django源码结构的文章,觉得了解一下这些内容对开发Django项目还是很有帮助的。所以,我按照自己的逻辑总结了一下Django项目的运行方式和对Request的基本处理流程。

一、Django的运行方式

运行Django项目的方法很多,这里主要介绍一下常用的方法。一种是在开发和调试中经常用到runserver方法,使用Django自己的web server;另外一种就是使用fastcgi,uWSGIt等协议运行Django项目,这里以uWSGIt为例。

1、runserver方法

runserver方法是调试Django时经常用到的运行方式,它使用Django自带的WSGI Server运行,主要在测试和开发中使用,使用方法如下:

Usage: manage.py runserver [options] [optional port number, or ipaddr:port]
# python manager.py runserver  # default port is 8000
# python manager.py runserver 8080
# python manager.py runserver 127.0.0.1:9090

看一下manager.py的源码,你会发现上面的命令其实是通过Django的execute_from_command_line方法执行了内部实现的runserver命令,那么现在看一下runserver具体做了什么。。

看了源码之后,可以发现runserver命令主要做了两件事情:

    1). 解析参数,并通过django.core.servers.basehttp.get_internal_wsgi_application方法获取wsgi handler;

    2). 根据ip_address和port生成一个WSGIServer对象,接受用户请求

get_internal_wsgi_application的源码如下:
def get_internal_wsgi_application():
  """
  Loads and returns the WSGI application as configured by the user in
  ``settings.WSGI_APPLICATION``. With the default ``startproject`` layout,
  this will be the ``application`` object in ``projectname/wsgi.py``.
 
  This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
  for Django's internal servers (runserver, runfcgi); external WSGI servers
  should just be configured to point to the correct application object
  directly.
 
  If settings.WSGI_APPLICATION is not set (is ``None``), we just return
  whatever ``django.core.wsgi.get_wsgi_application`` returns.
 
  """
  from django.conf import settings
  app_path = getattr(settings, 'WSGI_APPLICATION')
  if app_path is None:
    return get_wsgi_application()
 
  return import_by_path(
    app_path,
    error_prefix="WSGI application '%s' could not be loaded; " % app_path
  )

通过上面的代码我们可以知道,Django会先根据settings中的WSGI_APPLICATION来获取handler;在创建project的时候,Django会默认创建一个wsgi.py文件,而settings中的WSGI_APPLICATION配置也会默认指向这个文件。看一下这个wsgi.py文件,其实它也和上面的逻辑一样,最终调用get_wsgi_application实现。

2、uWSGI方法

uWSGI+Nginx的方法是现在最常见的在生产环境中运行Django的方法,本人的博客也是使用这种方法运行,要了解这种方法,首先要了解一下WSGI和uWSGI协议。

WSGI,全称Web Server Gateway Interface,或者Python Web Server Gateway Interface,是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口,基于现存的CGI标准而设计的。WSGI其实就是一个网关(Gateway),其作用就是在协议之间进行转换。(PS: 这里只对WSGI做简单介绍,想要了解更多的内容可自行搜索)

uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议。注意uwsgi是一种通信协议,而uWSGI是实现uwsgi协议和WSGI协议的Web服务器。uWSGI具有超快的性能、低内存占用和多app管理等优点。以我的博客为例,uWSGI的xml配置如下:

<uwsgi>
  <!-- 端口 -->
  <socket>:7600</socket>
  <stats>:40000</stats>
  <!-- 系统环境变量 -->
  <env>DJANGO_SETTINGS_MODULE=geek_blog.settings</env>
  <!-- 指定的python WSGI模块 -->
  <module>django.core.handlers.wsgi:WSGIHandler()</module>
  <processes>6</processes>
  <master />
  <master-as-root />
  <!-- 超时设置 -->
  <harakiri>60</harakiri>
  <harakiri-verbose/>
  <daemonize>/var/app/log/blog/uwsgi.log</daemonize>
  <!-- socket的监听队列大小 -->
  <listen>32768</listen>
  <!-- 内部超时时间 -->
  <socket-timeout>60</socket-timeout>
</uwsgi>

以上就是uWSGI xml配置的写法,也可以使用ini的方式。安装uWSGI和运行的命令如下:

sudo pip install uwsgi
uwsgi --pidfile=/var/run/geek-blog.pid -x uwsgi.xml --uid blog --gid nogroup

uWSGI和Nginx一起使用的配置方法就不在这里说明了,网上教程很多,需要的可以自行搜索。

二、HTTP请求处理流程

Django和其他Web框架一样,HTTP的处理流程基本类似:接受request,返回response内容。Django的具体处理流程大致如下图所示:

1、加载project settings

在通过django-admin.py创建project的时候,Django会自动生成默认的settings文件和manager.py等文件,在创建WSGIServer之前会执行下面的引用:
from django.conf import settings

上面引用在执行时,会读取os.environ中的DJANGO_SETTINGS_MODULE配置,加载项目配置文件,生成settings对象。所以,在manager.py文件中你可以看到,在获取WSGIServer之前,会先将project的settings路径加到os路径中。

2、创建WSGIServer

不管是使用runserver还是uWSGI运行Django项目,在启动时都会调用django.core.servers.basehttp中的run()方法,创建一个django.core.servers.basehttp.WSGIServer类的实例,之后调用其serve_forever()方法启动HTTP服务。run方法的源码如下:

def run(addr, port, wsgi_handler, ipv6=False, threading=False):
  server_address = (addr, port)
  if threading:
    httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})
  else:
    httpd_cls = WSGIServer
  httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
  # Sets the callable application as the WSGI application that will receive requests
  httpd.set_app(wsgi_handler)
  httpd.serve_forever()

如上,我们可以看到:在创建WSGIServer实例的时候会指定HTTP请求的Handler,上述代码使用WSGIRequestHandler。当用户的HTTP请求到达服务器时,WSGIServer会创建WSGIRequestHandler实例,使用其handler方法来处理HTTP请求(其实最终是调用wsgiref.handlers.BaseHandler中的run方法处理)。WSGIServer通过set_app方法设置一个可调用(callable)的对象作为application,上面提到的handler方法最终会调用设置的application处理request,并返回response。

其中,WSGIServer继承自wsgiref.simple_server.WSGIServer,而WSGIRequestHandler继承自wsgiref.simple_server.WSGIRequestHandler,wsgiref是Python标准库给出的WSGI的参考实现。其源码可自行到wsgiref参看,这里不再细说。

3、处理Request

第二步中说到的application,在Django中一般是django.core.handlers.wsgi.WSGIHandler对象,WSGIHandler继承自django.core.handlers.base.BaseHandler,这个是Django处理request的核心逻辑,它会创建一个WSGIRequest实例,而WSGIRequest是从http.HttpRequest继承而来

4、返回Response

上面提到的BaseHandler中有个get_response方法,该方法会先加载Django项目的ROOT_URLCONF,然后根据url规则找到对应的view方法(类),view逻辑会根据request实例生成并返回具体的response。

在Django返回结果之后,第二步中提到wsgiref.handlers.BaseHandler.run方法会调用finish_response结束请求,并将内容返回给用户。

三、Django处理Request的详细流程

上述的第三步和第四步逻辑只是大致说了一下处理过程,Django在处理request的时候其实做了很多事情,下面我们详细的过一下。首先给大家分享两个网上看到的Django流程图:

分析Python的Django框架的运行方式及处理流程

Django流程图1

分析Python的Django框架的运行方式及处理流程

Django流程图2
上面的两张流程图可以大致描述Django处理request的流程,按照流程图2的标注,可以分为以下几个步骤:

    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返回到浏览器,呈现给用户

上述流程中最主要的几个部分分别是:Middleware(中间件,包括request, view, exception, response),URLConf(url映射关系),Template(模板系统),下面一一介绍一下。

1、Middleware(中间件)

Middleware并不是Django所独有的东西,在其他的Web框架中也有这种概念。在Django中,Middleware可以渗入处理流程的四个阶段:request,view,response和exception,相应的,在每个Middleware类中都有rocess_request,process_view, process_response 和 process_exception这四个方法。你可以定义其中任意一个活多个方法,这取决于你希望该Middleware作用于哪个处理阶段。每个方法都可以直接返回response对象。

Middleware是在Django BaseHandler的load_middleware方法执行时加载的,加载之后会建立四个列表作为处理器的实例变量:

  1.     _request_middleware:process_request方法的列表
  2.     _view_middleware:process_view方法的列表
  3.     _response_middleware:process_response方法的列表
  4.     _exception_middleware:process_exception方法的列表

Django的中间件是在其配置文件(settings.py)的MIDDLEWARE_CLASSES元组中定义的。在MIDDLEWARE_CLASSES中,中间件组件用字符串表示:指向中间件类名的完整Python路径。例如GeekBlog项目的配置:

MIDDLEWARE_CLASSES = (
  'django.middleware.cache.UpdateCacheMiddleware',
  'django.middleware.common.CommonMiddleware',
  'django.middleware.cache.FetchFromCacheMiddleware',
  'django.contrib.sessions.middleware.SessionMiddleware',
  'django.middleware.csrf.CsrfViewMiddleware',
  'django.contrib.auth.middleware.AuthenticationMiddleware',
  'django.contrib.messages.middleware.MessageMiddleware',
  'django.middleware.locale.LocaleMiddleware',
  'geek_blog.middlewares.MobileDetectionMiddleware',  # 自定义的Middleware
)

Django项目的安装并不强制要求任何中间件,如果你愿意,MIDDLEWARE_CLASSES可以为空。中间件出现的顺序非常重要:在request和view的处理阶段,Django按照MIDDLEWARE_CLASSES中出现的顺序来应用中间件,而在response和exception异常处理阶段,Django则按逆序来调用它们。也就是说,Django将MIDDLEWARE_CLASSES视为view函数外层的顺序包装子:在request阶段按顺序从上到下穿过,而在response则反过来。以下两张图可以更好地帮助你理解:

分析Python的Django框架的运行方式及处理流程

Django Middleware流程1

分析Python的Django框架的运行方式及处理流程

Django Middleware流程图2
2、URLConf(URL映射)

如果处理request的中间件都没有直接返回response,那么Django会去解析用户请求的URL。URLconf就是Django所支撑网站的目录。它的本质是URL模式以及要为该URL模式调用的视图函数之间的映射表。通过这种方式可以告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码。具体的,在Django项目的配置文件中有ROOT_URLCONF常量,这个常量加上根目录"/",作为参数来创建django.core.urlresolvers.RegexURLResolver的实例,然后通过它的resolve方法解析用户请求的URL,找到第一个匹配的view。

其他有关URLConf的内容,这里不再具体介绍,大家可以看DjangoBook了解。

3、Template(模板)

大部分web框架都有自己的Template(模板)系统,Django也是。但是,Django模板不同于Mako模板和jinja2模板,在Django模板不能直接写Python代码,只能通过额外的定义filter和template tag实现。由于本文主要介绍Django流程,模板内容就不过多介绍。

PS: 以上代码和内容都是基于Django 1.6.5版本,其他版本可能与其不同,请参考阅读。

Over!

Python 相关文章推荐
Python Deque 模块使用详解
Jul 04 Python
Django实现简单分页功能的方法详解
Dec 05 Python
Python 编码规范(Google Python Style Guide)
May 05 Python
解决Mac安装scrapy失败的问题
Jun 13 Python
解决Pycharm下面出现No R interpreter defined的问题
Oct 29 Python
python提取具有某种特定字符串的行数据方法
Dec 11 Python
python抓取需要扫微信登陆页面
Apr 29 Python
Python 寻找局部最高点的实现
Dec 05 Python
python实现的分层随机抽样案例
Feb 25 Python
python爬虫中采集中遇到的问题整理
Nov 27 Python
python flappy bird小游戏分步实现流程
Feb 15 Python
使用Django框架创建项目
Jun 10 Python
给Python的Django框架下搭建的BLOG添加RSS功能的教程
Apr 08 #Python
在Python中使用NLTK库实现对词干的提取的教程
Apr 08 #Python
使用Python操作Elasticsearch数据索引的教程
Apr 08 #Python
用Python实现协同过滤的教程
Apr 08 #Python
在Python中调用ggplot的三种方法
Apr 08 #Python
Python字符串和文件操作常用函数分析
Apr 08 #Python
Python遍历zip文件输出名称时出现乱码问题的解决方法
Apr 08 #Python
You might like
使用bcompiler对PHP文件进行加密的代码
2010/08/29 PHP
JAVASCRIPT对象及属性
2007/02/13 Javascript
poshytip 基于jquery的 插件 主要用于显示微博人的图像和鼠标提示等
2012/10/12 Javascript
『JavaScript』限制Input只能输入数字实现思路及代码
2013/04/22 Javascript
jQuery中:disabled选择器用法实例
2015/01/04 Javascript
jquery实现从数组移除指定的值
2015/06/24 Javascript
九种原生js动画效果
2015/11/11 Javascript
javascript实现下拉提示选择框
2015/12/29 Javascript
基于jquery实现动态竖向柱状条特效
2016/02/12 Javascript
AngularJS基础 ng-init 指令简单示例
2016/08/02 Javascript
深入学习js瀑布流布局
2016/10/14 Javascript
使用JavaScript获取URL中的参数(两种方法)
2016/11/16 Javascript
详解微信小程序Page中data数据操作和函数调用
2017/09/27 Javascript
在vue-cli的组件模板里使用font-awesome的两种方法
2018/09/28 Javascript
vue路由事件beforeRouteLeave及组件内定时器的清除方法
2018/09/29 Javascript
可能被忽略的一些JavaScript数组方法细节
2019/02/28 Javascript
vue中nextTick用法实例
2019/09/11 Javascript
Node.js 实现抢票小工具 &amp; 短信通知提醒功能
2019/10/22 Javascript
基于Python实现对PDF文件的OCR识别
2016/08/05 Python
python 实现删除文件或文件夹实例详解
2016/12/04 Python
关于Python 3中print函数的换行详解
2017/08/08 Python
python交互式图形编程实例(三)
2017/11/17 Python
python3实现跳一跳点击跳跃
2018/01/08 Python
聊聊Python中的pypy
2018/01/12 Python
Django如何自定义分页
2018/09/25 Python
Django实现学生管理系统
2019/02/26 Python
python通过opencv实现图片裁剪原理解析
2020/01/19 Python
python离线安装外部依赖包的实现
2020/02/13 Python
python实现感知机模型的示例
2020/09/30 Python
python 简单的调用有道翻译
2020/11/25 Python
番木瓜健康和保健产品第一大制造商:Herbal Papaya
2017/04/25 全球购物
英国最大的在线快递公司之一:ParcelHero
2019/11/04 全球购物
会计自我鉴定
2013/11/02 职场文书
新郎新娘婚礼答谢词
2014/01/11 职场文书
应聘教师自荐信
2015/03/26 职场文书
2016关于学习党章的心得体会
2016/01/15 职场文书