flask 基础知识复习

前言

并不是真正的文档教程。我的复习资料罢了。

文档

中文文档

许多内容可以在这里找到

简单介绍

配置选项

Flask 繁多的配置选项在初始状况下都有一个明智的默认值,并会遵循一些惯例。 例如,按照惯例,模板和静态文件分别存储在应用 Python 源代码树下的子目录 templates 和 static 里。虽然这个配置可以修改,但你通常不必这么做, 尤其是在刚开始的时候。

安全措施

快乐码 Web,安全记心间。

你希望用户数据被妥善安全地保存。

Flask 可保护你免受一个在现代 Web 应用中最常见的安全问题的困扰:跨站脚本攻击(XSS)。Flask 和底层的 Jinja2 模板引擎已经为你应付得足够好,除非你蓄意把不安全的 HTML 标记为安全。但仍有很多导致安全问题的可能。

本文档会在 Web 开发中那些需要注意安全的方面警示你。一些安全上的顾虑远比人们想象的复杂,我们所有人都会有低估漏洞被利用的可能性的时候——直到一个精明的攻击者找出利用我们程序的方法。而且,不要侥幸认为你的应用没有重要到足够吸引攻击者。取决于攻击的类型,有时候会是自动化的僵尸机器来检测如何在你数据库中填充垃圾内容、恶意程序链接或之类东西。

开发者必须在为需求编写代码时留心安全隐患。

依赖

Flask 依赖两个外部库:WerkzeugJinja2 。 Werkzeug 是一个 WSGI(在 Web 应用和多种服务器之间的标准 Python 接口) 工具集。Jinja2 负责渲染模板。

开发环境配置

多数时候我们使用虚拟环境进行python web项目的开发。

virtualenv 解决了什么问题?如果你像我一样喜欢 Python,不仅会在采用 Flask 的Web 应用中用上 virtualenv,在别的项目中你也会想用上它。你拥有的项目越多,同时使用不同版本的 Python 工作的可能性也就越大,或者起码需要不同版本的 Python 库。悲惨现实是:常常会有库破坏向后兼容性,然而正经应用不采用外部库的可能微乎其微。当在你的项目中,出现两个或更多依赖性冲突时,你会怎么做?

virtualenv 拯救世界!virtualenv 为每个不同项目提供一份 Python 安装。它并没有真正安装多个 Python 副本,但是它确实提供了一种巧妙的方式来让各项目环境保持独立。让我们来看看 virtualenv 是怎么工作的。

我们可以使用上述的virtualenv

使用 pip 等进行安装它。

pip install virtualenv

如果你使用好像是 python 3.6 以上版本可以使用自带的创建虚拟环境的方式

python -m venv env

# 注:env为虚拟环境名

通常虚拟环境名称为 venv,env等

在python 3.6 + ,gitbash环境(为windows下但是使用liunx命令)下

说明以下如何进入虚拟环境等

mkdir project
cd project
python -m venv env

# 上述已经建立好了一个项目虚拟环境
# 进入

source /env/Scripts/activate

# 退出
deactivate

进入后我们便可以在当前虚拟环境下安装我们的python包和框架等

pip install Flask

演示

应用

# hello.py
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
	return 'Hello World!'

if __name__ == '__main__':
	app.run()
# 运行
python hello.py

那么,这段代码做了什么?

  1. 首先,我们导入了 Flask 类。这个类的实例将会是我们的 WSGI 应用程序。
  2. 接下来,我们创建一个该类的实例,第一个参数是应用模块或者包的名称。 如果你使用单一的模块(如本例),你应该使用 __name__ ,因为模块的名称将会因其作为单独应用启动还是作为模块导入而有不同( 也即是 '__main__' 或实际的导入名)。这是必须的,这样 Flask 才知道到哪去找模板、静态文件等等。详情见 Flask 的文档。
  3. 然后,我们使用 route() 装饰器告诉 Flask 什么样的URL 能触发我们的函数。
  4. 这个函数的名字也在生成 URL 时被特定的函数采用,这个函数返回我们想要显示在用户浏览器中的信息。
  5. 最后我们用 run() 函数来让应用运行在本地服务器上。 其中 if __name__ == '__main__': 确保服务器只会在该脚本被 Python 解释器直接执行的时候才会运行,而不是作为模块导入的时候。

欲关闭服务器,按 Ctrl+C。

初始化

Flask类构造函数唯一需要的参数就是应用程序的主模块或包。Flask使用这个参数来确定应用程序的根目录,这样以后可以相对这个路径来找到资源文件.

from flask import Flask
app = Flask(__name__)

路由

如上所见, route() 装饰器把一个函数绑定到对应的URL 上。而被该装饰器装饰的函数称之为视图函数。

非动态路由:

@app.route('/')
def index():
   return 'Index Page'

@app.route('/hello')
def hello():
   return 'Hello World'

动态路由:

指定一个变量将变量在视图函数中进行使用。

@app.route('/user/<username>')
def show_user_profile(username):
   return 'User %s' % username

类似这样的,默认的转换器是str类型,接受以下:

<string:name>
<int:id>
<float:num>
<path:p>
1.<id> :默认接受的类型是str
2.<string:name> :指定name的类型为str
3.<int:id> :指定的id类型是整性
4.<float:num> : 指定num的类型为浮点数(四舍五入,且不能接收整数类型)
5.<path:path1> : 指定接收的path为url中的路径

同时允许多个url装饰某个视图函数

比如:

@app.route('/')
@app.route('/user/<int:id>')
def user(id):
	1return id

更多的URL定向内容看文档

使用 url_for()在程序里进行url的构造。

url_for(‘蓝图.视图函数名’,动态参数)

如果参数不是本url必须的参数将会形成一个查询参数在url后面

类似:

@app.route('/user/<int:id>')
def user(id):
	return id

# url_for(user,id=1,name='xxx')
# --> /user/1?name=xxx
  1. 反向构建通常比硬编码的描述性更好。更重要的是,它允许你一次性修改 URL, 而不是到处边找边改。
  2. URL 构建会转义特殊字符和 Unicode 数据,免去你很多麻烦。
  3. 如果你的应用不位于 URL 的根路径(比如,在 /myapplication 下,而不是 / ), url_for() 会妥善处理这个问题。

HTTP方法

methods:

  • GET :获取
  • POST:创建
  • PUT:修改(全属性)
  • DELETE:删除
  • PATCH:修改(部分)

定义路由的请求方式,默认情况下是GET

@app.route('/user',methods=['GET','POST'])
def user():
   if request.method == 'POST':
       pass
   else :
       pass

HTTP 方法(也经常被叫做“谓词”)告知服务器,客户端想对请求的页面 些什么。下面的都是非常常见的方法:

  • GET

浏览器告知服务器:只 获取 页面上的信息并发给我。这是最常用的方法。

  • HEAD

浏览器告诉服务器:欲获取信息,但是只关心 消息头 。应用应像处理 GET 请求一样来处理它,但是不分发实际内容。在 Flask 中你完全无需 人工 干预,底层的 Werkzeug 库已经替你打点好了。

  • POST

浏览器告诉服务器:想在 URL 上 发布 新信息。并且,服务器必须确保 数据已存储且仅存储一次。这是 HTML 表单通常发送数据到服务器的方法。

  • PUT

类似 POST 但是服务器可能触发了存储过程多次,多次覆盖掉旧值。你可 能会问这有什么用,当然这是有原因的。考虑到传输中连接可能会丢失,在 这种 情况下浏览器和服务器之间的系统可能安全地第二次接收请求,而 不破坏其它东西。因为 POST 它只触发一次,所以用 POST 是不可能的。

  • DELETE

删除给定位置的信息。

  • OPTIONS

给客户端提供一个敏捷的途径来弄清这个 URL 支持哪些 HTTP 方法。 从 Flask 0.6 开始,实现了自动处理。

如果存在 GET ,那么也会替你自动地添加 HEAD,无需干预。它会确保遵照 HTTP RFC (描述 HTTP 协议的文档)处理 HEAD 请求,所以你可以完全忽略这部分的 HTTP 规范。同样,自从 Flask 0.6 起, 也实现了 OPTIONS 的自动处理。

有趣的是,在 HTML4 和 XHTML1 中,表单只能以 GET 和 POST 方法提交到服务器。但是 JavaScript 和未来的 HTML 标准允许你使用其它所有的方法。此外,HTTP 最近变得相当流行,浏览器不再是唯一的 HTTP 客户端。比如,许多版本控制系统就在使用 HTTP。

模版渲染

用 Python 生成 HTML 十分无趣,而且相当繁琐,因为你必须手动对 HTML 做转义来保证应用的安全。为此,Flask 配备了 Jinja2 模板引擎。

你可以使用 render_template() 方法来渲染模板。你需要做的一切就是将模板名和你想作为关键字的参数传入模板的变量。这里有一个展示如何渲染模板的简例:

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
   return render_template('hello.html', name=name)

Flask 会在 templates 文件夹里寻找模板。所以,如果你的应用是个模块,这个文件夹应该与模块同级;如果它是一个包,那么这个文件夹作为包的子目录:

请求和响应

from flask import request

可以使用request 对象里的各个属性进行数据处理等,接受到请求会自动创建request对象,并且该对象不可修改。

url:完整的请求地址

base_url:去掉GET参数的url

host_url:只有主机和端口号的url

path:路由中的路径

method:请求方法

remote_addr:请求的客户端的地址

args:GET请求参数

form:POST请求参数

files:文件上传

headers:请求头

cookies:请求中的cookie

注:form 与 args 对象均是 ImmutableMultiDict对象,类字典。

(key-value)

可以使用 form[‘key’]、form.get(‘key’)、form.getlist(‘key’) 等方法获取数据。

当访问 form 属性中的不存在的键会发生什么?会抛出一个特殊的 KeyError 异常。你可以像捕获标准的 KeyError 一样来捕获它。 如果你不这么做,它会显示一个 HTTP 400 Bad Request 错误页面。所以,多数情况下你并不需要干预这个行为。

关于文件上传,HTML表单需要指定enctype="multipart/form-data",已上传的文件存储在内存或是文件系统中一个临时的位置。你可以通过请求对象的 files 属性访问它们。每个上传的文件都会存储在这个字典里。它表现近乎为一个标准的 Python file 对象,但它还有一个 save() 方法,这个方法允许你把文件保存到服务器的文件系统上。

from flask import make_response

视图函数的返回值会被自动转换为一个response对象。

如果返回值是一个字符串, 它被转换为该字符串为主体的、状态码为 200 OK的 、 MIME 类型是 text/html 的响应对象。Flask 把返回值转换为响应对象的逻辑是这样:

  1. 如果返回的是一个合法的响应对象,它会从视图直接返回。
  2. 如果返回的是一个字符串,响应对象会用字符串数据和默认参数创建。
  3. 如果返回的是一个元组,且元组中的元素可以提供额外的信息。这样的元组必须是 (response, status, headers) 的形式,且至少包含一个元素。 status 值会覆盖状态代码, headers 可以是一个列表或字典,作为额外的消息标头值。
  4. 如果上述条件均不满足, Flask 会假设返回值是一个合法的 WSGI 应用程序,并转换为一个请求对象。

如果你想在视图里操纵上述步骤结果的响应对象,可以使用 make_response() 函数。

# API
make_reponse(date,status)
# date 为响应数据
# status 为响应状态码

比如:

使用 errorhandler(status_code)装饰器定义我们的错误页面

@app.errorhandler(404)
def not_found(error):
   return render_template('error.html'), 404
   
@app.errorhandler(404)
def not_found(error):
   resp = make_response(render_template('error.html'), 404)
   resp.headers['X-Something'] = 'A value'
   return resp

重定向和终止

from flask import abort, redirect, url_for

@app.route('/')
def index():
   return redirect(url_for('login'))

@app.route('/login')
def login():
   abort(401)
   this_is_never_executed()

使用 redirecturl_for('endpoint',**args)来进行重定向。

终止或者捕获异常

自动抛出异常:abort(status_code)

捕获异常进行处理:errorhandler(status_code),需要一个参数进行异常信息的接收。

@app.errorhandler(400)
def handler(exception):
   return '异常信息:%s' % exception

Cookie和Session

访问者的标识问题服务器需要识别来自同一访问者的请求。

这主要是通过浏览器的cookie实现的。 访问者在第一次访问服务器时,服务器在其cookie中设置一个唯一的ID号:会话ID(session_id)。

这样,访问者后续对服务器的访问头中将自动包含该信息,服务器通过这个ID号,即可区 隔不同的访问者。

cookie:

客户端会话技术,浏览器的会话技术
数据全部存储在客户端中
存储使用的键值对结构进行存储
特性:
   支持过期时间
   默认会自动携带本网站的cookie
   不能跨域名
   不能跨浏览器

通过服务器创建的响应来创建一个cookie。

# 设置
set_cookie('key',value,max_ages='',expires='')
# 删除
delete_cookie('key')


import datetime
from flask import make_response,request


@app.route('/setCookie')
def set_cookie():
   res = make_response('set-cookie')
   # 设置过期时间
   expires = datetime().datetime.today() + datetime.timedelta(days=30)
   # 设置cookie
   res.set_cookie('flask_cookie','sth...',expires=expires)
   return res

@app.route('/delCookie')
def del_cookie():
   res = make_response('del-cookie')
   # 删除cookie
   res.del_cookie('flask_cookie')
   return res

@app.route('/getCookie')
def get_cookie():
   res = request.cookies.get('flask_cookie')
   return res

session:

它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名。这意味着用户可以查看你 Cookie 的内容,但却不能修改它,除非用户知道签名的密钥。

要使用会话,你需要设置一个密钥。

以下通过一个基于session的登录退出流程演示。

import os
from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)

# 设置密钥
app.secret_key = os.urandom(24)

@app.route('/')
def index():
   if 'username' in session:
       # 获取session['key'] 不存在报异常
       # session.get('key') 不存在 返回 None
       return 'Logged in as %s' % escape(session['username'])
   return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
   if request.method == 'POST':
       # 设置 session['key'] = value  
       session['username'] = request.form['username']
       return redirect(url_for('index'))
   return '''
       <form action="" method="post">
           <p><input type=text name=username>
           <p><input type=submit value=Login>
       </form>
   '''

@app.route('/logout')
def logout():
   # 删除session.pop('key')
   session.pop('username', None)
   return redirect(url_for('index'))


# 清空session 
session.clear()

消息闪现

Flask 提供了消息闪现系统,可以简单地给用户反馈。 消息闪现系统通常会在请求结束时记录信息,并在下一个(且仅在下一个)请求中访问记录的信息。展现这些消息通常结合要模板布局。

使用 flash() 方法可以闪现一条消息。要操作消息本身,请使用 get_flashed_messages() 函数,并且在模板中也可以使用。完整的例子见 消息闪现 部分。

{% for message in get_flashed_messages() %}
<script>
   // 使用脚本进行消息的展示
	Materialize.toast("{{ message }}", 3000, 'rounded')
</script>
{%endfor%}

蓝图

Flask 用 蓝图(blueprints) 的概念来在一个应用中或跨应用制作应用组件和支持通用的模式。蓝图很好地简化了大型应用工作的方式,并提供给 Flask 扩展在应用上注册操作的核心方法。一个 Blueprint 对象与 Flask 应用对象的工作方式很像,但它确实不是一个应用,而是一个描述如何构建或扩展应用的 蓝图

主要是为了以下情况设计:

  • 把一个应用分解为一个蓝图的集合。这对大型应用是理想的。一个项目可以实例化一个应用对象,初始化几个扩展,并注册一集合的蓝图。
  • 以 URL 前缀和/或子域名,在应用上注册一个蓝图。 URL 前缀/子域名中的参数即成为这个蓝图下的所有视图函数的共同的视图参数(默认情况下)。
  • 在一个应用中用不同的 URL 规则多次注册一个蓝图。
  • 通过蓝图提供模板过滤器、静态文件、模板和其它功能。一个蓝图不一定要实现应用或者视图函数。
  • 初始化一个 Flask 扩展时,在这些情况中注册一个蓝图。

Blueprint()中传入了两个参数,第一个是蓝图的名称,第二个是蓝图所在的包或模块,__name__代表当前模块名或者包名。

from flask import Blueprint

main = Blueprint('main',__name__)

# from . import views 

# views.py

@main.route('/')
@main.route('/home/<int:page>')
def home(page=1):
posts = Post.query.order_by(db.desc(Post.time)).paginate(page,PAGESIZE,False)
articles = Article.query.order_by(db.desc(Article.time)).limit(5).all()
return render_template(
  'index.html',
  title='Home',
  posts=posts,
  articles=articles,
  year = datetime.now().year
)

我们需要将这个蓝图注册后才可以使用

from .main import main
app.register_blueprint(main)

注意 使用蓝图需要在反向解析url时进行处理。

# 任何使用
url_for('main.index')
# 在本模块或包里可以这样
url_for('.index')

更多的参数查看文档。

扩展

暂时仅列出我使用过的一些。

Flask-CKEditor==0.4.3
Flask-Login==0.4.1
Flask-Migrate==2.4.0
Flask-RESTful==0.3.7
Flask-Script==2.0.6
Flask-SQLAlchemy==2.3.2
Flask-Uploads==0.2.1
Flask-WTF==0.14.2
mysql-connector==2.2.9

包括,数据库,数据库迁移管理,脚本命令使用,上传,登录,表单处理

练习项目中会使用到。到时再记录吧



奖励一下