Kikimo's blog

An occasional blog on programming

Django 源码分析: (一) WSGI 规范与 Django 框架

WSGI 是一套定义 Python Web 服务器和 Python Web 应用之间通信接口的规范. Django 框架的本质是一套实现了 WSGI 应用端接口的框架(当然 Django 还提供了 ORM, 模板引擎等诸多常见的 Web 编程组件), WSGI 应用接口可以看做 Django 处理用户请求的入口, 要分析 Django 框架的源码, 我们就从 Django 的 WSGI 应用接口开始. 本文首先简要介绍了 WSGI 规范, 然后介绍 Django 中的 WSGI 定义.

WSGI 规范

Python PEP 3333 提出了 WSGI 的概念. WSGI 的全称是 Web 服务器网关接口(Web Server Gatway Interface), 它是一套接口通信规范, 这套接口规范定义了 Python Web 服务器和 Python Web 应用之间的通信标准. WSGI 规范同时定义了Python Web 服务器端(网关)的接口和 Python Web 应用端(框架)接口. WSGI 服务器接收客户端发送过来的请求, 然后将请求封装好发送给 WSGI 应用处理, WSGI 应用处理完请求后将处理结果发送给 WSGI 服务器, WSGI 服务器进一步将结果发送给客户端. 使用这个规范带来的好处是: 基于 WSGI 规范开发的 Python Web 应用可以运行在任意支持 WSGI 规范的 Web 服务器上. 譬如, 一个基于 WSGI 规范开发的 Python Web 应用既可运行于 gunicorn 服务器上, 又能运行与 usgwi 服务器上, 这两个 Python Web 服务起都支持 WSGI 规范.

WSGI 应用接口是一个可调用对象(Python callable object): 一个函数, 一个方法, 一个实现了 object.call()方法的对象实例. 这个可调用对象必须满足[1]:

  1. 接收两个参数:

    • 一个字典, 包含类似 CGI 的变量;
    • 一个回调函数, 这个回调函数将被用于发送 HTTP 状态码/消息和 HTTP 头部信息给 WSGI Web 服务器.
  2. 返回响应体(response body)给WSGI Web 服务器, 这个响应体应该被封装在一个 Python iterator中.

[1] 这部分关于 WSGI 应用接口的描述引用自 WSGI -- Application Interface

我们来看一个 WSGI 应用接口的例子, wsgiapp.py[2]

# The application interface is a callable object
def application ( # It accepts two arguments:
    # environ points to a dictionary containing CGI like environment
    # variables which is populated by the server for each
    # received request from the client
    environ,
    # start_response is a callback function supplied by the server
    # which takes the HTTP status and headers as arguments
    start_response
):

    # Build the response body possibly
    # using the supplied environ dictionary
    response_body = 'Request method: %s' % environ['REQUEST_METHOD']

    # HTTP response code and message
    status = '200 OK'

    # HTTP headers expected by the client
    # They must be wrapped as a list of tupled pairs:
    # [(Header name, Header value)].
    response_headers = [
        ('Content-Type', 'text/plain'),
        ('Content-Length', str(len(response_body)))
    ]

    # Send them to the server using the supplied function
    start_response(status, response_headers)

    # Return the response body. Notice it is wrapped

    # in a list although it could be any iterable.
    return [response_body]

[2] 这部分关于 WSGI 应用接口的描述也是引用自 WSGI -- Application Interface

这就实现了一个简单的 WSGI Web 应用. WSGI 应用必须跑在 WSGI Web 服务器中. 利用 Python 中的 wsgiref 模块, 我们来构造一个简单的 WSGI Web 服务器:

#! /usr/bin/env python

# Python's bundled WSGI server
from wsgiref.simple_server import make_server

from wsgiapp import application

# Instantiate the server
httpd = make_server (
    'localhost', # The host name
    8000, # A port number where to wait for the request
    application # The application object name, in this case a function
)

# Wait for a single request, serve it and quit
httpd.serve_forever()

执行以上代码, 用浏览器打开页面 http://localhost:8000, 可以看到如下结果:

djserver

Django 中的 WSGI 应用

通常, 想要让我们的 Web 应用能够运行在 WSGI 服务器上的话并不需要我们自己去实现 WSGI 应用接口. 现有的支持 WSGI 规范的 Python Web 框架都已经实现了 WSGI 应用接口部分, 例如 Django, Flask, 我们只要在框架下开发 Web 应用即可. 这些框架中带有 WSGI 应用接口的实现和应用的构造代码. 我们来看看 Django 中的 WSGI 应用的构造.

在一个 Django 项目的 settings.py 文件可以看到一个 WSGI_APPLICATION 变量. 如果我们的 Django 项目名字叫 djtest, 那么 WSGI_APPLICATION 变量的值就会是 djtest.wsgi.application. 打开 djstest/wsgi.py 文件(这个文件是 django-admin 在创建 Django 项目时自动生成的), 可以看到以下内容:

"""
WSGI config for djtest project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djtest.settings")

application = get_wsgi_application()

在以上代码, 我们看到函数 get_wsgi_application() 构造并返回 Django 的 WSGI Web 应用对象, 这个对象被赋值个 application 变量. 利用 Python 的 wsgiref 模块, 我们写个简单的 WSGI Web 服务器 djserver.py, 我们的这个服务器将利用 djtest.wsgi.application 对象来处理用户请求.

from wsgiref.simple_server import make_server
from djtest.wsgi import application

httpd = make_server('localhost', 8000, application)
httpd.serve_forever()

执行 djserver.py 脚本, 在浏览器中打开 http://localhost:8000/, 我们可以看到 Django 项目正常启动的页面:

run django app

这也是我们运行 python manage.py runserver 用浏览器打开页面 http://localhost:8000/ 能看到的结果.

comments powered by HyperComments