4. 控制逻辑单元/视图

这一部分是MVC中的控制部分,但在Django中此部分的功能称为视图,所以,就 Flask而言,此部分成为啥不我也不知道,这个不重要,重要的是,我们要清楚,这部分 的核心功能是逻辑控制。

4.1. 返回JSON

现在后端经常需要返回数据而不是页面,我们在Flask中一般需要使用jsonify直接生成 JSON响应。

from flask import jsonify
... ...
@app.route('/json_return')
def json_return():
    j = {
        "name":"北京图灵学院",
        "teacher":"刘大拿",
        "Blog_address":"http://www.mycode.wang"
    }
    return  jsonify(j)

实际我们是用jsonify构造了一个json返回,但参数使用的是一个python的字典。

这里我们用的是jsonify,不推荐直接受用json.dumps转成json字符串直接返回,因为返回的 数据如果按照HTTP协议规范,需要指定返回content-type值。

jsonify的源代码如下,做了一些删减:

def jsonify(*args, **kwargs):
    """This function wraps :func:`dumps` to add a few enhancements that make
    life easier.  It turns the JSON output into a :class:`~flask.Response`
    object with the :mimetype:`application/json` mimetype.  For convenience, it
    also converts multiple arguments into an array or multiple keyword arguments
    into a dict. 
    
    ## 上面说明了jsonify的功能,包括自动设置mimetype值等。
    
    indent = None
    separators = (",", ":")

    if current_app.config["JSONIFY_PRETTYPRINT_REGULAR"] or current_app.debug:
        indent = 2
        separators = (", ", ": ")

    if args and kwargs:
        raise TypeError("jsonify() behavior undefined when passed both args and kwargs")
    elif len(args) == 1:  # single args are passed directly to dumps()
        data = args[0]
    else:
        data = args or kwargs

    # 其实核心功能也就这么多
    # 把传入的数据写入返回内容后设置minetype
    return current_app.response_class(
        dumps(data, indent=indent, separators=separators) + "\n",
        mimetype=current_app.config["JSONIFY_MIMETYPE"],
    )

4.2. 重定向

重定向的概念就是当你访问的资源或者地址由于某些原因不在了,但给你一个新的地址,根据 访问规则,你会自动的访问它给你的新的地址。

就好比多年以后你发达了,去拜访你的初恋准备炫耀一下,结果敲开门是她前夫,前夫很客气 的告诉你你要找的初恋已经搬去了隔壁老王家,然后你就又奔向了隔壁老王家,敲开了老王的门, 然后… …

下面的代码展示了重定向,你访问的URL是/chulian, 但是执行结果是用重定向 给你了一个新的URL是http://www.mycode.wang, 于是你看到的是重定向后的 麦扣网的主页:

@app.route('/chulian')
def chulian_redirect():
    # 如果你访问本函数,则返回用重定向功能
    # 指向了博客网站
    return  redirect("http://www.mycode.wang")

有时候需要重定向到我们自己的URL,但如果写出URL来会导致硬编码,这样一般不太 professional,所以,为了提高程序的B格,我们需要用一个高大上的东西,于是 url_for就这样诞生了。 url_for让我们提供一个URL的名称即可,如果以后url的 值改变了,我们照样可以使用,不需要修改名称。

下面案例访问URL chulian_json的时候,逻辑处理函数重定向到了json_return处理 函数所对应的URL,当然最后的结果也相当于调用了json_return 一遍:

@app.route('/chulian_json')
def chulian_json():
    return  redirect(url_for("json_return"))

结果很好理解,需要注意的是,实际执行结果并不是直接调用了函数json_return, 而是 通过访问json_return所对应的URL后,由路由调用的json_return函数。这一点从程序 的控制台中显示的访问记录可以看出来:

重定向

4.3. 自定义状态码

Flask编程可以随时自定义自己的HTTP返回状态码,这个状态码基本上可以随心所以的定义,哈哈哈

以下代码返回一个888的状态码,熟悉HTTP协议的同学们应该这点,这个状态码太屌了~~~

@app.route('/statuscode888')
def statuscode_888():
    c = 888
    return  "你要的状态,码, {}".format(c), c

4.4. 异常捕获

Flask为异常处理提供了一些方便的功能。

4.4.1. 抛出异常

如果是正常的异常,HTTP协议的错误状态范畴内,可以通过abort函数抛出, abort 的参数可以是HTTP的错误状态码。

请参见下面代码示例。

4.4.2. 捕获异常

Flask可以通过errorhandler 装饰器来捕获抛出的异常,对应的是异常错误 码或者异常类名称。

下面访问URL make_error 后引发505异常,函数get_error捕获异常后把异常内容 返回:

@app.route('/make_error')
def make_error():
    return abort(505, "Made an error by 刘大拿")

@app.errorhandler(505)
def get_error(e):
    return "Error Code: {}, Info: {}".format(e.code, e.description)

4.5. 请求钩子

钩子可以理解成是一个静静的函数,他不需要你手动调用,只有当 发生某些事情的时候(事件), 他会被自动执行,你需要做的只是编写 一个这样的函数就好,调用时机由系统来决定。

有的钩子程序名称不允许自定义,位置也不允许自定义,Flask的 钩子程序由装饰器完成,名称可以自己来写。

Flask支持如下四种常见请求类钩子:

  • before_first_request
    • 在处理第一个请求前执行
  • before_request
    • 在每次请求前执行
    • 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
  • after_request
    • 如果没有抛出错误,在每次请求后执行
    • 接受一个参数:视图函数作出的响应
    • 在此函数中可以对响应值在返回之前做最后一步修改处理
    • 需要将参数中的响应在此参数中进行返回
  • teardown_request:
    • 在每次请求后执行
    • 接受一个参数:错误信息,如果有相关错误抛出

上面四个钩子的调用顺序如下:

1钩子顺序

其他的钩子请参阅相应文件../site-packages/flask/app.py中的代码。

示例程序代码如下:

@app.before_first_request
def a():
    print("第一次请求前被调用")

@app.before_request
def b():
    print("请求前被调用")

@app.after_request
def c(r):
    print("请求后被调用")
    return r

@app.teardown_request
def d(e):
    print("请求后被调用")

连续执行三次后的后台记录如下:

钩子后台

4.6. 获取请求参数

Werkzeug会把用户的请求封装成一个request对象传入,Flask直接使用这个封装好的 对象就可以,所有请求的内容都可以在reqeust对象内找到,常用的属性有:

  • data: 请求的数据
  • form: 记录请求中的表单数据, MultiDict类型
  • args: 记录请求中的查询参数, MultiDict类型
  • cookies: 记录请求中的cookie信息, Dict类型
  • headers: 记录请求中的报文头, EnvironHeaders类型
  • method: 记录请求使用的HTTP方法
  • url: 记录请求的URL地址
  • files: 记录请求上传的文件

具体使用可以参看下面案例:

@app.route('/req')
def req():
    print("request的内容如下:")
    r = {
        'data': request.data.decode(),
        'args': request.args,
        'cookies':request.cookies,
        'method': request.method,
        'url': request.url,
        'files': request.files
    }

    return  jsonify(r)

4.7. 状态保持

有HTTP的无状态性,当外部访问服务器的时候,下次再访问服务器不能记住上次访问 的状态,此时服务器就像一个老年痴呆患者,每次你都要自我介绍还需要从新 开始,这个是很麻烦的,为了弥补HTTP协议的这一问题,通常使用cookie和session来解决。

举个栗子: 我们去医院看病,每次都会带一个病历本,正常医生很难记得祝你是谁,得的什么病, 治疗的办法,用的药物等,为了解决这个问题,每次你看病都要带着你的病历本, 医生会先看你的病历本,以此来知道你的病情进展治疗情况,他好接着给你治病。

有时候一些重要的资料, 让你个人保存也不太方便,比如一些增强ct的照片等,此时 医院会有单独的档案室,你每次见医生需要先去档案室把你的资料提出了,然后再拿着 资料找医生去,在这个例子中,服务器就是医生,浏览器就是病人,病人拿在 手中的病例就是cookie, 存放在医院档案室的资料是session。

4.7.1. cookie和session的区别

cookie就是你的病历本,这个在你手里,你随时可以看,还可以改,所以,这个病例 本对医生来讲就没那么重要,你丢了就都丢了,也就是说,重要信息,需要保密的 信息,不允许写在你的病历本上,说人话就是,cookie不允许存放重要机密信息。

而session就是你的存放医院资料室的重要信息,这些信息因为是存放在医院,从安全 角度上来讲是有一定保障的,所以,如果必须,重要信息请放在session中。

4.7.3. session

session存放在服务器端,虽然增加了服务器的压力,但对于敏感、重要的信息, 只能存放在服务器, 常见的有用户名,余额,验证码等。

session是和cookie配对使用的,此时cookie就像一把钥匙,用来开启存放在session中的内容, 所以,实际使用中cookie或者session丢失哪一个都需要重新认证。

使用session需要设置app.secret_key 的值

以下代码是关于session的设置和读取:

from flask import session

app.secret_key = "北京图灵学院很牛逼,嗯,就是牛逼"

@app.route('/set_session')
def set_session():
    r = make_response("这是session的设置:")
    session["北京图灵学院"] = "讲义放在红宝书网站了"
    session["红宝书网站"] = 'www.baoshu.red'

    return  r

@app.route('/get_session')
def get_session():

    r = {
        '北京图灵学院': session["北京图灵学院"],
        '红宝书网站': session["红宝书网站"]
    }

    return  jsonify(r)

以下是访问set_sessionget_session的结果,需要注意的是,我因为换了新的浏览器, 原来设置的cookie就没有了,所以在cookie显示项只有一个session的值,这个不是session存储 的值,可以把它当做服务器端存储的session的id值,用来查找对应这个cookie的session。

session1

读取session显示内容因为解码设置问题,显示乱码:

session2

4.8. 上下文环境

上下文就是你的程序运行过程中的环境,一般指的是各种环境变量和你需要的变量。

Flask上下文分两种:

  • 请求上下文
  • 应用上下文

一个比较彻底的解释是:

  • application 指的就是当你调用app = Flask(__name__)创建的这个对象app;
  • request指的是每次http请求发生时,WSGI server(比如gunicorn)调用 Flask.__call__()之后,在Flask对象内部创建的Request对象
  • application表示用于响应WSGI请求的应用本身,request表示每次http请求
  • application的生命周期大于request,一个application存活期间,可能发生多次http请求, 所以,也就会有多个request

4.8.1. 请求上下文(request context)

请求上下文主要存储的是本次请求相关的内容, 比如:请求地址,请求方式,cookie

在 flask 中, request和session独享保存有本次请求相关的信息:

  • request:
    • 封装了HTTP请求的内容,针对的是http请求
    • request包含的内容已经给大家讲过
  • session:
    • 用来记录请求会话中的信息,针对的是用户信息

4.8.2. 应用上下文(application context)

应用上下文,可以把它理解成是app变量的内容,本次启动的应用需要用到的信息 保存着应用上下文中。

但它不是一直存在的,它只是RequestContext 中的一个对 app 的代理, 所谓local proxy。

它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。

应用上下文对象有: - current_app - g

4.8.2.1. 应用程序上下文(current_app)

用于存储应用程序中的变量,例如可以通过current_app.name打印当前app的名称, 也可以在current_app中存储一些变量,例如:

  • 应用的启动脚本是哪个文件,启动时指定了哪些参数
  • 加载了哪些配置文件,导入了哪些配置
  • 连了哪个数据库
  • 有哪些public的工具类、常量
  • 应用跑再哪个机器上,IP多少,内存多大

4.8.2.2. g变量

g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用, 我们可以通过它传递一些数据

g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别

4.8.2.3. 区别

  • 请求上下文:保存了客户端和服务器交互的数据
  • 应用上下文:flask 应用程序运行过程中,保存的一些配置信息, 比如程序名、数据库连接、应用信息等

4.8.2.4. 信息展示

上下文环境主要是使用时候根据需要选用,我们写了个代码然后用断点断住 程序后在调试面板查看的结果如下:

from flask import current_app, g

@app.route('/context')
def context():
    ca = current_app
    gg = g
    print(current_app)
    print(g)
    return "Context Infor"

两个变量当前都处于未绑定状态。

current_app变量的内容很丰富,很多:

current_app

但是相对g上下文来讲则没有东西:

g