2023年11月29日发(作者:)

Flask进阶(⼀)——请求上下⽂和应⽤上下⽂完全解答

(上)

前⾔:

flask的轻便和强⼤的扩展性能会让web的初级开发者甚⾄是有经验的开发者神往。

flask能在短时间内快速搭建web的后台,⽽《flask web开发--基于python的web应⽤开发实战》是最好的flask⼊门教程了。

但当中对应⽤上下⽂和请求上下⽂的讲解有点简单,本⽂对这两个重要的概念做⼀个总结,⽅便⾃⼰以后的回顾。

由于不看源码是很难理解上下⽂的前世今⽣,所有本⽂在某些地⽅会涉及到flask的源码,由于flask和werkzug结合得很紧密,werkzeug也会有所

展⽰。

预备知识:

1Thread Local(本地线程)

从⾯向对象设计的⾓度看,对象是保存“状态”的地⽅。Python 也是如此,⼀个对象的状态都被保存在对象携带的⼀个特殊字典中。Thread Local

则是⼀种特殊的对象,它的“状态”对线程隔离 —— 也就是说每个线程对⼀个 Thread Local 对象的修改都不会影响其他线程。

()

import threading

mydata = ()

= 42

为什么werkzeug还⾃⼰搞了⼀套⽽不直接使⽤呢?

在python中,除了线程之外,还有个叫协程的东东,(这⾥不提进程)。java中貌似是⽆法实现协程的。⽽python的协程感觉⾼⼤尚的样

⼦,python3.5开始对协程内置⽀持,⽽且也有相关开源库greenlet等。

协程是什么?

举个例⼦,⽐如⼀个线程在处理IO时,该线程是处于空闲状态的,等待IO返回。但是此时如果不让我们的线程⼲等着cpu时间⽚耗光,有没有其他办

法,解决思路就是采⽤协程处理任务,⼀个线程中可以运⾏多个协程,当当前协程去处理IO时,线程可以马上调度其他协程继续运⾏,⽽不是⼲等着

不⼲活。

flask从客户端获取到请求时,要让视图函数能访问⼀些对象,这样才能处理请求。例如请求对象就是⼀个很好的例⼦。要让视图函数访问请求对

象,⼀个显⽽易见的⽅法就是将其作为参数传⼊视图函数,不过这回导致程序中的每个视图函数都增加⼀个参数,为了避免⼤量可有可⽆才参数把视

图函数弄得⼀团糟,flask使⽤上下⽂临时把某些对象变为全局可访问(只是当前线程的全局可访问)。

正⽂:

1、请求上下⽂(requestsession

1)⽣命周期

先回顾⼀个请求上下⽂的使⽤例⼦

@('/')

def index():

user_agent = ('User-Agent')

return'

Your broswer is %s

' % user_agent

在视图函数中,可以直接使⽤request来获取请求对象。这⾥牵涉到请求上下⽂的⽣命周期问题了。

def handle_request():

print 'handle request'

print

handle_request() #会收到运⾏时错误:RuntimeError: working outside of request context

request对象只有在请求上下⽂的⽣命周期内才可以访问。离开了请求的⽣命周期,其上下⽂环境也就不存在了,⾃然也⽆法获取request对象。

那什么是请求上下⽂的⽣命周期呢?⼀般就是在视图函数⾥,或者请求钩⼦中,才能使⽤请求上下⽂request。flask在接收到来⾃客户端的请求时,

会帮我们构造请求上下⽂的使⽤环境,当flask根据url跳到相应的视图函数的时候,⾃然⽽然就可以直接使⽤请求上下⽂,请求钩⼦也是同理。

此外,在不同http请求之间得到的request必然也是不⼀样的,它只包含当前http请求的⼀些信息,是线程隔离的。

⽽session不⼀样,session是可以跨请求的,在不同的http请求之间,session都是同⼀个,因此,可以借session来存储⼀些请求之间需要‘记

住’的字典值。

2)请求上下⽂环境构造(本篇幅有点长,涉及flask在接收http请求后的⼯作过程)

from flask import Flask

app = Flask(__name__) #⽣成app实例

@('/')

def index():

return 'Hello World'

flask是遵循WSGI接⼝的web框架,因此它会实现⼀个类似如下形式的函数以供服务器调⽤:

def application(environ, start_response): #⼀个符合wsgi协议的应⽤程序写法应该接受2个参数

start_response('200 OK', [('Content-Type', 'text/html')]) #environhttp的相关信息,如请求头等 start_response则是响应信息

return [b'

Hello, web!

'] #return出来是响应内容

class Flask(_PackageBoundObject): #Flask

#中间省略⼀些代码

def __call__(self, environ, start_response): #Flask实例的__call__⽅法

"""Shortcut for :attr:`wsgi_app`."""

return _app(environ, start_response) #注意他的return,他返回的时候,实际上是调⽤了wsgi_app这个功能

如此⼀来,我们便知道,当http请求从server发送过来的时候,他会启动__call__功能,最终实际是调⽤了wsgi_app功能并传⼊environ和

start_response。

接下来查看flask中符合wsgi接⼝的这么⼀个函数wsgi_app:

class Flask(_PackageBoundObject):

#中间省略⼀些代码

def wsgi_app(self, environ, start_response):

ctx = t_context(environ)

()

error = None

try:

try:

response = _dispatch_request() #full_dispatch_request起到了预处理和错误处理以及分发请求的作⽤

except Exception as e:

error = e

response = _response(_exception(e)) #如果有错误发⽣,则⽣成错误响应

class RequestContext(object):

def __init__(self, app, environ, request=None):

= app #appFlask(__name__)实例

if request is None:

request = t_class(environ) #request_class实质上是rs下的class Request。这⾥根据environ创建⼀个Request实例

t = request

_adapter = _url_adapter(t)

s = None

n = None

self._implicit_app_ctx_stack = []

ved = False

self._preserved_exc = None

self._after_request_functions = []

_request()

第⼀个http请求过来时,request默认是空的,通过t_class(environ)构造

因此ctx获得⼀个刚初始化过的RequestContext实例,ctx包含了当前请求的request、session等各种信息,⾮常重要。

然后使⽤()⽅法,这个⽅法很重要,继续查看:

class RequestContext(object):

def push(self):

top = _request_ctx_ #top其实是个RequestContext实例

if top is not None and ved:

(top._preserved_exc)

app_ctx = _app_ctx_

if app_ctx is None or app_ != :

app_ctx = _context()

app_()

self._implicit_app_ctx_(app_ctx)

else:

self._implicit_app_ctx_(None)

class LocalStack(object):

def __init__(self):

self._local = Local() #由此可以看出LocalStack实例包裹了⼀个Local类的实例

def push(self, obj):

rv = getattr(self._local, 'stack', None)

if rv is None:

self._ = rv = []

(obj)

return rv

def pop(self):

stack = getattr(self._local, 'stack', None)

if stack is None:

return None

elif len(stack) == 1:

release_local(self._local)

return stack[-1]

else:

return ()

@property

def top(self):

try:

return self._[-1]

except (AttributeError, IndexError):

return None

#LocalStackpushpoptop的操作对象都是⾃⾝的_local对象。

def _lookup_req_object(name):

top = _request_ctx_

if top is None:

raise RuntimeError(_request_ctx_err_msg)

return getattr(top, name)

# context locals

_request_ctx_stack = LocalStack()

_app_ctx_stack = LocalStack()

request = LocalProxy(partial(_lookup_req_object, 'request'))

session = LocalProxy(partial(_lookup_req_object, 'session'))

request和session都是先从_request_ctx_stack⾥获取头元素,这个元素是RequestContext实例,它包含了request、session等等的很多信

息。request和session就是从它⾝上获取的。这⾥可以看到,request和session其实是⼀个LocalProxy实例,LocalProxy其实就是⼀个代理,⼀

个为werkzeug的Local对象服务的代理。他把所以作⽤到⾃⼰的操作全部“转发”到 它所代理的对象上去。它初始化的⽅法如下:

@implements_bool

class LocalProxy(object):

__slots__ = ('__local', '__dict__', '__name__', '__wrapped__')

def __init__(self, local, name=None):

# 这⾥有⼀个点需要注意⼀下,通过了__setattr__⽅法,self

@implements_bool

class LocalProxy(object):

def _get_current_object(self):

"""

获取当前被代理的真正对象,⼀般情况下不会主动调⽤这个⽅法,除⾮你因为

某些性能原因需要获取做这个被代理的真正对象,或者你需要把它⽤来另外的

地⽅。

"""

# 这⾥主要是判断代理的对象是不是⼀个werkzeugLocal对象,在我们分析request

# 的过程中,不会⽤到这块逻辑。

if not hasattr(self.__local, '__release_local__'):

# LocalProxy(partial(_lookup_req_object, 'request'))看来

# 通过调⽤self.__local()⽅法,我们得到了 partial(_lookup_req_object, 'request')

# 也就是 ``_request_ctx_t``

return self.__local()

try:

return getattr(self.__local, self.__name__)

except AttributeError:

raise RuntimeError('no object bound to %s' % self.__name__)

# 接下来就是⼀⼤段⼀段的Python的魔法⽅法了,Local Proxy重载了(⼏乎)?所有Python

# 内建魔法⽅法,让所有的关于他⾃⼰的operations都指向到了_get_current_object()

# 所返回的对象,也就是真正的被代理对象。

def __getattr__(self, name):

if name == '__members__':

return dir(self._get_current_object())

return getattr(self._get_current_object(), name)

def __setitem__(self, key, value):

self._get_current_object()[key] = value

request和session⼀般的使⽤如:('args_name')、('args_name') 或者session['name']=value。

这⾥分析⼀个简单的例⼦:page = ('page', 1, type=int)

这⾥会先调⽤request的__getattr__⽅法,返回getattr(self._get_current_object(),'args')获取真正的代理对象,即_request_ctx_stack栈顶的

RequestContext元素,再在它⾝上得到request,再获取args。

class Flask(_PackageBoundObject):

#中间省略⼀些代码

def wsgi_app(self, environ, start_response):

ctx = t_context(environ)

()

error = None

try:

try:

from import EnvironBuilder