从 flask 的 hello world 说起
直接讨论 WSGI,很多人可能没有概念,我们还是先从一个简单的 hello world 程序开始吧。
from flask import Flask
app = Flask(__name__)
@app.route("/", methods=['GET'])
def index():
return "Hello world!"
if __name__ == '__main__':
app.run(port=8000)
相信只要使用过 flask 框架或者其它 Python 框架的人,应该都会见过这个警告。这个警告的意思是:这是一个开发服务器。不要在生产环境使用它。使用一个生产的 WSGI 服务器来代替。
那么什么是 WSGI 呢?
WSGI is the Web Server Gateway Interface. It is a specification that describes how a web server communicates with web applications, and how web applications can be chained together to process one request.
WSGI 是 Web 服务器网关接口。它是一个规范,描述了web服务器如何与web应用程序通信,以及如何将web应用程序链接在一起以处理一个请求。
这里来看一个简略的时序图:
它描述了用户通过浏览器上网的过程,浏览器发送请求到 Server,Server 再把请求交给 Application 来处理,最后返回响应数据给用户的过程。(通常来说,请求在到达 Server 之前,还会先经过一个反向代理服务器,例如 nginx 或者 apache 等)
上面这个时序图其实可以看做是大部分网络应用程序的模式了。从请求到达应用和响应返回给用户,这个过程其实是固定的。所以,一个web应用的区别就在于它对请求的处理方式上,例如:用户使用百度、搜狗亦或是谷歌浏览器,对于用户的感知其实是相同的(尽管它们内部的实现并不相同,这里也不考虑用户的体验问题,哈哈)。
什么是 WSGI?
我们都知道通用的部分可以抽取出来,做成一个组件供其它应用使用。所以 WebServer 就是这样一个组件,它负责接收用户的请求,然后交给用户的Web应用,等到它处理完成之后,再把响应数据返回给调用者。所以,WebServer 要和 WebApplication 进行交互,那就需要定义一个协议或者更专业一点叫做接口,因此这就是 WSGI。而实现 WSGI 接口的,我们则成为 WebServer 或者 WSGI Server。
注:如果你有 java web 的背景,相信你应该使用过 tomcat,也听过 servlet API。其实,它们之间的关系和WSGI服务器与 WSGI接口的关系是类似的。
它的接口其实很简单,只要 Web 框架实现了它,就可以使用各种实现了 WSGI 接口的服务器了。
我们常用的 Web 框架有:falsk、django 等;WSGI 服务器有:gunicorn、uWSGI等。
对于我们入门学习来说,它其实也是很简单的,我们也不需要了解那么多。对于Web 框架的作者来说,他们需要提供一个带两个参数的可调用对象即可:
callable(environ, start_response)
下面是 Python 的 PEP3333 中提供的两个简单例子,一个是函数实现,一个是类实现,它们都是一个 Web Application。
HELLO_WORLD = b"Hello world!\n"
def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [HELLO_WORLD]
class AppClass:
"""Produce the same output, but using a class
(Note: 'AppClass' is the "application" here, so calling it
returns an instance of 'AppClass', which is then the iterable
return value of the "application callable" as required by
the spec.
If we wanted to use *instances* of 'AppClass' as application
objects instead, we would have to implement a '__call__'
method, which would be invoked to execute the application,
and we would need to create an instance for use by the
server or gateway.
"""
def __init__(self, environ, start_response):
self.environ = environ
self.start = start_response
def __iter__(self):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield HELLO_WORLD
Python 官方提供了一个简单的 WSGI 实现,我们就以上面这两个例子为例来演示一下:
from wsgiref.simple_server import make_server
if __name__ == '__main__':
with make_server("127.0.0.1", 8000, simple_app) as httpd:
print("Server started on port 8000...")
httpd.serve_forever()
使用基于类的实现,效果也是一样的,通常更推荐使用类的方式,因为可以利用面向对象的思想来编程。
from wsgiref.simple_server import make_server
if __name__ == '__main__':
with make_server("127.0.0.1", 8000, AppClass) as httpd:
print("Server started on port 8000...")
httpd.serve_forever()
甚至,我们还可以再进行一步,使用这个 Python 自带的 WSGI 服务来运行最开始的 flask 的 hello world:
from wsgiref.simple_server import make_server
from flask import Flask
app = Flask(__name__)
@app.route("/", methods=['GET'])
def index():
return "Hello world!"
if __name__ == '__main__':
with make_server("127.0.0.1", 8000, app) as httpd:
print("Server started on port 8000...")
httpd.serve_forever()
这样再次运行它,连警告也没有了。不过,自带的这个只是一个简单的实现,官方也并不推荐在生产环境使用它。但是 flask 其实并不知道,我们使用了什么 WSGI 服务器,因为它也不关心这个。
flask 背后的秘密
甚至不需要深入 flask 的源码,只需要浅浅的一探,我们就能明白为什么上面那样做的可以的了。这是 Flask 类的源码的一小部分,其它的无需解释了,我相信这是不言自明的。
class Flask(App):
"""The flask object implements a WSGI application and acts as the central
object.
"""
def wsgi_app(
self, environ: WSGIEnvironment, start_response: StartResponse
) -> cabc.Iterable[bytes]:
...
def __call__(
self, environ: WSGIEnvironment, start_response: StartResponse
) -> cabc.Iterable[bytes]:
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app`, which can be
wrapped to apply middleware.
"""
return self.wsgi_app(environ, start_response)
总结
关于 WSGI,它的文档就直接说明了,对于 Web 开发者来说,其实并不用去了解它。因为我们的工作重点其实是请求的处理,对于我上面序列图中的大部分操作都不需要关系了,它隐藏了很多的细节,使得我们可以更加专注于自己的工作内容。不过,我觉得简单的了解一下也是蛮好的,也不需要深入的了解,浅尝辄止即可。对于我们工作的整个领域都有一个概览,然后还是专注于自己工作的重点内容上。