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 流程图:
Django 流程图 1
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 方法执行时加载的, 加载之后会建立四个列表作为处理器的实例变量:
_request_middleware:process_request 方法的列表
_view_middleware:process_view 方法的列表
_response_middleware:process_response 方法的列表
_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 则反过来. 以下两张图可以更好地帮助你理解:
Django Middleware 流程 1
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 流程, 模板内容就不过多介绍.
来源: https://yq.aliyun.com/articles/688782