首先让我们了解一下WSGI规范是什么?简单来说,WSGI就是服务器和应用程序之间的接口。前端的请求传给服务端后,比如gunicorn,服务端再将请求转发给应用程序。因为服务器很多,如果我们根据不同的服务为自己的应用编写不同的代码会很麻烦,所以出现了WSGI。WSGI规定应用程序应该实现一个可调用对象(函数、类、方法或带有__call__的实例),该对象应该接受两个位置参数:环境变量(如头信息、状态码等)、回调函数(WSGI服务器是responsible),用于发送http状态和headers等。同时,该对象需要返回一个可迭代的响应文本。更具体的解释可以去google搜索相关知识。最简单的实现之一:defapp(environ,start_response):response_body=b"Hello,World!"status="200OK"#将响应状态和标头移交给gunicorn等WSGI服务器start_response(status,headers=[])returniter([response_body])我们可以直接使用gunicorn等服务启动此应用程序。有了WSGI规定,框架必须实现规范要求的部分。我们来看看Flask是怎么做的。注:笔者分析的是Flask0.1版本的代码。Flask0.1版本的实现只有一个文件,总共600多行代码。根据官方文档,最简单的Web服务如下所示:fromflaskimportFlaskapp=Flask(__name__)@app.route('/')defhello_world():return'HelloWorld!'if__name__=='__main__':app.run()调用Flask()后会发生什么?首先,__init__内置方法中有几个变量:classFlask(object):def__init__(self,package_name):#view_functions存储视图函数名和视图函数self.view_functions={}#路由字典self.url_map=map()根据名字可以猜到view_functions是用来存放视图函数的,url_map是用来存放路由字典的。暂且跳过,先看看__call__内置方法:def__call__(self,environ,start_response):returnself.wsgi_app(environ,start_response)environ,start_response,你在哪见过?WSGI规范要求的实现是否正确?它返回wsgi_app方法:defwsgi_app(self,environ,start_response):withself.request_context(environ):rv=self.preprocess_request()ifrvisNone:rv=self.dispatch_request()response=self.make_response(rv)response=self.process_response(response)returnresponse(environ,start_response)看到了吗,是不是很像我们上面实现的简单app?先对请求进行预处理,然后将请求分发到不同的视图函数,最后进行响应。我们先看看dispatch_request是如何实现的:defdispatch_request(self):#简化代码.....defmatch_request(self):rv=_request_ctx_stack.top.url_adapter.match()request.endpoint,request.view_args=rvreturnrvdispatch_request首先获取endpoint和一些变量,然后在view中找到对应的view函数字典函数返回。Endpoint和values是我们定义路由的处理函数时,例如:url_for('profile',username='JohnDoe'),其中profile是endpoint,也就是对应视图函数的名称,以及用户名是变量。match_request中的这个_request_ctx_stack是什么。好像是用来匹配路由的。_request_ctx_stack是请求上下文栈,使用一个栈将当前请求相关的数据压入栈中,然后进行路由分发和后续处理,处理完成后退出。具体的,我们回头看wsgi_app方法中有一个with语句,控制请求上下文的进入和退出。withself.request_context(environ):这个request_context是这样的:class_RequestContext(object):def__init__(self,app,environ):...self.url_adapter=app.url_map.bind_to_environ(environ)...def__enter__(self):_request_ctx_stack.push(self)def__exit__(self,exc_type,exc_value,tb):iftbisNoneornotself.app.debug:_request_ctx_stack.pop()其中url_adapter获取路由字典,然后一起和其他变量一起入栈,所以在上面的match_request方法中,从栈中获取url_adapter,然后匹配路由找到对应的端点和参数,然后根据端点从view_functions中找到对应的视图函数和参数。self.url_adapter=app.url_map.bind_to_environ(environ)rv=_request_ctx_stack.top.url_adapter.match()其中bind_to_environ将url绑定到当前环境,返回一个适配器,然后适配器匹配请求。这两个方法都来自Flask的底层调用werkzeug。梳理一下流程,首先adapter在url_map中查找当前路由对应的endpoint和values,然后dispatch_request根据endpoint找到对应的viewfunction,然后返回。那么,url_map中路由和端点的对应关系从何而来呢?我们在使用Flask的时候,是不是需要用装饰器来添加路由和方法来查看函数,对吧,像这样:@app.route('/')defhello_world():return'HelloWorld!'这个路由装饰器看起来像这样,你可以看到它调用了add_url_rule方法。defroute(self,rule,**options):defdecorator(f):self.add_url_rule(rule,f.__name__,**options)self.view_functions[f.__name__]=freturnf返回装饰器defadd_url_rule(self,规则,端点,**选项):options['endpoint']=endpointoptions.setdefault('methods',('GET',))self.url_map.add(Rule(rule,**options))add_url_ruleadd将路由和端点添加到url_map中,这样的请求路由过来后,url_adapter.match()可以匹配到对应的端点,然后根据端点从view_functions中搜索视图函数。url_map是werkzeug中的Map对象,然后添加Rule对象。它看起来像这样:self.url_map=Map([Rule('/',endpoint='home'),Rule('/book/
