简单看了一下PEP-0333,谈谈个人见解:
WSGI里的组件分为『Server』,『Middleware』和『Application』三种,其中的『Middleware』是『设计模式』里的Decorator(装饰器)。
WSGI规范在PEP-333里讲得很详细: ,但我觉得比理解规范更重要的,是理解其设计目的和工作原理。
WSGI规范写得有点绕,之所以绕, 主要原因可能是没有用『类型提示(Type Hints)』,如果用强类型OOP语言的那种『Interface』和『UML』来解释会清晰很多。下面我试试用这种带有『类型提示』的风格简单讲讲WSGI的原理。
首先是『Application』 如果把它算作接口的话,它 要能被直接调用(Callable),换句话说,它应该是一个函数或者定义了『__call__』方法的对象,『__call__』方法的签名是这样的:
def __call__(environ: Dict[String, Any], start_response: (String, List) -> Any): Iterable[String]
为了便于叙述,这里用的『类型提示』没有严格遵从Python语法。这里大致的意思是 __call__ 方法需要两个参数,『environ』是一个键(Key)为 String 类型,值(Value)为 任意(Any)类型的字典,『start_response』则是一个函数(实际上为了兼容已有框架,它也可以是一个函数工厂,详见PEP-0333)。 __call__ 方法返回一个可迭代的对象。
可以这样理解,如果想正确调用『Application』,就必须知道两样东西,『数据』和『上层处理方法』,『数据』是那个environ变量(CGI里就叫这个名字),『上层处理方法』是那个start_response 回调函数,这两个参数,须在Server调用Application的时候,传给Application。或者说Application依赖于『数据』和『上层处理方法』,这两个依赖由Server『注入』给Application(称作『依赖注入 DI』)。
容易困惑的一点是为什么『上层处理方法』要通过参数传给Application,直接在Server里处理好了,把处理好的结果传给Application不是更简单吗?的确这样做似乎也可以,不过把『上层处理方法』作为回调函数传给Application,可以由Application本身控制该方法的调用时机,是更加灵活的一种方案(称作『控制反转 IoC』)。
接下来是『Middleware』 为了简化代码,我们先把『Application』单独定义为一个类型:
type Application = (Dict[String, Any], (String, List) -> Any) -> Iterable[String]
其实就是前面那个 __call__ 方法的签名,至于Application究竟是什么,叫它『函数』,或者『实现了__call__方法的 对象』,『实现了__init__方法的类』都可以,总之都可以通过『Application()』去使用,我们暂且统称为『Callable对象』。
『Middleware』本质上是一个『装饰器 Decorator』,和Application类似它也是一个『Callable对象』,如果它有『__call__』方法,其签名应该是这样的:
def __call__(app: Application): transformedApp: Application
这个Middleware返回的类型是一个被『装饰』过的Application(transformedApp变量),这个transformedApp所依赖的『environ』和『start_response』可以被当前Middleware所在上层的Server/Middleware所注入。
再看 这个Middleware的参数,它也是一个Application(app变量),所以Middleware本身的一些附加的逻辑和数据也可以通过app的参数注入到下层的Application里。
Middleware是可以嵌套使用的,比如有『mw1』和『mw2』两个Middleware和『app』一个Application,就可以通过
返回一个新的Application,如果mw1和mw2是相互独立的,嵌套顺序理论上也可以互换。
也可以使用Python的『Decorator』语法,直接装饰app:
@mw1@mw2def app(environ, start_response): ...
最后是『Server』 好像能说的不多,这一层更像是一个『适配器 Adapter』,比如PEP-0333里面给的例子就是一个『CGI』的Adapter。它从 os.environ 或 sys.stdin(CGI规定的输入方式) 中获取Request数据,自定义了start_response和write函数,在其中使用 sys.stdout(CGI规定的输出方式) 进行响应。它把environ和start_response传给Application进行调用,然后遍历Application返回的Iterable,使用write函数把结果写到sys.stdin里。 写进sys.stdin,其实就是和更上层的,也就是Web Server(例如Apache/Nginx)进行通讯,Web Server通过Socket获取到内容以后,就可以最终把Response返回给用户了。