flask源码剖析
这段时间想重新写个自己的博客系统,又正好在看一些框架源码,然后就想要不顺便写个小框架吧,既然想写框架,要不再顺便写个orm吧,再写个小的异步Server吧。。事实证明饭要一口一口吃
先梳理一下flask工作的整个流程吧。
首先flask是符合wsgi协议的,那么也就是说,flask要实现一个可以callable的object给web server。
那么什么是wsgi? 简而言之,wsgi就是把框架和web Server协同工作的一个协议,规范。流程大概是:当有请求来的时候, web Server调用一个callable object(函数,类,实现__call__的实例等等), 传入(environ, start_response), 其中environ是一个字典,包含了请求相关的所有信息,比如请求路径,参数;start_response是一个函数。在这个callable object中会调用start_response,返回http状态码,headers等信息给web server, callable object本身则会返回具体的内容给web server, 返回的object必须是可迭代的。当有callable object本身返回的内容时候, 就会把start_response返回的内容和callable object返回的内容写入缓冲区并且刷新。
那么flask 给web server的callable object是什么?
Class Flask: ... def __call__(self, environ, start_response): """Shortcut for :attr:`wsgi_app`.""" return self.wsgi_app(environ, start_response)
可以看到,是一个实现了__call__的实例,web server 调用Flask的实例,传入environ和start_response,那么这个self.wsgi_app是什么东西?
1 def wsgi_app(self, environ, start_response): 2 """The actual WSGI application. This is not implemented in 3 `__call__` so that middlewares can be applied without losing a 4 reference to the class. So instead of doing this:: 5 6 app = MyMiddleware(app) 7 8 It's a better idea to do this instead:: 9 10 app.wsgi_app = MyMiddleware(app.wsgi_app) 11 12 Then you still have the original application object around and 13 can continue to call methods on it. 14 15 .. versionchanged:: 0.7 16 The behavior of the before and after request callbacks was changed 17 under error conditions and a new callback was added that will 18 always execute at the end of the request, independent on if an 19 error ocurred or not. See :ref:`callbacks-and-errors`. 20 21 :param environ: a WSGI environment 22 :param start_response: a callable accepting a status code, 23 a list of headers and an optional 24 exception context to start the response 25 """ 26 with self.request_context(environ): 27 try: 28 response = self.full_dispatch_request() 29 except Exception, e: 30 response = self.make_response(self.handle_exception(e)) 31 return response(environ, start_response)
在文档里面第一句已经说明了“The actual WSGI application...”这个才是真正的callable object。那么这个真正的callable object会做什么事情?
首先,会打开self.request_context(envion),
class RequestContext(object): def __init__(self, app, environ): self.app = app self.request = app.request_class(environ) self.url_adapter = app.create_url_adapter(self.request) self.g = app.request_globals_class() self.flashes = None self.session = None self.preserved = False self._after_request_functions = [] self.match_request() blueprint = self.request.blueprint if blueprint is not None: # better safe than sorry, we don't want to break code that # already worked bp = app.blueprints.get(blueprint) if bp is not None and blueprint_is_module(bp): self.request._is_old_module = True def match_request(self): try: url_rule, self.request.view_args = \ self.url_adapter.match(return_rule=True) self.request.url_rule = url_rule except HTTPException, e: self.request.routing_exception = e def push(self): app_ctx = _app_ctx_stack.top if app_ctx is None or app_ctx.app != self.app: app_ctx = self.app.app_context() app_ctx.push() self._implicit_app_ctx_stack.append(app_ctx) else: self._implicit_app_ctx_stack.append(None) _request_ctx_stack.push(self) self.session = self.app.open_session(self.request) if self.session is None: self.session = self.app.make_null_session() def pop(self, exc=None): app_ctx = self._implicit_app_ctx_stack.pop() clear_request = False if not self._implicit_app_ctx_stack: self.preserved = False if exc is None: exc = sys.exc_info()[1] self.app.do_teardown_request(exc) clear_request = True rv = _request_ctx_stack.pop() assert rv is self, 'Popped wrong request context. (%r instead of %r)' \ % (rv, self) # get rid of circular dependencies at the end of the request # so that we don't require the GC to be active. if clear_request: rv.request.environ['werkzeug.request'] = None # Get rid of the app as well if necessary. if app_ctx is not None: app_ctx.pop(exc) def __enter__(self): self.push() return self def __exit__(self, exc_type, exc_value, tb): if self.request.environ.get('flask._preserve_context') or \ (tb is not None and self.app.preserve_context_on_exception): self.preserved = True else: self.pop(exc_value)
从__enter__可以看到,首先会把当前请求推入栈,这个是什么栈?有什么用呢?
简而言之,这个栈就是flask根据每次请求时候,为每个请求所创建的线程/协程的id所创建的一个自己的threadlocal,在这个threadlocal中所实现的一个栈结构,会把不同的请求隔离开来,每次请求时候把当前请求所在的app,request推入栈,就可以在全局可用了。
把current_app,request推入栈之后,就根据请求的方法,路径等参数分发请求,找到用@app.route('...')注册的函数,生成response。
但是这里有个问题,就是根据wsgi协议,返回的结果应该是个可迭代的结果,而且这时候start_response还没执行,状态码和其他头部信息还都没有。这个就是make_response的事情了,
def full_dispatch_request(self): self.try_trigger_before_first_request_functions() try: request_started.send(self) rv = self.preprocess_request() if rv is None: rv = self.dispatch_request() except Exception, e: rv = self.handle_user_exception(e) response = self.make_response(rv) response = self.process_response(response) request_finished.send(self, response=response) return response
make_response会把返回结果包装一下。
def make_response(self, rv): if isinstance(rv, basestring): rv = self.response_class(rv, headers=headers, status=status) headers = status = None else: rv = self.response_class.force_type(rv, request.environ) if status is not None: if isinstance(status, basestring): rv.status = status else: rv.status_code = status if headers: rv.headers.extend(headers) return rv
make_response会利用class Response(BaseResponse)把view function返回的结果包装一下,
class Response(ResponseBase): default_mimetype = 'text/html' class BaseResponse(object): automatically_set_content_length = True def __init__(self, response=None, status=None, headers=None, .... @classmethod def force_type(cls, response, environ=None): ... @classmethod def from_app(cls, app, environ, buffered=False): ... def _get_status_code(self): return self._status_code def __call__(self, environ, start_response): app_iter, status, headers = self.get_wsgi_response(environ) start_response(status, headers) return app_iter
当执行Response的实例时候,在__call__里面会执行start_response, 同时返回一个可迭代的对象
这就是flask的执行逻辑
参考资料:
PEP 333 -- Python Web Server Gateway Interface v1.0