# 控制逻辑单元/视图 这一部分是MVC中的控制部分,但在Django中此部分的功能称为视图,所以,就 Flask而言,此部分成为啥不我也不知道,这个不重要,重要的是,我们要清楚,这部分 的核心功能是逻辑控制。 ## 返回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"], ) ## 重定向 重定向的概念就是当你访问的资源或者地址由于某些原因不在了,但给你一个新的地址,根据 访问规则,你会自动的访问它给你的新的地址。 就好比多年以后你发达了,去拜访你的初恋准备炫耀一下,结果敲开门是她前夫,前夫很客气 的告诉你你要找的初恋已经搬去了隔壁老王家,然后你就又奔向了隔壁老王家,敲开了老王的门, 然后... ... 下面的代码展示了重定向,你访问的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`函数。这一点从程序 的控制台中显示的访问记录可以看出来: ![重定向](./pic/25.png) ## 自定义状态码 Flask编程可以随时自定义自己的HTTP返回状态码,这个状态码基本上可以随心所以的定义,哈哈哈 以下代码返回一个888的状态码,熟悉HTTP协议的同学们应该这点,这个状态码太屌了~~~ @app.route('/statuscode888') def statuscode_888(): c = 888 return "你要的状态,码, {}".format(c), c ## 异常捕获 Flask为异常处理提供了一些方便的功能。 ### 抛出异常 如果是正常的异常,HTTP协议的错误状态范畴内,可以通过`abort`函数抛出, `abort` 的参数可以是HTTP的错误状态码。 请参见下面代码示例。 ### 捕获异常 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) ## 请求钩子 钩子可以理解成是一个静静的函数,他不需要你手动调用,只有当 发生某些事情的时候(事件), 他会被自动执行,你需要做的只是编写 一个这样的函数就好,调用时机由系统来决定。 有的钩子程序名称不允许自定义,位置也不允许自定义,Flask的 钩子程序由装饰器完成,名称可以自己来写。 Flask支持如下四种常见请求类钩子: - before_first_request - 在处理第一个请求前执行 - before_request - 在每次请求前执行 - 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用 - after_request - 如果没有抛出错误,在每次请求后执行 - 接受一个参数:视图函数作出的响应 - 在此函数中可以对响应值在返回之前做最后一步修改处理 - 需要将参数中的响应在此参数中进行返回 - teardown_request: - 在每次请求后执行 - 接受一个参数:错误信息,如果有相关错误抛出 上面四个钩子的调用顺序如下: 1[钩子顺序](./pic/26.png) 其他的钩子请参阅相应文件`../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("请求后被调用") 连续执行三次后的后台记录如下: ![钩子后台](./pic/27.png) ## 获取请求参数 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) ## 状态保持 有HTTP的无状态性,当外部访问服务器的时候,下次再访问服务器不能记住上次访问 的状态,此时服务器就像一个老年痴呆患者,每次你都要自我介绍还需要从新 开始,这个是很麻烦的,为了弥补HTTP协议的这一问题,通常使用cookie和session来解决。 举个栗子: 我们去医院看病,每次都会带一个病历本,正常医生很难记得祝你是谁,得的什么病, 治疗的办法,用的药物等,为了解决这个问题,每次你看病都要带着你的病历本, 医生会先看你的病历本,以此来知道你的病情进展治疗情况,他好接着给你治病。 有时候一些重要的资料, 让你个人保存也不太方便,比如一些增强ct的照片等,此时 医院会有单独的档案室,你每次见医生需要先去档案室把你的资料提出了,然后再拿着 资料找医生去,在这个例子中,服务器就是医生,浏览器就是病人,病人拿在 手中的病例就是cookie, 存放在医院档案室的资料是session。 ### cookie和session的区别 cookie就是你的病历本,这个在你手里,你随时可以看,还可以改,所以,这个病例 本对医生来讲就没那么重要,你丢了就都丢了,也就是说,重要信息,需要保密的 信息,不允许写在你的病历本上,说人话就是,cookie不允许存放重要机密信息。 而session就是你的存放医院资料室的重要信息,这些信息因为是存放在医院,从安全 角度上来讲是有一定保障的,所以,如果必须,重要信息请放在session中。 ### Cookie cookie 指某些网站为了辨别用户身份、进行会话跟踪而储存在用户本地的数据,通常经过加密处理。 - Cookie最早是网景公司的前雇员Lou Montulli在1993年3月的发明 - Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存 - 下次请求同一网站时就发送该Cookie给服务器, 可以禁止,浏览器默认使用cookie - Cookie的key/value可以由服务器端自己定义, 即cookie的具体内容有开发者定义 应用: - 最典型的应用是判定注册用户是否已经登录网站,此时cookie保存的是用户的登录凭证 - 网站的广告推送,经常遇到访问某个网站时,会弹出小窗口,展示我们曾经在购物网站上看过的商品信息。此时cookie保存的是你的浏览记录 - 购物车,用户可能会在一段时间内在同一家网站的不同页面中选择不同的商品,这些信息都可以写入Cookie,以便在最后付款时提取信息 提示: - Cookie是存储在浏览器中的一段纯文本信息,由于安全愿意不要存储敏感信息如密码 - Cookie基于域名安全,不同域名的Cookie是不能互相访问的,每个网站都有自己独有的cookie - 如访问`www.baoshu.red`时向浏览器中写了Cookie信息,此时的cookie对`www.mycode.wang`无效 - 当浏览器请求某网站时,会将本网站下所有Cookie信息提交给服务器,所以在request中可以读取Cookie信息 #### 浏览器查看cookie 以下是用firefox浏览器访问`www.mycode.wang`显示的cookie信息: ![cookie](./pic/28.png) 可以看到,除了cookie的一些常规信息,比如过期时间,路径等,主要信息都经过了加密处理。 #### cookie的使用 在response里,我们可以直接`set_cookie`来进行设置cookie, 设置的参数为cookie的某一项的 键值内容,可以添加`max_age`表示过期时间。 from flask import make_response @app.route('/set_cookie') def set_cookie(): r = make_response("这是cookie的设置:") r.set_cookie("北京图灵学院", "讲义放在红宝书网站了") r.set_cookie("红宝书网站", 'www.baoshu.red', max_age=36000) return r @app.route('/get_cookie') def get_cookie(): r = { '北京图灵学院': request.cookies.get("北京图灵学院"), '红宝书网站': request.cookies.get("红宝书网站") } return jsonify(r) 通过访问`/set_cookie`展示的结果如下, 在firefox或者chrome中按下F12键就可以查看cookie, 此处因为我cookie的值用的是汉语,解码问题没有显示,显示的是乱码: ![set_cookie](./pic/29.png) 通过访问`/get_cookie`展示的结果如下: ![get_cookie](./pic/30.png) ### 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_session`和`get_session`的结果,需要注意的是,我因为换了新的浏览器, 原来设置的cookie就没有了,所以在cookie显示项只有一个session的值,这个不是session存储 的值,可以把它当做服务器端存储的session的id值,用来查找对应这个cookie的session。 ![session1](./pic/31.png) 读取session显示内容因为解码设置问题,显示乱码: ![session2](./pic/32.png) ## 上下文环境 上下文就是你的程序运行过程中的环境,一般指的是各种环境变量和你需要的变量。 Flask上下文分两种: - 请求上下文 - 应用上下文 一个比较彻底的解释是: - application 指的就是当你调用`app = Flask(__name__)`创建的这个对象app; - request指的是每次http请求发生时,WSGI server(比如gunicorn)调用 `Flask.__call__()`之后,在Flask对象内部创建的Request对象 - application表示用于响应WSGI请求的应用本身,request表示每次http请求 - application的生命周期大于request,一个application存活期间,可能发生多次http请求, 所以,也就会有多个request ### 请求上下文(request context) 请求上下文主要存储的是本次请求相关的内容, 比如:请求地址,请求方式,cookie 在 flask 中, request和session独享保存有本次请求相关的信息: - request: - 封装了HTTP请求的内容,针对的是http请求 - request包含的内容已经给大家讲过 - session: - 用来记录请求会话中的信息,针对的是用户信息 ### 应用上下文(application context) 应用上下文,可以把它理解成是app变量的内容,本次启动的应用需要用到的信息 保存着应用上下文中。 但它不是一直存在的,它只是RequestContext 中的一个对 app 的代理, 所谓local proxy。 它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。 应用上下文对象有: - current_app - g #### 应用程序上下文(current_app) 用于存储应用程序中的变量,例如可以通过current_app.name打印当前app的名称, 也可以在current_app中存储一些变量,例如: - 应用的启动脚本是哪个文件,启动时指定了哪些参数 - 加载了哪些配置文件,导入了哪些配置 - 连了哪个数据库 - 有哪些public的工具类、常量 - 应用跑再哪个机器上,IP多少,内存多大 #### g变量 g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用, 我们可以通过它传递一些数据 g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别 #### 区别 - 请求上下文:保存了客户端和服务器交互的数据 - 应用上下文:flask 应用程序运行过程中,保存的一些配置信息, 比如程序名、数据库连接、应用信息等 #### 信息展示 上下文环境主要是使用时候根据需要选用,我们写了个代码然后用断点断住 程序后在调试面板查看的结果如下: 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](./pic/33.png) 但是相对g上下文来讲则没有东西: ![g](./pic/34.png)