flask上下文管理

上下文分类

  • 请求上下文:包括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
    9
    pip 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
2
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)

self.wsgi_app

总调用接口,按步骤解耦为各个模块,wsgi接口处理uwsgi之类的分析好的http请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
```

### self.request_context(environ)
生成一个上下文实例对象,包括request、session、url_adapter、flashes

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

### ctx.push()
一看到local就想到threadlocal,这里即是前面提到的分割绑定request方法,

#### 步骤
主要思路是用两个全局线程栈去分别储存request、session、g、current_app

- 1.声明两个全局线程栈分别为_request_ctx_stack和_app_ctx_stack,当ctx.push()
就把ctx传入_request_ctx_stack.Local.storage\[indent]\[stack]中

- stack构成为主要储存为一个Local的类字典中的storage
- 传入name后,通过get_ident和name双层查找返回对象

- 2.request和session是LocalProxy对象
- LocalProxy()
- 偏函数partial(_lookup_req_object, "request")

# _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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def full_dispatch_request(self):
"""Dispatches the request and on top of that performs request
pre and postprocessing as well as HTTP exception catching and
error handling.

.. versionadded:: 0.7
"""
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 as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)
  • 首先执行before_first_request
  • 发送request_started信号
  • 执行所有的process_request的请求扩展(包括蓝图)
  • self.dispatch_request()
    • 解析路由
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      req = _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
2
status/headers
start_response('200 OK', [('Content-Type', 'text/html')])

就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。
start_response()函数接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP Header,每个
Header用一个包含两个str的tuple表示。通常情况下,都应该把Content-Type头发送给浏览器。其他很
多常用的HTTP Header也应该发送。

分享到