分析Python的Django框架的運行方式及處理流程

轉自: http://www.jb51.net/article/63734.htm
 
這篇文章主要介紹了分析Python的Django框架的運行方式及處理流程,本文對於Django框架的機制總結得很是之直觀精煉,極力推薦!須要的朋友能夠參考下 。

  以前在網上看過一些介紹Django處理請求的流程和Django源碼結構的文章,以爲了解一下這些內容對開發Django項目仍是頗有幫助的。因此,我按照本身的邏輯總結了一下Django項目的運行方式和對Request的基本處理流程。python


1、Django的運行方式web

  運行Django項目的方法不少,這裏主要介紹一下經常使用的方法。一種是在開發和調試中常常用到runserver方法,使用Django本身的web server;另一種就是使用fastcgi,uWSGIt等協議運行Django項目,這裏以uWSGIt爲例。django

一、runserver方法瀏覽器

  runserver方法是調試Django時常常用到的運行方式,它使用Django自帶的WSGI Server運行,主要在測試和開發中使用,使用方法以下:bash

 
1
2
3
4
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命令主要作了兩件事情:session

    1). 解析參數,並經過django.core.servers.basehttp.get_internal_wsgi_application方法獲取wsgi handler;app

    2). 根據ip_address和port生成一個WSGIServer對象,接受用戶請求框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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實現。socket

二、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配置以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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和運行的命令以下:

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

  uWSGI和Nginx一塊兒使用的配置方法就不在這裏說明了,網上教程不少,須要的能夠自行搜索。


2、HTTP請求處理流程

Django和其餘Web框架同樣,HTTP的處理流程基本相似:接受request,返回response內容。Django的具體處理流程大體以下圖所示:

一、加載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路徑中。

二、建立WSGIServer

  不論是使用runserver仍是uWSGI運行Django項目,在啓動時都會調用django.core.servers.basehttp中的run()方法,建立一個django.core.servers.basehttp.WSGIServer類的實例,以後調用其serve_forever()方法啓動HTTP服務。run方法的源碼以下:

1
2
3
4
5
6
7
8
9
10
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參看,這裏再也不細說。

三、處理Request

  第二步中說到的application,在Django中通常是django.core.handlers.wsgi.WSGIHandler對象,WSGIHandler繼承自django.core.handlers.base.BaseHandler,這個是Django處理request的核心邏輯,它會建立一個WSGIRequest實例,而WSGIRequest是從http.HttpRequest繼承而來

四、返回Response

  上面提到的BaseHandler中有個get_response方法,該方法會先加載Django項目的ROOT_URLCONF,而後根據url規則找到對應的view方法(類),view邏輯會根據request實例生成並返回具體的response。

  在Django返回結果以後,第二步中提到wsgiref.handlers.BaseHandler.run方法會調用finish_response結束請求,並將內容返回給用戶。


3、Django處理Request的詳細流程

  上述的第三步和第四步邏輯只是大體說了一下處理過程,Django在處理request的時候其實作了不少事情,下面咱們詳細的過一下。首先給你們分享兩個網上看到的Django流程圖:

201548164301191.png (467×525)

              Django流程圖1

201548164415899.png (531×699)

                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(模板系統),下面一一介紹一下。

一、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項目的配置:

1
2
3
4
5
6
7
8
9
10
11
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則反過來。如下兩張圖能夠更好地幫助你理解:

201548164517915.png (502×417)

          Django Middleware流程1

201548164601481.png (899×226)

          Django Middleware流程圖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瞭解。

三、Template(模板)

  大部分web框架都有本身的Template(模板)系統,Django也是。可是,Django模板不一樣於Mako模板和jinja2模板,在Django模板不能直接寫Python代碼,只能經過額外的定義filter和template tag實現。因爲本文主要介紹Django流程,模板內容就不過多介紹。

PS: 以上代碼和內容都是基於Django 1.6.5版本,其餘版本可能與其不一樣,請參考閱讀。

相關文章
相關標籤/搜索