WSGI 笔记

WSGI (Web Server Gateway Interface) - WSGI 是一个 Python 标准,在 PEP 3333 中定义。WSGI 定义了 web server 如何与 web 应用通信,以及多个 web 应用如何串联起来处理网络请求。

WSGI is a specification that describes how a web server communicates with web applications, and how web applications can be chained together to process one request.

Apache 与 WSGI 脚本

一个简单示例:

def application(environ, start_response):
    status = '200 OK'
    output = b'Hello World!'

    response_headers = [('Content-type', 'text/plain'),
                        ('Content-Length', str(len(output)))]
    start_response(status, response_headers)

    return [output]

注意:

Apache 主要通过以下几种方式使用 WSGI 脚本:

  1. Apache 直接访问 WSGI 脚本
  2. WSGI 脚本作为后台进程运行
  3. 通过 mod_proxy 及其相关模块转发请求

为了方便查看 WSGI 错误,应该将 Apache 默认的日志级别从 warn 修改为 info:

LogLevel info

方法一,Apache 直接访问 WSGI 脚本

这里的核心是将一个 URL 与一个 WSGI 脚本关联起来,使用 WSGIScriptAlias 即可:

WSGIScriptAlias /myapp /usr/local/www/wsgi-scripts/myapp.wsgi

WSGIScriptAlias 指令的第一个参数为 /myapp,表示访问 http://www.example.com/myapp 即调用对应 WSGI 脚本,注意第一个参数 /myapp 结尾处没有 /,只有 WSGI 脚本作为 root URL 时可以写为 /

WSGIScriptAlias 指令只能放在 Apache 的主配置文件即 httpd.conf,亦即不能置于 .htaccess 文件。

WSGIScriptAlias 指令可以放在 httpd.conf 全局,但更常见的是放在 VirtualHost 中,不能置于 Location, Directory, Files 配置中。

一个完整的配置示例:

<VirtualHost *:80>

    ServerName www.example.com
    ServerAlias example.com

    DocumentRoot /usr/local/www/documents

    <Directory /usr/local/www/documents>
        Require all granted
    </Directory>

    WSGIScriptAlias /myapp /usr/local/www/wsgi-scripts/myapp.wsgi

    <Directory /usr/local/www/wsgi-scripts>
        Require all granted
    </Directory>

</VirtualHost>

注意到,上例中我们的 Apache 根目录在 /usr/local/www/documents,而 WSGI 脚本在 /usr/local/www/wsgi-scripts,这样做的好处是即使 Apache 被错误地配置为了静态文件访问,我们的 Python 源代码也不会泄露。把所有的 Python 脚本放在一个独立的路径,是个更安全的做法。

扩展本示例

如果你的所有代码都用 WSGI 脚本实现,那么可以配置 root URL 指向 WSGI 脚本,即:

WSGIScriptAlias / /usr/local/www/wsgi-scripts/myapp.wsgi

这样有个副作用,就是位于 Apache 根目录的静态文件(如图片、视频等)就不能被访问了,因为所有请求都会转发到 WSGI 脚本处理。此时需要用 Alias 指定这些文件依然从 Apache 根目录访问。完整示例如下:

<VirtualHost *:80>

    ServerName www.example.com
    ServerAlias example.com

    DocumentRoot /usr/local/www/documents

    Alias /robots.txt /usr/local/www/documents/robots.txt
    Alias /favicon.ico /usr/local/www/documents/favicon.ico
    Alias /images/ /usr/local/www/documents/images/

    <Directory /usr/local/www/documents>
        Require all granted
    </Directory>

    WSGIScriptAlias /myapp /usr/local/www/wsgi-scripts/myapp.wsgi

    <Directory /usr/local/www/wsgi-scripts>
        Require all granted
    </Directory>

</VirtualHost>

方法二,WSGI 脚本作为后台进程运行

在方法一中,WSGI 的运行模式成为嵌入模式(embedded mode),这个模式的一个问题在于如果 WSGI 脚本修改了,需要重启 Apache。更灵活的方式是将 WSGI 作为后台进程运行(daemon mode),在此种模式下当 WSGI 代码更新时,WSGI 进程将自动重启。这里的核心配置为:

WSGIDaemonProcess example.com processes=2 threads=15
WSGIProcessGroup example.com

一个完整的配置示例:

<VirtualHost *:80>

    ServerName www.example.com
    ServerAlias example.com

    DocumentRoot /usr/local/www/documents

    Alias /robots.txt /usr/local/www/documents/robots.txt
    Alias /favicon.ico /usr/local/www/documents/favicon.ico
    Alias /images/ /usr/local/www/documents/images/

    <Directory /usr/local/www/documents>
        Require all granted
    </Directory>

    WSGIDaemonProcess example.com processes=2 threads=15 display-name=%{GROUP}
    WSGIProcessGroup example.com

    WSGIScriptAlias /myapp /usr/local/www/wsgi-scripts/myapp.wsgi

    <Directory /usr/local/www/wsgi-scripts>
        Require all granted
    </Directory>

</VirtualHost>

参考