Flask 学习笔记(一)
一、Web 服务器与 Web 框架
首先明确一下,要运行一个动态网页,我们需要
- 一个 Web 服务器来监听并响应请求,如果请求的是静态文件它就直接将其返回,如果是动态 url 它就将请求转交给 Web 应用。
- 一个 Web 应用来动态处理请求,生成响应
其中 Web 服务器通常都是别人已经实现好了的,它通过定义好的接口与我们编写的 Web 应用通信。WSGI 就是一个统一的 Web 服务器接口标准,如果我们按照 WSGI 编写 Web 应用,那么它就能在任何符合该标准的服务器上运行,例如 Gunicorn.(对比一下 Java 的 Servlet,按照 Servlet 规范编写的应用,都能运行在任何 Servlet 容器上,例如 Tomcat 和 Jetty,Servlet 容器就相当于 WSGI 服务器)
可是 WSGI 仍然比较底层,直接照着它写太麻烦,于是就有了 Web 框架,Python 知名的就是 Flask 和 Django。Java 的 Servlet 也是如此,对应的出现了 Spring MVC 框架。但是 Flask 和 Django 都有内置服务器用于测试,而 Spring MVC 没有,倒是 Spring Boot 可以使用内嵌的 tomcat 容器。
二、Flask
Flask 是一个微框架,“微”是指它的核心非常小,任何可选的功能都不包括在内。但是 Flask 社区提供了丰富的拓展插件,你可以通过选择需要的插件来实现你想要的功能。
中间略过 n 万字。。。
三、上下文 Context
上下文 Context,是动态创建的东西。flask 通过四个全局代理对象,提供对上下文的访问:
- current_app 和 g:应用上下文
- request 和 session:请求上下文
1. 请求上下文 Request Context
Java 的 Spring MVC 将请求作为方法参数传入,而且要求参数必须映射到某个类型或者某个 model 上。
Flask 的处理方式与之不同,它提供了一个全局代理对象——request,只要是在请求从开始到结束的过程中,都可以直接通过这个 request 访问 HTTP 请求的各种参数。
请求上下文在请求开始时被压入栈,这时 request 对象才可用,在请求结束后会立即被 pop 出来。
疑问
为什么 request 得是一个代理对象呢?
- 在视图函数被 load 时,很可能连 app 都还没有创建(如果用工厂模式 create_app 的话),更不可能存在 request 了。
- 因此 request 只能是一个占位符(代理对象),等到请求真正到来时,flask 再将请求数据 push 到 request_stack 内,这时 request 对象就可用了。
2. 应用上下文 Application Context
一个 app,就是一个 Flask 实例,通过 app = Flask(__name__)
创建。
在请求到来时,除了请求上下文被入栈,还有应用上下文也会被入栈。
请求结束时,它先 pop 出请求上下文,然后是应用上下文。
也就是说在请求过程中,应用上下文是可用的,current_app 就是当前 app 实例的代理,对它进行任何操作,都会直接影响到 app 本身!!(current_app 的修改是全局的!)
疑问
在 flask 初始化时,我们不是已经通过 app = Flask(__name__)
创建了一个 app 实例了么?直接用它不行么?
- 在工厂模式下,
app = Flask(__name__)
会在create_app()
被调用时,才会执行。- 可是在这之前,整个 Web 项目就会被初始化。初始化时根本不存在 app 实例。因此只能先用个 current_app 做占位符(代理对象)
- 编写可重用的 blueprint 时,你不知道要从哪里导入 app 实例。如果直接用 current_app,就不需要去管 app 什么时候在哪被创建了。
3. g 对象(应用上下文)
g 对象,名称来源于 global
,保存在应用上下文中,指应用上下文中的全局变量。(其实每个请求都拥有独立的 g 对象)
如果我们需要在当前请求中传递数据(请求结束,数据就销毁),是不能使用 current_app 的,因为对 current_app 的操作会直接影响到 app 本身。
但是应用上下文本身的生命周期,是和请求上下文相同的,flask 在应用上下文中提供了 g 对象,该对象不是什么别的代理!!因此对它的操作,在请求结束时,就会连同 current_app 一起被销毁。
4. session 对象(请求上下文)
session 位于 请求上下文中。它的内容会被保存到 cookie 中,发送到客户端。每次收到请求时又会从 cookie 中加载它。因此它可用于跨请求传输数据。
这种 session 被称为客户端 session(client side session)
session 被存入 cookie 前,会使用你设定的 SECRET_KEY
签名,这样才能确认请求中的 session 就是 flask 发送出去的,没有被人修改过,或者说不是伪造的。(除非你的 SECRET_KEY
泄漏了。。)
但是签名不是数据加密,session 的内容很容易被解码出来,因此 session 只适合存放非敏感数据!!!下面是 session 解密代码:
#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode
def decryption(payload):
payload, sig = payload.rsplit(b'.', 1)
payload, timestamp = payload.rsplit(b'.', 1)
decompress = False
if payload.startswith(b'.'):
payload = payload[1:]
decompress = True
try:
payload = base64_decode(payload)
except Exception as e:
raise Exception('Could not base64 decode the payload because of '
'an exception')
if decompress:
try:
payload = zlib.decompress(payload)
except Exception as e:
raise Exception('Could not zlib decompress the payload before '
'decoding the payload')
return session_json_serializer.loads(payload)
if __name__ == '__main__':
print(decryption(sys.argv[1].encode()))
通过命令行传入你的 session,它就能直接给出内容!
如果你确实需要在 session 中存放敏感数据(比如验证码),那么你可以阅读一下参考链接中的文章
四、插件
- python-dotenv:配置文件
- flask_wtform:表单
- flask-login:登录与权限验证
- flask-sqlalchemy + flask-migrate:数据库
- flask-rest-api:restful api
- flask-Sockets:websocket
- flask-mail: 邮件发送
- flask-limiter: ip 访问频率限制(防爬)
- flask-security: 整合了 flask-login flask-mail 等一系列用户验证相关的插件,提供完善的权限验证功能。
- flask-admin: 可以很方便的构建网站管理后台
- flask-caching: 缓存
四、使用 blueprint(蓝图)
入门级 flask app 都是单文件应用,当复杂度上升,我们可以把单文件应用分成多个文件,比如:views.py、models.py、forms.py、errors.py。
但是如果项目继续增大,各个文件也会渐渐变得难以维护。views.py 中各种功能的视图函数混在一起,models.py 和 forms.py 也是。显然我们可以按功能,继续分割这几个代码文件。
blueprint 就是用于应对大型项目开发的功能,使用它可以对上述的文件按功能做进一步的拆分。
蓝图使用起来就像应用当中的子应用一样,可以有自己的模板,静态目录,有自己的视图函数和URL规则,蓝图之间互相不影响。但是它们又属于应用中,可以共享应用的配置。
实际上 BluePrint 类的使用也几乎和 Flask 一模一样。差别只是 BluePrint 最后还需要在 Flask 实例中注册:app.register_blueprint(<blue_print_obj>)
.
注意事项
- url_for 的第一个参数 endpoint,是 view function 的名称,不是 route 路径!
- 使用 blueprint 时,endpoint 为
蓝图名称.视图函数名称
- 使用 blueprint 时,endpoint 为
- 读取资源文件:资源文件可以放在
data
文件夹下,使用flask.open_resources("data/<file_name>", mode="rb")
读取资源文件。- 不放在
static
下的原因是,该目录内的文件是可以直接通过 URI/static/<file_path>
访问的,是公开的。我们一般不希望用户直接访问这种资源文件。
- 不放在
- flask 的内置服务器和 gunicorn 都不支持 http2、keep-alive(http1.1),也没有 gzip 实体压缩。这些功能通常都通过使用 nginx 做反向代理来获得。
- gunicorn 对此的解释是,gunicorn 就是设计用来放在 nginx 后面运行的,而 nginx 与它的 upstream 通信时默认使用 http1.0