Flask

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进行区分

 

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值