首页 > 技术文章 > flask框架基础知识2

jiangxianseng 2020-03-20 01:28 原文

1.flask之请求扩展(类似于django的中间件)

#flask里面的请求扩展相当于django中的中间件
from  flask import Flask,request

app = Flask(__name__)


# 执行响应函数之前执行此下面的方法,相当于django中的process_request,这里面不需要参数是因为导入了request,设置为全局参数,若有return则之前返回,后面的before_request一律不执行,包括响应函数也不执行,只会执行after_request
@app.before_request
def before1():
   print("我是befor1")
   return "ok"

#若同时有多个before_request,则谁先注册谁先执行
@app.before_request
def before2():
   print("我是befor2")


@app.after_request
def after1(response):
   print("响应后的参数",response)
   print("我是after1")
   return response

@app.after_request
def after2(response):
print (response)
   # <Response 2 bytes [200 OK]>
   print("我是after2")
   return response

@app.before_first_request
def first():
   print("我是第一次")

@app.route("/")
def index():
   print("我是响应函数")
   return "ok"

if __name__ == '__main__':
   
   
app.__call__
   app.run()
   #1 执行run本质就是执行这句run_simple(host, port, self, **options)
   #2 这是基于wsgi协议的,每次向self传两个参数(environ, start_response)(请求相关,一个响应相关)
   #3 这个self就是上面获得的app对象
   #4 即app.run()就是app对象加括号执行(请求相关,一个响应相关)
   #5 对象加括号执行,就是执行__call__
   # 6 看app的__call__方法
   #7 app里面的wsgi_app
   # 8 response = self.full_dispatch_request()

分析源码

    #1.点开上面的__call__方法即可看到下面这段代码,发现是执行了wsgi_app方法
   def __call__(self, environ, start_response):
       """The WSGI server calls the Flask application object as the
      WSGI application. This calls :meth:`wsgi_app` which can be
      wrapped to applying middleware."""
       return self.wsgi_app(environ, start_response)

   #2.再点开wsgi_app方法
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)
           
    #3.点开full_dispatch_request()方法
    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
      """
       # 执行请求扩展中的before_first_request方法
       self.try_trigger_before_first_request_functions()
       try:
           request_started.send(self)
           rv = self.preprocess_request()
           # 由下面的4.2可以得到若返回值是None,则继续执行响应函数
           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只执行一次
    #4.1点开3中的try_trigger_before_first_request_functions()方法
    def try_trigger_before_first_request_functions(self):
       """Called before each request and will ensure that it triggers
      the :attr:`before_first_request_funcs` and only exactly once per
      application instance (which means process usually).

      :internal:
      """
       # 若first_request为True则直接返回,若不是则下面for循环并设为True
       if self._got_first_request:
           return
       with self._before_request_lock:
           if self._got_first_request:
               return
           for func in self.before_first_request_funcs:
               func()
           self._got_first_request = True
           
           
   # 源码分析为何before_request有返回值其他响应就不执行    
   #4.2. 点开3中的preprocess_request()方法
     def preprocess_request(self):
       """Called before the request is dispatched. Calls
      :attr:`url_value_preprocessors` registered with the app and the
      current blueprint (if any). Then calls :attr:`before_request_funcs`
      registered with the app and the blueprint.

      If any :meth:`before_request` handler returns a non-None value, the
      value is handled as if it was the return value from the view, and
      further request handling is stopped.
      """

       bp = _request_ctx_stack.top.request.blueprint
# 获取响应函数执行前的所有函数
       funcs = self.url_value_preprocessors.get(None, ())
       if bp is not None and bp in self.url_value_preprocessors:
           funcs = chain(funcs, self.url_value_preprocessors[bp])
       for func in funcs:
           func(request.endpoint, request.view_args)

       funcs = self.before_request_funcs.get(None, ())
       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
     
   # 源码分析为何after_request执行顺序不同
     # 4.3 点击finalize_request(rv)方法,再点击process_response(response)方法,
     def process_response(self, response):
       """Can be overridden in order to modify the response object
      before it's sent to the WSGI server. By default this will
      call all the :meth:`after_request` decorated functions.

      .. versionchanged:: 0.5
          As of Flask 0.5 the functions registered for after request
          execution are called in reverse order of registration.

      :param response: a :attr:`response_class` object.
      :return: a new response object or the same, has to be an
                instance of :attr:`response_class`.
      """
       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]))
       # 将after_request方法进行反转
       if None in self.after_request_funcs:
           funcs = chain(funcs, reversed(self.after_request_funcs[None]))
       for handler in funcs:
           response = handler(response)
       # 还有一个添加session操作
       if not self.session_interface.is_null_session(ctx.session):
           self.session_interface.save_session(self, ctx.session, response)
       return response

总结(执行顺序按下面的编号)

1 before_first_request 当项目启动后,接收到的第一个请求,就会执行before_first_request装饰的函数,执行顺序也是谁先注册就谁先执行,即使有response也不会影响下面的befor_request和响应函数,注意只有项目启动第一次请求才会执行,除非项目重启,后面都不会在执行此函数

2 before_request 请求没有经过响应函数的时候,会执行before_request装饰的函数,谁先注册谁先执行。只要有一个函数有返回值,后面的所有before_request都不会执行,且响应函数也不会执行。其有没有返回值都不会影响after_request的执行

3 after_request是再before_request与响应函数执行后执行。他必须接收响应参数,并且要把响应返回。执行顺序是谁先注册后执行。

2.请求扩展之错误处理以及标签与过滤器

from  flask import Flask,request,render_template

app = Flask(__name__)


# 只记录打印不能返回给前台
@app.teardown_request
def tear(e):
    print(e)
    print("我是teardown_request")


# 可记录并返回给前台,但必须指明是那种类型错误(即响应吗)
@app.errorhandler(500)
def error_500(e):
   print(e)
   return "我程序崩了,你下次来把"

@app.errorhandler(404)
def error_404(e):
   print(e)
   return render_template("404.html")

#相当于django中的标签。可以在页面中展示,无需之前那样return render_template("index.html",t1 =get_sb)
@app.template_global()
def get_sb(a1,a2):
   return a1 + a2

#再html中的用法{{get_sb(1,2)}}
# 注意页面中使用的名字和函数名应该一致


#django中的过滤器,第一个参数是你要过滤的那个值(参数数量不限)
@app.template_filter()
def get_something(a1,a2,a3,a4):
   return a1+a2+a3+a4

#在html中{{1|get_something(4,2,3)}}


@app.route("/")
def index():

   print("我是响应函数")
   return render_template("index.html")


if __name__ == '__main__':

   app.run()

总结

1 teardown_request,一旦遇到错误就会执行,并且把错误传递给teardown_request装饰的函数, 没有错误也会执行,但是是错误为None,他并不能处理错误,只能记录,并且不能返回给前台用户
2 errorhandle 可以捕获错误,并且对错误做出响应,返回给用户,如果你要用errorhandler你必须指定他捕获哪种类型的错误,就必须传错误码,然后就返回值,可以返回给前台用户

中间件(了解的知识点)

##了解的知识点

from flask import Flask

app = Flask(__name__)app.wsgi_app
# 重写源码中执行的MyMiddleware类,old_wsgi_app接收的是原来的参数app.wsgi_app
class MyMiddleware:
   def __init__(self,old_wsgi_app):
       self.old_wsgi_app =old_wsgi_app
   # 这样在执行类()产生对象并加括号执行,就会执行__call__方法,就实现了重新设计源码的方法
   def __call__(self, environ, start_response):
       #这befor的befor
       print("开始之前")
       ret = self.old_wsgi_app(environ, start_response)
       #这是after的after
       print("结束之后")
       return ret

@app.route("/")
def index():
   return "ok"


if __name__ == '__main__':
   # 源码中执行run时就是执行__call__方法,本质上就是执行这一句中的MyMiddleware,中间件就是在这一句的前后添加,再不改变源码的基础上修改源码的方法,如上面所写
   app.wsgi_app = MyMiddleware(app.wsgi_app)
   app.run()

3.cbv写法

基础版

from flask import Flask,views,url_for

app = Flask(__name__)

# 自定义装饰器
def tt(func):
  def inner(*args,**kwargs):
      print("你追到我。。")
      rv = func(*args,**kwargs)
      print("嘿嘿嘿。。。")
      return  rv
  return inner

class Index(views.View):
   methods = ["GET"] #规定哪些请求方式可以请求我这个路由
   decorators =[tt,]   #这个是给我们的响应添加装饰器
   # 注意,继承了views.View,必须用dispatch_request这个名字,源码就是这么要求的
   def dispatch_request(self):
       return "ojbk"

app.add_url_rule("/index",view_func=Index.as_view(name="index"),endpoint="index1")

# 为什么要给as_view传递name= "index",
#1 as_view的语法就要你传,
#2 他作用Index.as_view(name="index")他返回的是view这个函数对象,我们传递name="index"是给view的__name__改变名字。如果不传,我没有办法通过名字来找路由的映射关系,因为默认名字都是”view“
# 其实as_view可以看做是一个名为view的函数,若没有name重新命名,则路由就全部都一样了

# 若传递了endpoint,起别名依然有效

常用版

from flask import Flask,views,url_for

app = Flask(__name__)
def tt(func):
  def inner(*args,**kwargs):
      print("你追到我。。")
      rv = func(*args,**kwargs)
      print("嘿嘿嘿。。。")
      return  rv
  return inner

class Login(views.MethodView):
   methods = ["GET","POST"]  # 规定哪些请求方式可以请求我这个路由
   #decorators = [tt, ] # 这个是给 我们的响应添加装饰器
   def get(self):
       print(url_for("index1"))
       # 上面传的endpoint起的别名可以正常跳转
       return "get"
   def post(self):
       return "post"

app.add_url_rule("/login",view_func=Login.as_view(name="login"))
#实现方法是重写了dispatch_request,通过请求方法,来找到当前类中的函数。

if __name__ == '__main__':
   app.run()

4.蓝图

flask框架模拟django中的目录

 

 

#1. _init.py
from flask import Flask

app = Flask(__name__)


# 注册一下,让user先执行一遍,否则无法得到user里面的数据(比如路由和响应函数)
from app01 import user


#2. user.py
from app01 import app

@app.route('/user')
def user():
   return "user"


# 3.manage.py
from app01 import app

if __name__ == '__main__':
   app.run()
 

但是上面这种写法会出现循环导入的问题,可以通过导入Blueprint来解决

#1. _init.py
from flask import Flask

app = Flask(__name__)


# 注册一下,让user先执行一遍,否则无法得到user里面的数据(比如路由和响应函数)
from app01 import user

# 同样注册一下
app.register_blueprint(user.us)



#2. user.py
# from app01 import app
form flask import Blueprint


# 括号里面的user指的是起的别名,后面写路由若有重复路由可以通过别名来实现准确的路由跳转,比如(url_for('user.index')就是跳到user路由下的index路由
us = Blueprint("user",__name__)

# @app.route('/user')
@us.route('/user')
def user():
   return "user"


# 3.manage.py
from app01 import app

if __name__ == '__main__':
   app.run()
 

简单应用目录示例如下图所示,并且执行请求扩展顺序为先全局,再局部

 

 

大型应用目录示例如下图所示

 

 

 

 

 

 

推荐阅读