上下文分类
- 请求上下文:包括request和session,保存请求相关的信息
- request:封装客户端发送的请求报文数据
- session:用于记住请求之间的数据,通过签名的cookie实现,常用来记住用户登录状态
- 程序上下文:包括current_app和g,为了更好的分离程序的状态,应用起来更加灵活,方便调测等
- current_app:指向处理请求的当前程序实例,比如获取配置,经常会用current_app.config
- g:当前请求中的全局变量,因为程序上下文的生命周期是伴随请求上下文产生和销毁的,
所以每次请求都会重设。一般我会在结合钩子函数在请求处理前使用。
如何贯穿request
如何解决request多线程、多协程并存的问题?
思路获取线程或者协程的唯一标识:构造带有唯一标识的request,用_thread.get_ident只能获取线程的唯一标识
所以flask使用了gevent进行处理(协程支持)gevent
1
2
3
4
5
6
7
8
9pip install gevent
try:
# 优先用协程做唯一标识
from greenlet import getcurrent as get_indent # 协程支持
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident # 线程支持
上下文请求流程
请求 -> nginx -> uwsgi -> app.__call__ -> self.wsgi_app -> self.request_context(environ)
-> ctx.push() -> self.full_dispatch_request() -> return response(environ, start_response)
app.__call__(environment, start_response)
1 | def __call__(self, environ, start_response): |
self.wsgi_app
总调用接口,按步骤解耦为各个模块,wsgi接口处理uwsgi之类的分析好的http请求
1 | def wsgi_app(self, environ, start_response): |
def init(self, app, environ, request=None, session=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = None
try:
self.url_adapter = app.create_url_adapter(self.request)
except HTTPException as e:
self.request.routing_exception = e
self.flashes = None
self.session = session
1 |
|
# _lookup_req_object
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
1
- 当调用request.xxx会发生什么?
# LocalProxy
def __getattr__(self, name):
if name == "__members__":
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- self._get_current_object()
- return self.__local()
- _lookup_req_object(request)
- return getattr(top, 'request')
这里top取出来的是一个ctx对象,然后返回top.request,_request_stack是通过
一个threadlocal进行存储,所以self._local.stack[-1]获取到
_request_stack.Local.storage[ident][stack][-1]
- getattr(request, 'xxx')
返回request.xxx
```
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))
g = LocalProxy(partial(_lookup_app_object, "g"))
由于源码比较多,可以打开flask通过debug查看,或者按照请求流程层层往下查看
self.full_dispatch_request()
1 | def full_dispatch_request(self): |
- 首先执行before_first_request
- 发送request_started信号
- 执行所有的process_request的请求扩展(包括蓝图)
- self.dispatch_request()
- 解析路由
1
2
3
4
5
6
7
8
9
10
11
12req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if (
getattr(rule, "provide_automatic_options", False)
and req.method == "OPTIONS"
):
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint - 调用视图
1
return self.view_functions[rule.endpoint](**req.view_args)
- 解析路由
- self.finalize_request(rv)
- 构造make_response(rv)response
- 执行process_response请求扩展
- 发送 request_finished信号
start_response的作用
1 | status/headers |
就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。
start_response()函数接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP Header,每个
Header用一个包含两个str的tuple表示。通常情况下,都应该把Content-Type头发送给浏览器。其他很
多常用的HTTP Header也应该发送。