WSGI是什么?

289 阅读4分钟

在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 + ...