由内建库wsgiref理解wsgi
WSGI
WSGI是 Python Web Server Gateway Interface ,是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。自从 WSGI 被开发出来以后,许多其它语言中也出现了类似接口
WSGI 是作为 Web 服务器(server)与 Web 应用程序或应用框架(application)之间的一种低级别的接口,以提升可移植 Web 应用开发的共同点。WSGI 是基于现存的 CGI 标准而设计的
- WSGI是一套接口标准协议/规范
- 通信(作用)区间是Web服务器和Python Web应用程序之间
- 制定标准,以保证不同Web服务器可以和不同的Python程序之间相互通信
WSGI基本原理
- 浏览器发送请求包到wsgi server后server解包并封装成environ
- environ:http请求的参数以键值对字典的形式封装在environ中
- wsgi server传递environ和start_response函数给wsgi app
- app:后台处理逻辑的程序,需要environ和start_response作为参数
- wsgi app 调用,在返回可迭代对象给 wsgi server之前,必须先调用 start_response 函数
- start_response函数返回http status code、 headers和其他信息(response head)如Content-Type等给wsgi server
- app返回值(可迭代对象)作为http body(response body) 返回给wsgi server
- wsgi server组装从app获取到的所有信息作为response返回给浏览器
wsgiref
wsgiref是Python提供的一个WSGI参考实现库,注意不适用于生产环境
- wsgiref.simple_server 模块实现一个简单的WSGI HTTP服务器
- wsgiref.simple_server.make_server(host, port, app, server_class=WSGIServer,
handler_class=WSGIRequestHandler) 启动一个WSGI服务器 - wsgiref.simple_server.demo_app(environ, start_response) 两参数函数demo_app为WSGI的应用程序
- wsgiref.simple_server.make_server(host, port, app, server_class=WSGIServer,
from wsgiref.simple_server import make_server, demo_app
host = '127.0.0.1'
port = 9999
server = make_server(host, port, demo_app)
server.serve_forever()
启动后,通过浏览器访问127.0.0.1:9999则可看到返回响应数据
WSGI 服务器作用:
- 监听HTTP服务端口(TCPServer,默认端口80)
- 接收浏览器端的HTTP请求并解析封装成environ环境数据
- 负责调用应用程序,将environ数据和start_response方法两个实参传入给Application
- 将应用程序响应的正文封装成HTTP响应报文返回浏览器端
WSGI App
先来看下wsgiref库中demo_app的源码:
def demo_app(environ, start_response):
from io import StringIO
stdout = StringIO() # stringio
print("Hello world!", file=stdout) # 正如我们在浏览器中看到的第一行
print(file=stdout) # 打印空行
h = sorted(environ.items()) # 排序封装的environ中的kv
for k, v in h:
print(k, '=', repr(v), file=stdout) # 遍历kv
start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')]) # 在返回之前先调用start_response(返回http status code、 headers和其他信息)
return [stdout.getvalue().encode("utf-8")] # 返回可迭代对象
通过以上对demo_app的分析,可以看出:
- make_server(host, port, app)中的参数app需要满足app是一个可调用对象
- app必须返回一个可迭代对象
environ
改写如上demo_app来看看我们关心的一些请求参数
from wsgiref.simple_server import make_server
host = '127.0.0.1'
port = 9999
def app(environ, start_response):
from io import StringIO
stdout = StringIO()
for k, v in environ.items():
if k.startswith('HTTP') or k.startswith('SERVER') or k in ("QUERY_STRING", "REQUEST_METHOD", "PATH_INFO", "CONTENT_TYPE"):
print(k, '=', v, file=stdout)
start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
return [stdout.getvalue().encode("utf-8"), b'abc~~~~']
server = make_server(host, port, app)
server.serve_forever()
浏览器访问127.0.0.1:9999,我们可以看到类似如下信息:
SERVER_NAME = 1.0.0.127.in-addr.arpa
SERVER_PORT = 9999
SERVER_PROTOCOL = HTTP/1.1
SERVER_SOFTWARE = WSGIServer/0.2
REQUEST_METHOD = GET
PATH_INFO = /
QUERY_STRING =
CONTENT_TYPE = text/plain
HTTP_HOST = 127.0.0.1:9999
HTTP_CONNECTION = keep-alive
HTTP_SEC_CH_UA = "Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"
HTTP_SEC_CH_UA_MOBILE = ?0
HTTP_DNT = 1
HTTP_UPGRADE_INSECURE_REQUESTS = 1
HTTP_USER_AGENT = Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36
HTTP_ACCEPT = text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
HTTP_SEC_FETCH_SITE = none
HTTP_SEC_FETCH_MODE = navigate
HTTP_SEC_FETCH_USER = ?1
HTTP_SEC_FETCH_DEST = document
HTTP_ACCEPT_ENCODING = gzip, deflate, br
HTTP_ACCEPT_LANGUAGE = zh-CN,zh;q=0.9,en;q=0.8
abc~~~~
修改访问地址为127.0.0.1:9999/abc?d=100,再比较下变化
start_response
- start_response(status, response_headers, exc_info=None)
- status:状态码和状态描述,例如 200 OK
- response_headers:一个元素为二元组的列表,例如
[('Content-Type', 'text/plain;charset=utf-8')]
,可修改Content-Type即MIME类型来决定浏览器响应结果的展示形式 - exc_info:在错误处理的时候使用
可调用对象
通过以上的分析已经有如下的结论,app必须满足如下条件
- app必须是可调用对象,且接受environ和start_response两个参数
- 在返回响应body之前必须调用start_response
- app返回的是一个可迭代对象
可调用对象可以是函数、类、实现了__call__
方法的类的实例,关于可调用对象可参看可调用对象,接下来我们来看看除函数以外的app实现
-
函数
如上demo_app和demo_app,这里不再过多介绍 -
类
from wsgiref.simple_server import make_server
host = '127.0.0.1'
port = 9999
class Application:
def __init__(self, environ, start_response):
self.environ = environ
self.start_response = start_response
self.html = '<h1>欢迎来到 monkeyjerry 的个人小站</h1>'.encode("utf-8")
def __iter__(self):
# __iter__必须返回迭代器iterator
self.start_response("200 OK", [('Content-Type', 'text/html; charset=utf-8')])
# yield self.html
return iter([self.html])
server = make_server(host, port, Application)
server.serve_forever()
更多关于类容器化可参看魔术方法iter
- 实现了
__call__
方法的类实例
from wsgiref.simple_server import make_server
host = '127.0.0.1'
port = 9999
class App:
def __init__(self):
self.html = b'Hello MonkeyJerry\n'
def __call__(self, environ, start_response):
start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
return [self.html]
server = make_server(host, port, App()) # App()类实例是可调用对象
server.serve_forever()
可进一步参看可调用对象__call__
Web框架
Web框架如Django,Flask等可以看作是比较复杂的App
有了WSGI接口标准协议,则允许开发者互不干扰地选择 Web 框架及 Web 服务器的类型。Web 服务器必须实现 WSGI 接口的服务器部分,而现代的 Python Web 框架均已实现了 WSGI 接口的框架部分。现在,则可以真正将 Web 服务器及框架任意搭配,比如,你可以使用 Django,Flask 或者 Pyramid,与 Gunicorn,Nginx/uWSGI 或 Waitress 进行结合
这样我们就只需专注于其擅长的部分来进行开发,而不需要触及另一部分的代码。其它语言也拥有类似的接口,比如:Java 拥有 Servlet API,而 Ruby 拥有 Rack