0%

WSGI 极简入门

WSGI(Web Server Gateway Interface)是一个规范,用于描述在 Python 中 Web 服务器和 Web 应用程序之间如何交互,以及如何串接多个 Web 应用程序来处理 Web 请求。WSGI 规范通过 Python PEP3333 标准详细描述。

介绍

WSGI 既不是 Web 服务器,也不是 Web 框架,它只是一个接口规范,用于描述在 python 中 Web 服务器和 Web 应用程序之间如何交互。如果 Web 应用程序遵循 WSGI 规范,那么这个 Web 应用程序可以在任何符合 WSGI 规范的服务器上运行。多个兼容 WSGI 的 Web 应用程序也可以形成 stack,此时 stack 中间的 Web 应用程序也被称为 middleware,middleware 必须同时实现 WSGI server 和 application 侧的接口。

WSGI server(兼容 WSGI 规范的 Web server)从客户端接收请求,将其传递给应用程序,并将应用程序返回的响应发送给客户端。

应用程序接口

WSGI 应用程序接口 由一个可调用对象实现:函数、方法、类 或者实现了 object.__call__() 方法的实例。该可调用对象需要满足以下条件:

  • 接收两个位置参数:
    • 参数 1:一个字典,包含一系列环境变量(类似于 CGI 环境变量),这些变量由 server 根据所处理的每个请求而产生
    • 参数 2:一个回调函数,由 server 提供,由应用程序使用。应用程序使用该回调函数向 Web server 返回 HTTP 响应状态码以及 HTTP 响应头部
  • 在一个可迭代对象中封装返回给 server 的响应 body 字符串(字节字符串)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from wsgiref.simple_server import make_server


def application(environ, start_response):
response_body = '\n'.join(('{}: {}'.format(k, v) for k, v in environ.items()))
status = '200 OK'
response_headers = [
('Content-Type', 'text/plain'),
('Content-Length', str(len(response_body)))
]

start_response(status, response_headers)

return [response_body.encode()]


httpd = make_server('localhost', 8051, application)
httpd.handle_request()
  • 在该示例中,编写了一个 WSGI 应用程序 application,该 web 应用程序总是返回 200,并将 environ 字典的内容作为响应 body 返回
  • 使用 Python 标准库中 wsgiref 模块提供的 WSGI server 作为 Web server,并将该应用程序提供给 WSGI server

实际运行结果如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# curl http://127.0.0.1:8051/
SHELL: /bin/bash
KERNEL_FLAVOR: linux
no_proxy: localhost,127.0.0.1,git.in.chaitin.net,portus.in.chaitin.net,s3-ephemeral.in.chaitin.net,registry-mirrors.dev.in.chaitin.net,mirror.dev.in.chaitin.net,172.17.0.2,10.2.28.5
TERM_PROGRAM_VERSION: 3.2a
TMUX: /tmp/tmux-0/default,2722,1
TMUX_PLUGIN_MANAGER_PATH: /root/.tmux/plugins/
EDITOR: /usr/bin/vim
PWD: /root/code/private/python/wsgi
LOGNAME: root
XDG_SESSION_TYPE: tty
MOTD_SHOWN: pam
HOME: /root
LANG: en_US.UTF-8
LS_COLORS: rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
LC_TERMINAL: iTerm2
https_proxy: http://proxy.in.chaitin.net:8123
SSH_CONNECTION: 10.2.7.21 52704 10.9.33.133 22
LESSCLOSE: /usr/bin/lesspipe %s %s
XDG_SESSION_CLASS: user
TERM: tmux-256color
LESSOPEN: | /usr/bin/lesspipe %s
USER: root
TMUX_PANE: %11
LC_TERMINAL_VERSION: 3.4.16
SHLVL: 2
XDG_SESSION_ID: 1
http_proxy: http://proxy.in.chaitin.net:8123
XDG_RUNTIME_DIR: /run/user/0
SSH_CLIENT: 10.2.7.21 52097 22
DEBUGINFOD_URLS:
XDG_DATA_DIRS: /usr/local/share:/usr/share:/var/lib/snapd/desktop
PATH: /root/bin/:/root/.cargo/bin:/root/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/go/bin:/usr/local/go/bin
DBUS_SESSION_BUS_ADDRESS: unix:path=/run/user/0/bus
SSH_TTY: /dev/pts/0
OLDPWD: /root/docker_share/code/safeline-2/package
TERM_PROGRAM: tmux
_: /usr/bin/python3
SERVER_NAME: localhost
GATEWAY_INTERFACE: CGI/1.1
SERVER_PORT: 8051
REMOTE_HOST:
CONTENT_LENGTH:
SCRIPT_NAME:
SERVER_PROTOCOL: HTTP/1.1
SERVER_SOFTWARE: WSGIServer/0.2
REQUEST_METHOD: GET
PATH_INFO: /
QUERY_STRING:
REMOTE_ADDR: 127.0.0.1
CONTENT_TYPE: text/plain
HTTP_HOST: 127.0.0.1:8051
HTTP_USER_AGENT: curl/7.81.0
HTTP_ACCEPT: */*
wsgi.input: <_io.BufferedReader name=4>
wsgi.errors: <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>
wsgi.version: (1, 0)
wsgi.run_once: False
wsgi.url_scheme: http
wsgi.multithread: False
wsgi.multiprocess: False
wsgi.file_wrapper: <class 'wsgiref.util.FileWrapper'>

下面的程序添加了一个 middleware,它的作用是在 applicaiton 的响应 body 之前添加 Hello, WSGI 字符串

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
26
27
28
from wsgiref.simple_server import make_server


def application(environ, start_response):
response_body = '\n'.join(('{}: {}'.format(k, v) for k, v in environ.items()))
status = '200 OK'
response_headers = [
('Content-Type', 'text/plain'),
('Content-Length', str(len(response_body)))
]

start_response(status, response_headers)

return [response_body.encode()]


class HelloMidlleware:
def __init__(self, app):
self._app = app

def __call__(self, environ, start_response):
results = self._app(environ, start_response)
yield b"Hello, WSGI\n"
yield from results


httpd = make_server('localhost', 8051, HelloMidlleware(application))
httpd.handle_request()

运行结果如下:

1
2
3
4
5
# curl http://127.0.0.1:8051/
Hello, WSGI
SHELL: /bin/bash
KERNEL_FLAVOR: linux
......

Reference

wsgi
WSGI tutorial