1+1=10

记记笔记,放松一下...

Python WSGI入门笔记

WSGI,是Web Server Gateway Interface首字符缩写, 发音 “whiz-gee”(g发硬/g/) 或者 “whiskey”。

WSGI 历史很久了,2003年由PEP-333规范,2010年,PEP3333基于Python3进行了更新。由于出现比较早,没有Python3.5引入的async、await 等异步内容,这导致2019年出现了ASGI(Asynchronous Server Gateway Interface)。

规范

WSGI 是一个规范,它遵循这种规范将一个Web组件抽象成三个部件层:

  • 服务器(server、gateway)
  • 中间件(middleware)
  • 应用程序(application、framework)

要提升程序的可移植性,需要规范服务器和应用程序之间的交互操作,这就是WSGI做的事情。

wsgi-server-application

接下来只考虑应用程序端

例子

通过简单例子,一点点看看

Hello world

一个简单的WSGI的应用程序,大致这个样子:

1
2
3
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [b'<h1>Hello World</h1>']

而要执行它,需要将其告知WSGI服务器才行。

在python标准库中有一个wsgi服务器的参考实现,我们可以启动它并告知其我们的应用程序入口:

1
2
import wsgiref.simple_server as server
server.make_server('', 8000, application).serve_forever()

此时用浏览器打开127.0.0.1:8000,即可看到程序运行内容。

应用程序参数

  • environ:一个dict类型对象。一般包含
    • 浏览器相关的上下文信息
    • CGI接口定义的环境变量:REQUEST_METHODSCRIPT_NAMEPATH_INFOQUERY_STRING
    • WSGI接口定义的环境变量:wsgi.versionwsgi.input
    • OS或Web服务器相关的环境变量
  • start_response:一个在Server中定义的回调函数。这个回调函数接受两个必选参数status和response_headers,以及可选参数exec_info
    • status:字符串,标识HTTP响应状态。比如 "200 OK""404 Not Found"
    • response_headers:列表类型对应,列表元素是(header_name, header_value)。HTTP响应的headers。
    • exec_info:出错时,返回错误信息给浏览器
  • 返回值:如果HTTP响应没有body,可以返回None,否则返回可迭代对象。

我们稍微修改一下程序,输出些额外信息

1
2
3
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [f'<h1>Hello World: {environ["PATH_INFO"]}</h1>'.encode('utf-8')]

此时用浏览器打开127.0.0.1:8000/aaa/bbb/ccc,即可在页面中看到路径信息。

中间件(Middleware)

中间件是一个运行在 Server 和 Application 之间的应用程序。它同时具备了 Server 和 Application 的角色功能:

  • 对于 Server 来说,它是一个 Application;
  • 对于 Application来说,它是一个 Server。

中间件并不修改 Server 端和 Application 端的规范,只是需要同时实现了这两个角色的功能以便于两端之间起协调作用。中间件也可以将客户端 HTTP Request 路由给不同的 Application。中间件可以由很多层。

比如,可以写一个简单的中间件,添加到前面的例子中,并告知wsgi服务器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [f'<h1>Hello World: {environ["PATH_INFO"]}</h1>'.encode('utf-8')]

def middleware(environ, start_response):
    if environ['PATH_INFO'] == '/secret':
        start_response('403 Forbidden', [('Content-Type', 'text/html')])
        return [b'<h1>Forbidden</h1>']
    return application(environ, start_response)

import wsgiref.simple_server as server
server.make_server('', 8000, middleware).serve_forever()

装饰器模式可能更直观:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def middleware(application):
    def mywrapper(environ, start_response):
        if environ['PATH_INFO'] == '/secret':
            start_response('403 Forbidden', [('Content-Type', 'text/html')])
            return [b'<h1>Forbidden</h1>']
        return application(environ, start_response)
    return mywrapper

@middleware
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [f'<h1>Hello World: {environ["PATH_INFO"]}</h1>'.encode('utf-8')]

import wsgiref.simple_server as server
server.make_server('', 8000, application).serve_forever()

Werkzeug

  • https://werkzeug.palletsprojects.com

Wekzeug,/ˈvɛrkʦɔyk/,德语,工具的意思。

Werkzeug 这个python库,是一个WSGI(Web Server Gateway Interface)工具包,提供了一系列实用功能来帮助开发者处理HTTP请求、响应、URL等。

Werkzeug 是WSGI的中间件。它也提供了WSGI的Server,这个Server只作为开发使用,不用于生产环境。

开发用 Server

既然是WSGI Server,可以用它跑前面的应用程序:

1
2
import werkzeug.serving as serving
serving.run_simple('localhost', 8000, application)

也可以为其启用ssl证书:

1
2
3
import werkzeug.serving as serving
serving.run_simple('localhost', 8000, application,
                   ssl_context=('111.cert', '111.key'))

中间件

  • Werkzeug为HTTP请求和响应提供易于使用的包装器

使用其提供的Request和Response,上面的例子可以改写成:

1
2
3
4
5
6
from werkzeug.wrappers import Request, Response

def application(environ, start_response):
    request = Request(environ)
    response = Response(f'<h1>Hello World: {request.args.get("PATH_INFO")}!</h1>', mimetype='text/html')
    return response(environ, start_response)
  • Werkzeug提供了强大的URL路由功能。

使用Map和Rule:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from werkzeug.wrappers import Request, Response
from werkzeug.routing import Map, Rule

url_map = Map([
    Rule('/', endpoint='index'),
    Rule('/hello', endpoint='hello'),
])

def application(environ, start_response):
    request = Request(environ)

    urls = url_map.bind_to_environ(request.environ)
    endpoint, args = urls.match()

    response = Response(f'<h1>Hello World: {endpoint} with {args}!</h1>', mimetype='text/html')
    return response(environ, start_response)

WSGI服务器

生产用...

不过,这些WSGI基本都要用 Nginx或Apache等http服务器作为反向代理服务器。

Gunicorn

  • https://gunicorn.org/

不支持原生Windows(可通过WSL使用)。

告知它如何加载我们的application。比如要等价于from hello import application

1
$gunicorn -w 4 'hello:application'
  • -w指定进程数(--workers=4)。默认是1,建议CPU * 2?
  • -b指定监听地址和端口(--bind)。格式HOSTHOST:PORT,默认是localhost和8000
  • 语法格式:{module_import}:{app_variable}

gunicorn使用root权限运行不安全(会让application以root运行),而不用root的话,会造成无妨绑定80和443端口。所以最好使用 nginx或apache作为反向代理服务器。

Waitress

  • https://docs.pylonsproject.org/projects/waitress/

原生支持Windows。

安装:

1
$ pip install waitress

启动:

1
$ waitress-serve hello:application --host 127.0.0.1
  • 参数格式{module}:{app}
  • --host指定监听地址。默认监听 0.0.0.0:8080

它不支持ssl,需要反向代理服务器。

mod_wsgi

  • https://modwsgi.readthedocs.io/

与 apache httpd 集成使用。

1
pip install mod_wsgi

它由一个现代的命令mod_wsgi-express,用于启动server

1
mode_wsgi-express start-server wsgi.py --process 4

其中 wsgi.py 是我们的程序,里面必须要包含application 这个对象:

1
2
3
# wsgi.py
from hello import myapp
application = myapp

uwsgi

  • https://uwsgi-docs.readthedocs.io/en/latest/

官网上说,The project is in maintenance mode (only bugfixes and updates for new languages apis).

启动命令

1
uwsgi --http 127.0.0.1:8000 --master -p 4 -w hello:application

其他

  • gevent
  • eventlet

参考

  • https://peps.python.org/pep-3333/
  • https://peps.python.org/pep-0333/
  • https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface
  • https://segmentfault.com/a/1190000003069785
  • https://gunicorn.org/

Python web