中间件 - Middleware
对于服务端来说中间件可以充当应用,响应服务器发来的请求;而对应用端来说中间件则充当了服务器的角色,与应用端进行交互。中间件就像餐厅里的服务员,把大餐(数据)从厨师(服务器)手中接过,传递给客户(应用端),客户如果对饭菜不满意,会经由服务员反馈给大厨。
中间件这个概念在很多 web 框架中都存在,这与编程语言无关。nodejs 和 django 会更加显式的申明中间件,比如下面 nodejs 的中间件,每次应用端收到 request 都会打印出当前时间。
app.use((req, res, next) => {
console.log("Time:", Date.now());
next();
});
作为 WSGI 框架之一的 flask 则用了 extension/plugin 的概念,应用端 app 被 extension 包裹,以达到中间件的目的。
当然也可以直接用 flask 的 before_request, after_request 去达到同样效果。
"""
Example how flask uses middleware wrapping around the application
"""
from flask import Flask
from flask_login import LoginManager
app = Flask(__name__)
login_manager = LoginManager()
login_manager.init_app(app)
下面这一部分是 flask_login 里 init_app 的代码,作用是把 LoginManager 这个中间件挂载在 flask app 这个全局实例上,同时在每次 request 循环后触发一个_update_remember_cookie 的任务来更新 cookie,以保持登录状态。
def init_app(self, app, add_context_processor=True):
"""
Configures an application. This registers an `after_request` call, and
attaches this `LoginManager` to it as `app.login_manager`.
:param app: The :class:`flask.Flask` object to configure.
:type app: :class:`flask.Flask`
:param add_context_processor: Whether to add a context processor to
the app that adds a `current_user` variable to the template.
Defaults to ``True``.
:type add_context_processor: bool
"""
app.login_manager = self
app.after_request(self._update_remember_cookie)
if add_context_processor:
app.context_processor(_user_context_processor)
第一个 WSGI 中间件
我们来自己做一个 WSGI 中间件。
hello_world_app 是一个简单的 WSGI 应用,它会返回 Hello World 字符串,并添加相应的 headers,给客户端浏览器。
log_environ 是我们做的中间件,采用闭包和装饰器。内部的_inner 函数会打印出 WSGI 服务端(httpd)发来的 environ 环境变量, 最终把由 hello_world_app WSGI 应用端产生的结果返回给浏览器。
所以这个流程是先由浏览器发送 get 请求给 WSGI 服务器,服务器会整理请求的环境变量并把 environ 和 start_response 对象打包发给 WSGI 中间件,这个 log_environ 中间件会先把 environ 打印出来,然后调用 WSGI 客户端 hello_world_app,并把客户端的结果 Hello World 返回给服务器,再由服务器传回浏览器,最终结果在浏览器上呈现。
可以看的出 log_environ 就是装饰器,目的是加强 WSGI 应用端原有的功能,所以在 hello_world_app 上采用装饰器语法 @log_environ 也是能正常工作的。
写法一
from wsgiref.simple_server import make_server
def hello_world_app(environ, start_response):
status = '200 OK' # HTTP Status
# HTTP Headers
headers = [('Content-type', 'text/plain; charset=utf-8')]
start_response(status, headers)
# The returned object is going to be printed
return [b"Hello World"]
def log_environ(handler):
"""print the envrionment dictionary to the console"""
from pprint import pprint
def _inner(environ, start_function):
pprint(environ)
return handler(environ, start_function)
return _inner
# this will show "Hello World!" in your browser,
# and the environment in the console
app = log_environ(hello_world_app)
httpd = make_server('', 8000, app)
print("Serving on port 8000...")
# Serve until process is killed
httpd.serve_forever()
写法 2
from wsgiref.simple_server import make_server
def log_environ(handler):
"""print the envrionment dictionary to the console"""
from pprint import pprint
def _inner(environ, start_function):
pprint(environ)
return handler(environ, start_function)
return _inner
@log_environ
def hello_world_app(environ, start_response):
status = '200 OK' # HTTP Status
# HTTP Headers
headers = [('Content-type', 'text/plain; charset=utf-8')]
start_response(status, headers)
# The returned object is going to be printed
return [b"Hello World"]
httpd = make_server('', 8001, hello_world_app)
print("Serving on port 8001...")
# Serve until process is killed
httpd.serve_forever()