1、第一个Flask程序
from flask import Flask
from flask import render_template,request,redirect
app = Flask(__name__) # 实例化Flask对象
@app.route('/test') # 将路径和函数的对应关系添加到路由中
def test():
return render_template('test.html')
if __name__ == '__main__':
app.run(debug=True) # 开启debug模式,监听用户的请求,有请求到来执行__call__方法(类加括号相当于执行__call__方法)
app.debug = True # 自动重启
2、模板的渲染和跳转
render_template('test.html') # 模板渲染,可以传递参数
render_template("test.html",**{"msg":"error"}) # 渲染模板参数加 "**"
redirect("http://www.baidu.com") # 跳转,直接指明跳转的路径
3、请求方式和起别名
@app.route('/login/<int: id>',methods=["GET","POST"],endpoint="name")
# methods 定义视图函数可执行请求的方式
# endpoint 对路径进行取别名,和Django的 name属性别名一样,需要配合url_for使用。url_for(别名).
当需要传参数时直接写nid=1。
# <int: id> 表示url上传递的参数,变量名称是id,并要求是int型。当然也可以是其他类型float、path。
同时需要在函数的加上该形参,但是不支持正则表达式
@app.route装饰器实际上在底层调用add_url_rule()方法
其他的参数:
app.add_url_rule("/register","n2",register,methods=["GET","POST"],
defaults={"nid":"6666"},strict_slashes=False,redirect_to="/pp")
# defaults 默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数
# strict_slashes 对URL最后的 / 符号是否严格要求
@app.route('/index',strict_slashes=False),访问 http://www.xx.com/index/ 或 http://www.xx.com/index均可.
@app.route('/index',strict_slashes=True)仅访问 http://www.xx.com/index.
# redirect_to 重定向到指定地址
# subdomain=None 子域名访问 如果subdomain=admin此时在访问就需要 admin/域名
如果想用正则匹配也是可以的,不过Flask提供的足够使用。
from werkzeug.routing import BaseConverter
class Regex(BaseConverter): # 继承这个类
def __init__(self, map, regex):
super().__init__(map)
self.regex = regex
def to_python(self, value): # 路由匹配成功时调用此函数
return int(value) # 在这里对匹配到的值进行加工
def to_url(self, value): # 反向路由匹配成功时调用此函数
return super().to_url(value)
app.url_map.converters["regex"] = Regex
@app.route("/tree/<regex(r'\d') :nid>")
4、请求和响应
request.form # 获取请求体中所有的数据
request.form.get("") # 获取请求中的参数值
from flask import make_response
# 请求相关信息
request.method # 请求方式
request.args # 接收get请求的参数
request.form # 接收post请求的参数
request.values # 接收所有的参数
request.cookies # 请求的cookie
request.headers # 请求头
request.path # 请求的路径
request.full_path # 请求的全路径会带上get请求的参数
request.url # 请求的url (加上http的格式)
request.base_url # 请求的
request.url_root # 请求的根路径
request.host_url # 请求主机的url
request.host # 请求的主机
request.files # 接受文件
# 还可以对接收的文件直接进行保存
obj = request.files['the_file_name']
obj.save('/var/www/uploads/' + secure_filename(obj.filename))
# 响应相关信息
return "字符串" # 返回一个字符串
return render_template('html模板路径',**{}) # 渲染模板
return redirect('/index.html') # 跳转到其他路径
return jsonify({k:v}) # 返回json字符串
# 设置响应头
response = make_response(render_template('index.html'))
# response是flask.wrappers.Response类型
# response.delete_cookie('key') # 删除cookie
# response.set_cookie('key', 'value') 设置cookie
# response.headers['X-Something'] = 'A value' 设置请求头
return response
5、jinjia2一些语法上的不同
{% for k,item in user.items() %} # 前台页面进行遍历需要加括号
{{ item["name"] }} # 三种方法进行取值,这里和Django不同
{{ item.name }}
{{ item.get("name") }}
{% endfor %}
6、session
session["name"] = "123" # 设置session的值
session.get("name") # 获取session中的值
7、关于配置文件的写法
方式一: 直接在app.py中写,但是这样配置信息多了以后就不方便
app.debug = True
方式二: 通过写settings文件,在app.py中在引入, 但是这样不方便后面的调试
app.config.from_pyfile("settings.py")
方式三: 通过settings文件中写类的形式引入, 这样只需引入不同的类就可到达不同的调试环境
app.config.form_object(settings.developConfig)
---------------------------------------------------------
settings.py
class Config(object):
DEBUG = False
TESTING = False
class ProductionConfig(Config):
pass
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
---------------------------------------------------------
PS:settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录
8、路由系统
Flask的路由系统是由装饰器实现的,究其本质由add_url_rule 实现的。
def register():
return "注册"
app.add_url_rule("/register","N2",register,method=["GET","POST"]) # 这样也可以添加一个路由关系,装饰器的本质是调用add_url_rule方法
如果不传endpoint(别名),Flask内部会自动将endpoint命名为 view_func.__name__(函数名)
9、CBV模式
class Register(views.MethodView):
method = ["GET","POST"]
decorator = [auth,] # 在这里定义装饰器
def get(self):
return "注册"
def post(self):
return ""
app.add_url_rule("/register",view_func=Register.as_view(name="re")) # 这里的name就是别名。
# 和Django一样调用as_view方法
当然也可以继承其他类。
class Register(views.View): # 继承这个类需要继承dispatcher_request()方法,自己做分发
def dispatch_request(self, *args, **kwargs):
return "get"
app.add_url_rule("/register",view_func=Register.as_view(name="re"))
10、Flask不仅能传参数到前台页面,还可以传函数
前台页面:
{{ ff("123")|safe }} # 加上safe防止xss攻击
后台页面:
def proj(arg):
return "<input type='text' value="+arg+">"
@app.route("/pp")
def pp():
return render_template("test.html",ff=proj)
在后台也可以做好防止xss攻击: 这里的Markup和Django的make_safe的作用一样
from flask import Masrkup
def proj(arg):
return Markup("<input type='text' value="+arg+">")
更高级的还有宏定义:
{% macro ff(value)%} # 在这里定义一个函数
<input type="text" value={{ value }}> # 用来显示的HTML
{% endmacro %}
{{ ff("999") }} # 在这里调用
11、请求扩展
@app.before_request # 请求之前的动作,视图函数执行之前
def process_request():
print("进来了")
return None
# return "栏截" 当请求来不符合条件就会有返回值(进行拦截),返回值为none就代表放行
@app.route("/index")
def index():
print("index")
return render_template("test.html")
@app.after_request # 请求之后的动作,视图函数执行之后
def process_after(response):
print("走了")
return response # 必须返回response
当有多个before_request 和 after_request 方法时的执行流程
@app.before_request
def process_request1():
print("进来了1")
return None
@app.before_request
def process_request2():
print("进来了2")
return None
@app.route("/index")
def index():
print("index")
return render_template("test.html")
@app.after_request
def process_after1(response):
print("走了1")
return response
@app.after_request
def process_after2(response):
print("走了2")
return response
执行顺序:
>>> 进来了1
>>> 进来了2
>>> index
>>> 走了2
>>> 走了1
※:当有一个before_request方法有返回值时,后面的before_request 方法都不会执行 而 after_requets方法会全部执行,可以有多个函数。
@app.before_first_request # 有返回值也不会进行栏截
def first():
return "只在第一次请求前生效"
12、flash方法
app.secret_key = "23333" # 使用flash方法必须定义secret_key,因为这是基于session做的
@app.route("/get")
def get():
# get_flashed_messages() 从某个地方获取所有值,并清除
print(get_flashed_messages("wo")) # 当有参数时,按照分类去取
return "Get"
@app.route("/set")
def set():
# flash() 向某个地方设置值
flash("超市",category="wo") # category 分类的意思
return "Set"
# session基于用户隔离数据
13、错误页面的展示
@app.errorhandler(404) # 定制错误信息,需要传错误代码
def error(*arg): # 需要一个参数
print(arg)
return "404错误"
14、自定义标签和过滤器
@app.template_global() # 自定义模板
def sb():
return "傻逼韩宇乾"
@app.template_filter() # 自定义过滤器
def adds(a1,a2):
return a1+a2
调用方式:
{{ sb() }}
{{ 1|adds (2) }}
15、数据库连接池 (DBUtils 模块使用)
此连接池有两种连接模式:
- 模式一:为每个线程创建一个连接,线程即使调用了close方法,也不会关闭,只是把连接重新放到连接池,供自己线程再次使用。当线程终止时,连接自动关闭。用到了threading.local()方法。
-
import pymysql from DBUtils.PersistentDB import PersistentDB POOL = PersistentDB( creator=pymysql, # 使用链接数据库的模块 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always closeable=False, # 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接) threadlocal=None, # 本线程独享值得对象,用于保存链接对象,如果链接对象被重置 host='127.0.0.1', port=3306, user='root', password='', database='', charset='utf8' ) def func(): conn = POOL.connection(shareable=False) # 是否可以共享 cursor = conn.cursor() cursor.execute('select * from lab') result = cursor.fetchall() print(result) cursor.close() conn.close() func()
- 模式二:创建一批连接到连接池,供所有线程共享使用。一般采用模式二。
PS:由于pymysql、MySQLdb等threadsafety值为1,所以该模式连接池中的线程会被所有线程共享。 -
import pymysql from DBUtils.PooledDB import PooledDB, SharedDBConnection POOL = PooledDB( creator=pymysql, # 使用链接数据库的模块 maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数 mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建 maxcached=5, # 链接池中最多闲置的链接,0和None不限制 maxshared=3, # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。 blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # 一般用4或7,ping为0 表示并不保证连接一定可用 # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always host='127.0.0.1', port=3306, user='root', password='', database='', charset='utf8' ) def func(): # 检测当前正在运行连接数的是否小于最大链接数,如果不小于则:等待或报raise TooManyConnections异常 # 否则 # 则优先去初始化时创建的链接中获取链接 SteadyDBConnection。 # 然后将SteadyDBConnection对象封装到PooledDedicatedDBConnection中并返回。 # 如果最开始创建的链接没有链接,则去创建一个SteadyDBConnection对象,再封装到PooledDedicatedDBConnection中并返回。 # 一旦关闭链接后,连接就返回到连接池让后续线程继续使用。 conn = POOL.connection() # print(th, '链接被拿走了', conn1._con) # print(th, '池子里目前有', pool._idle_cache, '\r\n') cursor = conn.cursor() cursor.execute('select * from lab') result = cursor.fetchall() print(result) conn.close() func()
实际使用的方式分为两种:
-
1、通过@staticment 装饰器:通过加静态属性装饰器
-
class DBhandler: @staticmethod def fetch_one(sql,args): # 动态传值 conn = POOL.connection() cursor = conn.cursor() cursor.execute(sql,args) result = cursor.fetchall() cursor.close() conn.close() return result
- 2、通过初始化对象
-
class DBhandler: def __init__(self): # 初始化时就去创建连接 self.conn = POOL.connection() self.cursor = self.conn.cursor() def fetch_one(self, sql, args): self.cursor.execute(sql, args) result = self.cursor.fetchall() return result def close(self): # 关闭游标的方法 self.cursor.close() self.conn.close()
16、请求上下文、应用上下文
- Flask启动时先执行app.run()方法,app.run()方法底层调用 run_simple(host, port, self, **options),此时的self是对象app,此时执行app(),调用了app.__call__()方法
- app.__call__()方法,__call__方法的返回 self.wsgi_app(environ, start_response),environ放了所有和请求相关的数据
- wsgi_app 方法
1. 在这里进行了请求初始化 ctx 封装了请求相关的数据 此时就可以用ctx对象获取请求相关的内容 ctx = self.request_context(environ) 2. 请求进行ctx的上下文操作 ctx.push() error = None try: try: 3. 找到视图函数并执行 response = self.full_dispatch_request() except Exception as e: error = e 4. 错误处理 response = self.handle_exception(e) except: error = sys.exc_info()[1] raise return response(environ, start_response) finally: if self.should_ignore_error(error): error = None 5. 将请求清除 ctx.auto_pop(error)
在这里一共分为了5步,我们先来说第一步:
-
第一步的self.request_context(),初始化了一个类
def request_context(self, environ): return RequestContext(self, environ) # 返回一个类 ------------------------------------------------------------------- class RequestContext(object): def __init__(self, app, environ, request=None): self.app = app if request is None: # 第一次进来request必定为空 request = app.request_class(environ) # 在这里进行初始化了Request类 self.request = request self.url_adapter = app.create_url_adapter(self.request) self.flashes = None self.session = None ------------------------------------------------------------------- request_class = Request ------------------------------------------------------------------- class Request(RequestBase): url_rule = None view_args = None routing_exception = None _is_old_module = False
第二步:
def push(self): top = _request_ctx_stack.top # 2.1 取请求上下文的栈顶元素 if top is not None and top.preserved: top.pop(top._preserved_exc) app_ctx = _app_ctx_stack.top # 2.2 取应用请求上下文的栈顶元素 if app_ctx is None or app_ctx.app != self.app: app_ctx = self.app.app_context() # 2.3 初始化一个应用上下文,此时app_ctx是Appcontext对象。 app_ctx.push() # 2.4 将应用上下文的信息压栈 self._implicit_app_ctx_stack.append(app_ctx) else: self._implicit_app_ctx_stack.append(None) if hasattr(sys, 'exc_clear'): sys.exc_clear() _request_ctx_stack.push(self) # 2.5 将请求上下文的信息压栈 # 2.6 session相关的请求 self.session = self.app.open_session(self.request) if self.session is None: self.session = self.app.make_null_session()
2.1 先进行初始化 _request_ctx_stack
_request_ctx_stack = LocalStack() # 初始化了LocalStack()类 ------------------------------------------------------------------ class LocalStack(object): def __init__(self): self._local = Local() # 在LocalStack类中又初始化了Local()类 ------------------------------------------------------------------ class Local(object): # local()类用来保存所有数据 # 之所以以这种方式通过调用父类进行初始化,为了不触发类内部的__setattr___()方法 def __init__(self): object.__setattr__(self, '__storage__', {}) # 在这里初始化了一个字典 object.__setattr__(self, '__ident_func__', get_ident) # 初始化了一个线程的唯一标识,用于区分各个请求
现在我们再回来,通过列表的 -1 的特性取栈顶元素
-
@property def top(self): try: return self._local.stack[-1] # 取栈顶元素 except (AttributeError, IndexError): return None
2.2 先进行初始化_app_ctx_stack
_app_ctx_stack = LocalStack() # 也初始化了一个LocalStack()类 ------------------------------------------------------------- class LocalStack(object): def __init__(self): self._local = Local() # 初始化了一个Local()类, Local类是实际存放数据的地方 --------------------------------------------------------------- class Local(object): # 做了和上面一样的事情 def __init__(self): object.__setattr__(self, '__storage__', {}) object.__setattr__(self, '__ident_func__', get_ident)
现在再回来,通过列表的 -1 的特性取栈顶元素
@property def top(self): try: return self._local.stack[-1] # 取栈顶元素 except (AttributeError, IndexError): return None
2.3 app.app_context()方法
def app_context(self): return AppContext(self) # 初始化了一个AppContext类,self 是app ------------------------------------------------------------ class AppContext(object): def __init__(self, app): self.app = app self.url_adapter = app.create_url_adapter(None) self.g = app.app_ctx_globals_class() # 这里相当于一个全局变量 self._refcnt = 0
※:先来说一下 self.g :相当于一个空字典,请求到来会创建g,请求走的时候会销毁g, 每个请求周期都会创建一个用于在请求周期中传递至的一个容器,在一个请求中进行传值时用g,而不用request。
-
2.4 app_ctx.push()
class AppContext(object): def push(self): self._refcnt += 1 if hasattr(sys, 'exc_clear'): sys.exc_clear() _app_ctx_stack.push(self) # 调用了LocalStack类的push()方法 appcontext_pushed.send(self.app) # 这是一个信号 ----------------------------------------------------------------- class LocalStack(object): def push(self, obj): rv = getattr(self._local, 'stack', None) # 第一次肯定取不到 if rv is None: self._local.stack = rv = [] # 在这里进行进行赋值,就会触发Local()类的__setattr__()方法 rv.append(obj) # 将AppendContext加入 return rv ------------------------------------------------------------------ class Local(object): def __setattr__(self, name, value): ident = self.__ident_func__() # 获取唯一标识 storage = self.__storage__ # 获取存放的字典 try: storage[ident][name] = value # 将ctx放进去 {唯一标识:{"stack":[app_ctx]}} except KeyError: storage[ident] = {name: value}
2.5 _request_ctx_stack.push(self) self是RequestContext
class LocalStack(object): def push(self, obj): rv = getattr(self._local, 'stack', None) # 这里同上 if rv is None: self._local.stack = rv = [] # 同上 rv.append(obj) # RequestContext 对象 return rv --------------------------------------------------------------- class Local(object): def __setattr__(self, name, value): ident = self.__ident_func__() # 获取唯一标识 storage = self.__storage__ # 获取存放数据的字典 try: storage[ident][name] = value # 进行赋值 {唯一标识:{"stack":[ctx]}} except KeyError: storage[ident] = {name: value}
2.6 session相关
self.session = self.app.open_session(self.request) # 调用app的open_session # self.session 是在ctx中,而ctx又在Local中 -------------------------------------------------- def open_session(self, request): return self.session_interface.open_session(self, request) -------------------------------------------------- def open_session(self, app, request): s = self.get_signing_serializer(app) # 查看 是否有secret_key if s is None: return None val = request.cookies.get(app.session_cookie_name) # 先去请求的cookie中获取随机字符串 第一次肯定是获取不到的 app.session_cookie_name一个配置文件 获取key为session的值 "session" 是自己配置文件定义的 if not val: # 请求第一次来的时候取不到值 return self.session_class() #返回了一个 类似字典 max_age = total_seconds(app.permanent_session_lifetime) try: data = s.loads(val, max_age=max_age) #loads 作用是: 反序列化+解析乱码 return self.session_class(data) #返回了一个 类似字典对象,对象里面有data except BadSignature: return self.session_class()
第三步 response = self.full_dispatch_request() self是app
def full_dispatch_request(self): self.try_trigger_before_first_request_functions() # 3.1 before_first_request 请求 try: request_started.send(self) # Flask的信号 rv = self.preprocess_request() # 3.2 before_request 请求 if rv is None: rv = self.dispatch_request() # 3.3 执行视图函数 except Exception as e: rv = self.handle_user_exception(e) return self.finalize_request(rv) # 3.4 对请求的最后处理
3.1 before_first_request 的原理
def try_trigger_before_first_request_functions(self): if self._got_first_request: # 这个值初始时为false return with self._before_request_lock: # 在这里加了把锁 if self._got_first_request: return for func in self.before_first_request_funcs: # 所有被before_first_request请求都会加到before_first_request_funcs列表中 func() self._got_first_request = True # 将这个值变为True就实现了第一次的请求执行
3.2 before_request
def preprocess_request(self): funcs = self.before_request_funcs.get(None, ()) # 获得被before_request装饰器修饰的函数 if bp is not None and bp in self.before_request_funcs: funcs = chain(funcs, self.before_request_funcs[bp]) # 在这里和蓝图进行了拼接 for func in funcs: # 进行遍历 rv = func() if rv is not None: return rv
3.4 最后的处理
def finalize_request(self, rv, from_error_handler=False): response = self.make_response(rv) try: response = self.process_response(response) # 3.4.1 对带有after_request装饰器进行执行 request_finished.send(self, response=response) # Flask的信号 except Exception: if not from_error_handler: raise self.logger.exception('Request finalizing failed with an ' 'error while handling an error') return response
3.4.1 的执行
def process_response(self, response): ctx = _request_ctx_stack.top bp = ctx.request.blueprint funcs = ctx._after_request_functions if bp is not None and bp in self.after_request_funcs: funcs = chain(funcs, reversed(self.after_request_funcs[bp])) if None in self.after_request_funcs: funcs = chain(funcs, reversed(self.after_request_funcs[None])) for handler in funcs: response = handler(response) if not self.session_interface.is_null_session(ctx.session): # 3.4.1.1 self.save_session(ctx.session, response) # 3.4.1.2 return response
3.4.1.1
def is_null_session(self, obj): #判断ctx.session 是不是 self.null_session_class = NullSession 类或者它派生类的对象 return isinstance(obj, self.null_session_class)
3.4.1.2
def save_session(self, session, response): return self.session_interface.save_session(self, session, response) ------------------------------------------------------------------- class SecureCookieSessionInterface(SessionInterface): def save_session(self, app, session, response): if not session: if session.modified: # 只有删除session使session为空才会执行 response.delete_cookie(app.session_cookie_name, domain=domain, path=path) return # 先将特殊的session字典进行转换成字典,然后加密序列化 val = self.get_signing_serializer(app).dumps(dict(session)) #给响应设置cookie,此时发现是将session写到cookie中了 response.set_cookie(app.session_cookie_name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure)
第四步: 错误处理
-
def handle_exception(self, e): exc_type, exc_value, tb = sys.exc_info() got_request_exception.send(self, exception=e) # Flask的信号 handler = self._find_error_handler(InternalServerError()) if self.propagate_exceptions: if exc_value is e: reraise(exc_type, exc_value, tb) else: raise e self.log_exception((exc_type, exc_value, tb)) if handler is None: return InternalServerError() return self.finalize_request(handler(e), from_error_handler=True) # 错误信息显示在页面上
第五步: 将请求删除
class RequestContext(object): def pop(self, exc=_sentinel): app_ctx = self._implicit_app_ctx_stack.pop() try: clear_request = False if not self._implicit_app_ctx_stack: self.preserved = False self._preserved_exc = None if exc is _sentinel: exc = sys.exc_info()[1] self.app.do_teardown_request(exc) if hasattr(sys, 'exc_clear'): sys.exc_clear() request_close = getattr(self.request, 'close', None) if request_close is not None: request_close() clear_request = True finally: rv = _request_ctx_stack.pop() # 5.1 将其从栈中pop出,调用的LocalStack的pop方法 if app_ctx is not None: app_ctx.pop(exc) # 5.2
5.1 执行 app.do_teardown_request方法
class Flask(_PackageBoundObject): def do_teardown_request(self, exc=_sentinel): # 信号 - 请求执行完毕后自动执行(无论成功与否) request_tearing_down.send(self, exc=exc)
5.2
class AppContext(object): def pop(self, exc=_sentinel): try: if self._refcnt <= 0: #5.2.1 self.app.do_teardown_appcontext(exc) # 信号 - 请求上下文pop时执行 appcontext_popped.send(self.app) ----------------------------------------------------------- class Flask(_PackageBoundObject): def do_teardown_appcontext(self, exc=_sentinel): # 信号 - 请求上下文执行完毕后自动执行(无论成功与否) appcontext_tearing_down.send(self, exc=exc)
至此,Flask的请求流程完毕!!!!撒花、撒花、撒花、*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。!
-
当我们执行视图函数时是如何调用的这些东西呢?
-
我们以request为例,说明一下流程,首先我们的视图函数这样写
@app.route("/index") def index(): print(request) request.method return ""
当我们打印request对象时,会调用其__str__()方法
# request是 LocalProxy类的一个对象,所以会调用这个类的__str__()方法 # 偏函数作为参数传入到LocalProxy类中 request = LocalProxy(partial(_lookup_req_object, 'request')) ------------------------------------------------------------------- class LocalProxy(object): # local是偏函数 def __init__(self, local, name=None): # 防止触发__setattr__(),所以这样赋值 object.__setattr__(self, '_LocalProxy__local', local) # self.__local = local object.__setattr__(self, '__name__', name) # self.__name__ = name if callable(local) and not hasattr(local, '__release_local__'): object.__setattr__(self, '__wrapped__', local) __str__ = lambda x: str(x._get_current_object()) # x是self也就是LocalProxy() -------------------------------------------------------------------- def _get_current_object(self): if not hasattr(self.__local, '__release_local__'): return self.__local() try: return getattr(self.__local, self.__name__) # 在这里执行了偏函数 except AttributeError: raise RuntimeError('no object bound to %s' % self.__name__) -------------------------------------------------------------------- 偏函数: def _lookup_req_object(name): # name是request top = _request_ctx_stack.top # 取栈顶元素 if top is None: raise RuntimeError(_request_ctx_err_msg) return getattr(top, name) # 从请求对象中获取request属性 相当于 return _request_ctx_stack.top.request 实际上是去Request对象去找
当调用request.method方法
class LocalProxy(object): # local是偏函数, name是method def __init__(self, local, name=None): # 防止触发__setattr__(),所以这样赋值 object.__setattr__(self, '_LocalProxy__local', local) # self.__local = local object.__setattr__(self, '__name__', name) # self.__name__ = name if callable(local) and not hasattr(local, '__release_local__'): object.__setattr__(self, '__wrapped__', local) def __getattr__(self, name): if name == '__members__': return dir(self._get_current_object()) return getattr(self._get_current_object(), name) # 相当于 return _request_ctx_stack.top.request.method -------------------------------------------------------------------- def _get_current_object(self): if not hasattr(self.__local, '__release_local__'): return self.__local() try: return getattr(self.__local, self.__name__) # 在这里执行了偏函数 except AttributeError: raise RuntimeError('no object bound to %s' % self.__name__) -------------------------------------------------------------------- 偏函数: def _lookup_req_object(name): # name是request top = _request_ctx_stack.top # 取栈顶元素 if top is None: raise RuntimeError(_request_ctx_err_msg) return getattr(top, name) # 从请求对象中获取request属性 相当于 return _request_ctx_stack.top.request
※:当请求进来时先执行_request_ctx_stack.push() 中间用_request_ctx_stack.top 取值 然后执行 _request_ctx_stack.pop() 进行销毁。
-
session是如何工作的?
-
当请求到来时,会根据随机字符串到"数据库"中找,有就拿相关的值,没有就返回一个存放数据的空容器(字典)
-
在视图函数中,可以操作放在内存中的session对象。
-
响应时,把随机字符串写到用户的session中,将数据保存到"数据库"
-
- 流程分析
当写到如下代码时会触发,Localproxy的__setitem__()方法
session["xxx"] = 123
-----------------------------------------------------------
session = LocalProxy(partial(_lookup_req_object, 'session'))
-----------------------------------------------------------
class LocalProxy(object):
def __setitem__(self, key, value):
# 1. obj = self._get_current_object() 先执行这个函数,返回一个session对象
# 2. 对session进行赋值 由于session是一个字典并不会触发__setitem__()方法
self._get_current_object()[key] = value
18、多应用app
from flask import Flask
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
# 创建两个应用
app01 = Flask("app01")
app02 = Flask("app02")
@app01.route("/index")
def index():
return "index"
@app02.route("/pop")
def pop():
return "pop"
# 当访问app01时:127.0.0.1/index
# 当访问app01时:127.0.0.1/xxx/pop
app = DispatcherMiddleware(app01,{"/xxx":app02}) # 请求分发
if __name__ == '__main__':
run_simple('127.0.0.1',5000,app) # app.run() 底层调用的是run_simple() 直接调用第三个参数+括号
源码:先到DispatcherMiddleware类中然后加括号运行__call__方法
def __init__(self, app, mounts=None):
self.app = app
self.mounts = mounts or {} # 将路由规则赋值给mounts如果没有就是空列表
__call__方法:
def __call__(self, environ, start_response):
script = environ.get('PATH_INFO', '') # 先获取url
path_info = ''
while '/' in script: # 当 / 在url中开始遍历 例如 : /sec/index
if script in self.mounts:
app = self.mounts[script] # 找到匹配的规则
break
script, last_item = script.rsplit('/', 1) # 从第一个 / 的位置进行分割
path_info = '/%s%s' % (last_item, path_info) # srcipt = /sec last_item = /index
else:
app = self.mounts.get(script, self.app)
original_script_name = environ.get('SCRIPT_NAME', '')
environ['SCRIPT_NAME'] = original_script_name + script
environ['PATH_INFO'] = path_info # 在这里进行重新赋值用于路由匹配
return app(environ, start_response) # 调用app.__call__
还有一个测试的脚本
from flask import Flask,_app_ctx_stack
app1 = Flask("001")
app2 = Flask("002")
# 会触发___enter__、__exit__ 方法
with app1.app_context():
print( _app_ctx_stack._local.__storage__)
with app2.app_context():
print(_app_ctx_stack._local.__storage__)
很好的解释了flask的local中保存数据时,为什么用列表创建出来的栈,以及为什么用栈而不用对其进行赋值。
---- 如果写web程序,web运行环境:栈中永远保存一条数据(可有不用栈)
---- 写脚本获取app信息时,可能存在app上下文嵌套关系。
19、信号
Flask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为。如果要使用信号,就要安装blinker。
Flask中信号的种类:
template_rendered = _signals.signal('template-rendered') # 模板渲染后执行
before_render_template = _signals.signal('before-render-template') # 模板渲染前执行
request_started = _signals.signal('request-started') # 请求到来前执行
request_finished = _signals.signal('request-finished') # 请求到来之后执行
request_tearing_down = _signals.signal('request-tearing-down') # 请求执行完毕后自动执行(无论成功与否)
got_request_exception = _signals.signal('got-request-exception') # 请求执行出现异常时执行
appcontext_tearing_down = _signals.signal('appcontext-tearing-down') # 应用上下文执行完毕后自动执行(无论成功与否)
appcontext_pushed = _signals.signal('appcontext-pushed') # ctx.push() 应用上下文push执行
appcontext_popped = _signals.signal('appcontext-popped') # ctx.autopop() 应用上下文pop执行
message_flashed = _signals.signal('message-flashed') # 闪现时执行
正确使用信号的姿势: signals+信号的种类+connect(函数名)
from flask import signals
def send_signal(*args): # 这里有一个参数
print(args)
print("我是信号")
signals.request_started.connect(send_signal)
信号有返回值也不会拦截。
执行的顺序:
1. before_first_request 装饰器
2. request_start 请求前信号
3. before_request 装饰器
4.before_render_template 渲染前信号
5.template_rendered 渲染后信号
6.after_request 装饰器
7.session.save()
8.request_finished 请求后信号
如果上述步骤出错则会触发 got_request_exception 信号
最后还要补充的:
这些对象都是localproxy对象:
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'))
当请求终止时这些对象都会pop掉
如何体现多线程的?
各个线程都有自己的唯一id进行区分