在python web开发中,有两个概念非常重要,一个是wsgi,一个是asgi。
WSGI
一个web应用可以简单认为:用户发起网络请求——服务器返回数据。
当访问一个html界面的时候,就是用户发起http请求,服务器返回一个html文档。 这是很好理解的。
graph TD
客户端 --请求地址--> 服务端
graph TD
服务端 --html文档--> 客户端
现实生活中,不可能每个请求都是固定的文档,一定会有服务需要经过程序运算,比如从数据库中查询出最新的数据。因此,客户端请求过来的信息,需要经过服务端的web应用程序处理。
如果每个web应用程序都需要去解析HTTP协议的内容,那太麻烦了,最好是有一个统一的接口,将HTTP的内容转换成web应用程序所需要的格式。这样,不同的框架可以使用同一套转换后的格式,开发者也可以专注于业务内容。
这就是WSGI的需求。
WSGI全称是:Web Server Gateway Interface,由PEP333提出,还有一个python3升级版本的PEP3333。
通过上面的介绍,我们可以认为WSGI是在客户端和服务端的应用程序之间的桥梁,这时候可以认为请求的过程是这样的:
graph TD
客户端 --> WSGI --> web应用程序
例如,你希望当你访问example.com?name=Tom 的时候,返回Hello Tom,结果会根据你传递的数据,动态的变化。 那么你希望你服务端程序要从头解析HTTP吗? 显然是不想这么做的,你所需要的是从HTTP请求中获取到name,然后返回Hello xxx。这时候请求的流动是这样:
graph TD
客户端 --> WSGI --> hello_world函数
这样的流程图可能不太准确,因为WSGI是一个协议,将HTTP数据进行转换是需要web服务器(uwsgi、gunicorn)完成的,后面会介绍。
符合WSGI协议的接口
WSGI的协议要求一个【可调用对象】,接收两个参数,一个是包含HTTP信息的environ,一个是发起HTTP响应的函数start_response,这个函数包含响应HTTP状态码以及响应的header,最后return一个可迭代对象。
def app(environ,start_response):
...
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'hello world']
注意这里要求的是【可调用对象】,意味着你可以使用一个对象,这个对象要实现__call__。
start_response第二个参数是一个列表,每个元素都包含着请求头的key和value。
最后返回的是一个可迭代对象。
那么,这个函数要怎么调用呢? 这里就要使用实现了WSGI的web服务器。
web服务器
请求应该先通过web服务器,web服务器依照WSGI协议,将请求的数据进行转换,然后调用实现了这个协议的可调用对象。 直接上例子:
from wsgiref.simple_server import make_server
def application(environ, start_response):
print(environ)
# 构造响应内容
response_body = b"Hello, World!"
# 设置响应头
status = "200 OK"
response_headers = [("Content-Type", "text/plain"), ("Content-Length", str(len(response_body)))]
# 调用start_response函数发送响应头
start_response(status, response_headers)
# 返回响应内容作为可迭代对象
return [response_body]
class App:
def __call__(self, environ,start_response):
print(environ)
# 构造响应内容
response_body = b"Hello, World!"
# 设置响应头
status = "200 OK"
response_headers = [("Content-Type", "text/plain"), ("Content-Length", str(len(response_body)))]
# 调用start_response函数发送响应头
start_response(status, response_headers)
# 返回响应内容作为可迭代对象
return [response_body]
if __name__ == "__main__":
# 创建一个WSGI服务器,监听本地的127.0.0.1:6000地址
# 这里传入App()或者application都可以,它需要的是【可调用对象】
# server = make_server("127.0.0.1", 6000, application)
server = make_server("127.0.0.1", 6000, App())
print("服务器已启动,监听地址:127.0.0.1 端口:6000")
# 开始监听请求,直到按下Ctrl+C停止服务器
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
然后访问http://127.0.0.1:6000 将获得响应。
注意打印出来的eviron,它是一个包含了HTPP数据的字典。
{....,'kubernetes.docker.internal', 'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_PORT': '6000', 'REMOTE_HOST': '', 'CONTENT_LENGTH': '14', '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': 'application/json', 'HTTP_USER_AGENT': 'PostmanRuntime-ApipostRuntime/1.1.0', 'HTTP_CACHE_CONTROL': 'no-cache', 'HTTP_ACCEPT': '*/*', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_HOST': '127.0.0.1:6000', 'wsgi.input': <_io.BufferedReader name=592>, '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'>}
make_server会启动一个服务,监听127.0.0.1:6000,它就充当上述所说的web服务器。此时,它接收HTTP请求,根据WSGI协议进行转换,然后调用application,将application的结果返回。
在使用Django的manage.py runserver的时候,也会启动一个服务。使用Flask的app.run()的时候,也是一样。 这些框架都自身提供了实现WSGI协议的web服务器,但是这些都不适合生产,性能比较底下。
#Flask demo
from flask import Flask
# 创建 Flask 应用程序实例
app = Flask(__name__)
# 定义路由和视图函数
@app.route('/')
def hello():
return 'Hello, World!'
# 启动应用程序
if __name__ == '__main__':
app.run(host='127.0.0.1', port=6000)
实际生产中uwsgi、gunicorn是常用的web服务器,不过通常还会再加一层nginx以便更好处理静态文件。 所以是:nginx + uwsgi/gunicorn + Django/Flask + Mysql + ...