文章目录
- 简介
- 安装
- 初试
- 路由
- 请求上下文
- 请求钩子
- 响应
- 模板
- 自定义错误页面
- Web表单
- SQL数据库
- 电子邮件
- TODO:用户认证
- 大型程序结构
- 配置管理
- REST Web服务
- 测试
- 记录慢查询
- 部署
- 日志
- 下载文件
- 上传文件
- 获取参数
- 重定向
- 性能分析
- Python高并发服务部署——Nginx+Gunicorn+gevent+Flask+Supervisor
- Flask 扩展
- Flask-Bootstrap好看的页面
- Flask-Moment本地化日期和时间
- Flask-WTF表单验证和渲染
- Flask-SQLAlchemy管理数据库
- Flask-Migrate迁移数据库
- Flask-Mail电子邮件
- Flask-Login用户认证
- Flask-HTTPAuth接口认证
- Flask-Talisman安全HTTP
- Flask-RESTful构建RESTAPI
- Flask-OAuth第三方登录
- Flask-OpenID开放式认证
- Flask-WhooshAlchemy全文搜索
- Flask-Admin后台管理
- Flask-Security安全机制
- Flask-JWT-Extended用户认证
- Flask-Limiter接口频率限制
- Flask-Babel国际化
- Flask-Caching缓存
- Flask-DebugToolbar调试工具
- Flask-Redis
- Flask-SQLAcodegen自动生成模型
- Flask-SSE向前端发数据
- Flask-SocketIO实时双向通讯
- Flask-Uploads文件上传
- Flask-Dropzone拖拽上传
- Flask-CKEditor富文本编辑器
- Flask-FlatPages代码高亮
- Flask-Opentracing分布式跟踪
- Flasgger文档生成
- 更多扩展
- 自动化部署
- 自动化测试
- 日志处理
- 高级框架
- 推荐阅读
- 后台任务
- 解决循环引用
- Flask+RocketMQ
- 遇到的坑
- 参考文献
简介
Flask 是一款基于 Werkzeug 和 Jinja2 实现的轻量级 Web 框架,灵活轻便易上手,有非常多的扩展插件。
安装
pip install Flask
本文 Flask==2.1.2
初试
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
@app.route('/user/<name>')
def user(name):
return f'<h1>Hello, {name}!</h1>'
@app.route('/number/<int:id>')
def number(id):
return f'<h1>Hello, {id}!</h1>'
if __name__ == '__main__':
app.run(debug=True)
访问 http://127.0.0.1:5000/user/XerCis
访问 http://127.0.0.1:5000/number/1
Ctrl + C 退出程序
路由
处理 URL 到 Python 函数的映射程序称为路由,如 @app.route('/')
,将触发视图函数 index()
创建路由方式有以下两种:
装饰器
装饰器 app.route()
相当于函数 app.add_url_rule()
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
def user(name):
return f'<h1>Hello, {name}!</h1>'
app.add_url_rule('/user/<name>', view_func=user)
print(app.url_map)
# Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
# <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
# <Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])
蓝图
实现代码分层
auth/__init__.py
from flask import Blueprint
auth = Blueprint('auth', __name__)
from . import views
auth/views.py
from . import auth
@auth.route('/login')
def login():
return 'success'
main/__init__.py
from flask import Blueprint
main = Blueprint('main', __name__)
from . import views
main/views.py
from . import main
@main.route('/')
def index():
return '<h1>Hello World!</h1>'
app.py
from flask import Flask
from main import main as main_blueprint
from auth import auth as auth_blueprint
app = Flask(__name__)
app.register_blueprint(main_blueprint)
app.register_blueprint(auth_blueprint, url_prefix='/auth')
print(app.url_map)
# Map([<Rule '/auth/login' (OPTIONS, GET, HEAD) -> auth.login>,
# <Rule '/' (OPTIONS, GET, HEAD) -> main.index>,
# <Rule '/static/<filename>' (OPTIONS, GET, HEAD) -> static>])
注册蓝图标准流程:
- 创建蓝图对象
- 使用蓝图对象注册路由
- app 对象注册蓝图对象
from flask import Flask
from flask import Blueprint
app = Flask(__name__)
blueprint = Blueprint('main', __name__) # 1. 创建蓝图对象
@blueprint.route('/') # 2. 使用蓝图对象注册路由
def ping():
return 'pong'
app.register_blueprint(blueprint) # 3. app 对象注册蓝图对象
if __name__ == '__main__':
print(app.url_map)
app.run()
自动注册蓝图
按业务划分代码
app/blueprints.py,蓝图的统一定义
from flask import Blueprint
common = Blueprint('common', __name__)
app/common/views.py
from app.blueprints import common
@common.route('/')
def ping():
return 'pong'
main.py
import importlib
from flask import Flask
from flask import Blueprint
app = Flask(__name__)
module = importlib.import_module('app.blueprints')
for name, blueprint in module.__dict__.items():
if isinstance(blueprint, Blueprint):
importlib.import_module('app.{}.views'.format(name))
app.register_blueprint(blueprint)
if __name__ == '__main__':
print(app.url_map)
app.run()
请求上下文
Flask 使用上下文临时把某些对象变为全局可访问,如参数 request
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
user_agent = request.headers.get('User-Agent')
return f'<p>Your browser is {user_agent}</p>'
if __name__ == '__main__':
app.run(debug=True)
变量名 | 上下文 | 说明 |
---|---|---|
current_app | 程序上下文 | 当前激活程序的实例 |
g | 程序上下文 | 处理请求时的临时存储对象 |
request | 请求上下文 | 请求对象,封装了客户端的 HTTP 请求内容 |
session | 请求上下文 | 用户会话,存储请求间的键值对字典 |
请求钩子
在请求前或后执行代码,如请求前创建数据库连接或认证发起请求的用户
为避免在每个视图函数中使用重复代码,Flask 提供了注册通用函数的功能
请求钩子用装饰器实现:
before_first_request
:在处理第一个请求前运行before_request
:每次请求前运行after_request
:如果没有未处理的异常抛出,在每次请求后运行teardown_request
:即使有未处理的异常抛出,也在每次请求后运行
响应
Flask 响应的状态码默认为 200,表示成功处理
如果需要不同状态码,,可作为第二个返回值,如状态码 400,表示请求无效
或返回 Response 对象,可用 make_response()
函数构造
重定向的响应码为 302,可使用 redirect()
替代返回三个值或 Response 对象的形式
网页或页面没找到的响应码为 404,常通过 abort()
处理错误
from flask import Flask, make_response, redirect, abort
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Bad Request</h1>', 400
@app.route('/error')
def error():
response = make_response('<h1>This document carries a cookie!</h1>')
response.set_cookie('answer', '42')
return response
@app.route('/user/<int:id>')
def get_user(id):
if id % 2 == 0:
abort(404)
else:
return '<h1>Hello</h1>'
@app.route('/redirect')
def _redirect():
return redirect('http://www.baidu.com')
if __name__ == '__main__':
app.run(debug=True)
访问:
- http://127.0.0.1:5000/
- http://127.0.0.1:5000/error
- http://127.0.0.1:5000/redirect
- http://127.0.0.1:5000/user/1
- http://127.0.0.1:5000/user/2
模板
为避免混乱,业务逻辑和显示逻辑应分开
模板是一个包含响应文本的文件,占位符表示动态部分,Flask 使用 Jinja2
这个强大的模板引擎进行渲染
项目结构,Flask 默认在 templates 子文件夹下寻找模板
templates/index.html
<h1>Hello World!</h1>
templates/user.html
<h1>Hello, {{ name }}!</h1>
main.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
if __name__ == '__main__':
app.run(debug=True)
访问:
变量
- 变量要用空格隔开,如
{{ x }}
- 能识别多种类型,如列表、字典、对象,如
{{ d['key'] }}
、{{ obj.f() }}
- 能用过滤器修改变量,如首字母大写
Hello, {{ name|capitalize }}
templates/index.html
<p>{{ d['key'] }}</p>
<p>{{ l[3] }}</p>
<p>{{ l[index] }}</p>
<p>{{ obj.f() }}</p>
<p>Hello, {{ name|capitalize }}</p>
main.py
from flask import Flask, render_template
app = Flask(__name__)
class A():
def f(self):
return 'a'
@app.route('/')
def index():
d = {'key': 'Hello'}
l = [0, 1, 2, 3, 4]
index = 4
obj = A()
name = 'xercis'
return render_template('index.html', d=d, l=l, index=index, obj=obj, name=name)
if __name__ == '__main__':
app.run(debug=True)
流程控制
条件 templates/condition.html
{% if x > 0 %}
x is positive
{% elif x == 0 %}
x is zero
{% else %}
x is negative
{% endif %}
循环 templates/loop.html
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
宏,类似于 Python 中的函数,templates/macro.html
{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
</ul>
还有模板继承,此处略。
main.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/condition/<x>')
def condition(x):
x = int(x)
return render_template('condition.html', x=x)
@app.route('/loop')
def loop():
comments = 'abcde'
return render_template('loop.html', comments=comments)
@app.route('/macro')
def macro():
comments = 'abcde'
return render_template('macro.html', comments=comments)
if __name__ == '__main__':
app.run(debug=True)
访问:
- http://127.0.0.1:5000/condition/-1
- http://127.0.0.1:5000/condition/0
- http://127.0.0.1:5000/condition/1
- http://127.0.0.1:5000/loop
- http://127.0.0.1:5000/macro
自定义错误页面
from flask import Flask, abort
app = Flask(__name__)
@app.route('/a')
def a():
abort(404)
@app.route('/b')
def b():
abort(500)
@app.errorhandler(404)
def page_not_found(e):
return '<h1>Page not found</h1>', 404
@app.errorhandler(500)
def internal_server_error(e):
return '<h1>internal server error</h1>', 500
if __name__ == '__main__':
app.run(debug=True)
访问:
Web表单
安装
pip install flask-bootstrap
pip install flask-wtf
快速渲染表单 templates/index.html
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block content %}
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
{{ wtf.quick_form(form) }}
{% endblock %}
main.py
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask import Flask, render_template, session, redirect, url_for, flash
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string' # 生成加密令牌的密钥
from flask_bootstrap import Bootstrap
bootstrap = Bootstrap(app)
class NameForm(FlaskForm):
name = StringField('What is your name?', validators=[DataRequired()])
submit = SubmitField('Submit')
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
old_name = session.get('name')
if old_name is not None and old_name != form.name.data:
flash('Looks like you have changed your name!')
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name'))
if __name__ == '__main__':
app.run(debug=True)
SQL数据库
订单管理程序有数据表如下
- customers:
- products:
- orders:
安装
pip install flask-sqlalchemy
数据库URL
数据库引擎 | URL |
---|---|
MySQL | mysql://scott:tiger@localhost/foo mysql+mysqldb://scott:tiger@localhost/foo mysql+pymysql://scott:tiger@localhost/foo |
PostgreSQL | postgresql://scott:tiger@localhost/mydatabase postgresql+psycopg2://scott:tiger@localhost/mydatabase |
SQLite | sqlite:///C:\path\to\foo.db |
定义模型
类型 | Python 类型 | 描述 |
---|---|---|
BigInteger | int | 大整数 |
Boolean | bool | 布尔类型 |
Date | datetime.date | 日期 |
DateTime | datetime.datetime | 日期时间 |
Enum | str | 枚举 |
Float | float | 浮点数 |
Integer | int | 整数 |
Interval | datetime.timedelta | 时间间隔 |
LargeBinary | str | 二进制文件 |
MatchType | MATCH操作符的返回类型 | |
Numeric | decimal.Decimal | 数值的基类 |
PickleType | 任意 Python 对象 | pickle序列化的Python对象 |
SchemaType | Mark a type as possibly requiring schema-level DDL for usage. | |
SmallInteger | int | 较小整数 |
String | str | 字符串的基类 |
Text | str | 可变大小字符串 |
Time | datetime.time | 时间 |
Unicode | str | 可变大小Unicode字符串 |
UnicodeText | str | 长度无界的Unicode字符串 |
选项明 | 描述 |
---|---|
name | 数据库中的列名 |
type_ | 列类型 |
autoincrement | 整数主键列自动递增 |
default | 默认值 |
index | 索引 |
nullable | 是否允许为空 |
unique | 是否为唯一值 |
增删改查
过滤器 | 描述 |
---|---|
filter() | 筛选条件 |
filter_by() | 关键字形式的筛选条件 |
limit() | 数量限制 |
offset() | 偏移量 |
order_by() | 排序 |
group_by() | 分组 |
执行函数 | 描述 |
---|---|
all() | 以列表的形式返回 |
first() | 第一条结果 |
first_or_404() | 第一条结果或404 |
get() | 主键对应的行 |
get_or_404() | 主键对应的行或404 |
count() | 结果数量 |
paginate() | 返回一个 Paginate 对象,包含指定范围的结果 |
from pathlib import Path
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + str(Path(__file__).parent / 'data.sqlite') # 数据库连接
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 不跟踪对象修改
db = SQLAlchemy(app)
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
users = db.relationship('User', backref='role', lazy='dynamic')
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
def __repr__(self):
return '<User %r>' % self.username
if __name__ == '__main__':
db.drop_all() # 删除旧数据
db.create_all() # 创建数据库和表
# 插入行
admin_role = Role(name='Admin')
mod_role = Role(name='Moderator')
user_role = Role(name='User')
user_john = User(username='john', role=admin_role)
user_susan = User(username='susan', role=user_role)
user_david = User(username='david', role=user_role)
# db.session.add(admin_role)
# db.session.add(mod_role)
# db.session.add(user_role)
# db.session.add(user_john)
# db.session.add(user_susan)
# db.session.add(user_david)
db.session.add_all([admin_role, mod_role, user_role, user_john, user_susan, user_david])
db.session.commit()
print(admin_role.id) # 1
print(mod_role.id) # 2
print(user_role.id) # 3
# 修改行
print(admin_role.name) # Admin
admin_role.name = 'Administrator'
db.session.add(admin_role)
db.session.commit()
print(admin_role.name) # Administrator
# 删除行
db.session.delete(mod_role)
db.session.commit()
print(mod_role.id) # 2
# 查询行
print(Role.query.all()) # [<Role 'Administrator'>, <Role 'User'>]
print(User.query.all()) # [<User 'john'>, <User 'susan'>, <User 'david'>]
print(User.query.filter_by(role=user_role).all()) # [<User 'susan'>, <User 'david'>]
print(str(User.query.filter_by(role=user_role)))
# SELECT users.id AS users_id, users.username AS users_username, users.role_id AS users_role_id FROM users
users = user_role.users
print(users) # [<User 'susan'>, <User 'david'>]
print(users[0].role) # <Role 'User'>
print(user_role.users.order_by(User.username).all()) # [<User 'david'>, <User 'susan'>]
print(user_role.users.count()) # 2
电子邮件
安装
pip install flask-mail
推荐阅读:Python通过163和QQ收发邮件
异步发邮件
from threading import Thread
from flask import Flask
from flask_mail import Mail, Message
app = Flask(__name__)
app.config['MAIL_SERVER'] = 'smtp.163.com' # 服务器
app.config['MAIL_PORT'] = 465 # 启用SSL发信,端口一般是465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = 'abc@163.com' # 用户名
app.config['MAIL_PASSWORD'] = 'MXHQPFWUFEXCVMOQ' # 授权密码
mail = Mail(app) # 这句不能在app配置初始化前
def send_async_email(app, message):
"""异步发送电子邮件"""
with app.app_context():
mail.send(message)
@app.route('/')
def index():
subject = '关于Python日志库Loguru库的问题请教'
message = Message(
subject,
recipients=['12345678@qq.com'],
html='<h1>请问如何轻松记录日志?</h1>',
sender=app.config['MAIL_USERNAME']
)
# mail.send(message) # 这句会卡几秒,影响用户体验
thr = Thread(target=send_async_email, args=[app, message])
thr.start()
return '发送成功\n'
if __name__ == '__main__':
app.run(debug=True)
生产环境中最好用 Celery
TODO:用户认证
大多数用户在不同的网站中使用相同的密码,想保证数据库中用户密码的安全,不能存储密码本身,而要存储密码的散列值,通常使用散列值加盐的方法。
推荐阅读:
使用 Werkzeug 实现密码散列值加盐
from werkzeug.security import generate_password_hash, check_password_hash
password = '123456'
password_hash = generate_password_hash(password)
print(password_hash)
print(check_password_hash(password_hash, password))
# pbkdf2:sha256:260000$mcvf9pIsr5Dg625I$f9060c13862c0b2570407eb27fba8a59d68b8d56aaf6845d46b64d8788894768
# True
安装
pip install flask-login
pip install email_validator
Flask-Login 要求实现的用户模型方法
方法 | 描述 |
---|---|
is_authenticated() | 用户是否已登录 |
is_active() | 是否允许用户登录 |
is_anonymous() | 普通用户返回 False |
get_id() |
用户认证
from . import db, login_manager
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
username = db.Column(db.String(64), unique=True, index=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
password_hash = db.Column(db.String(128))
@property
def password(self):
raise AttributeError('password is not a readable attribute')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
def __repr__(self):
return '<User %r>' % self.username
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
具体代码
git checkout 8e
大型程序结构
PythonFlask快速入门与进阶
FlaskWeb开发:基于Python的Web应用开发实战
git clone https://github.com/miguelgrinberg/flasky.git
cd flasky
git checkout 7a
阿里巴巴Java开发手册
工程结构部分
cookiecutter-flask
笔者项目结构
前端能对状态码 404 和 500 进行路由,前后端分离的话,应由前端渲染,对应 static 和 templates 文件夹就不需要了
开源项目
配置管理
JSON排序
JSON_SORT_KEYS
默认为 True,自动根据键名排序,能提高缓存性能。
from flask import Flask
app = Flask(__name__)
app.config['JSON_SORT_KEYS'] = False # 禁用JSON自动排序
@app.route('/')
def index():
data = {'b': 5, 'a': 4, 'c': 3}
return data
if __name__ == '__main__':
app.run()
REST Web服务
版本
区分版本很重要,因为无法强制更新手机 APP,可让客户端的参数带上当前版本。
HTTP状态码
状态码 | 名 | 描述 |
---|---|---|
200 | OK | 成功 |
201 | Created | 成功并创建了新资源 |
400 | Bad request | 请求不可用 |
401 | Unauthorized | 未授权 |
403 | Forbidden | 无权访问 |
404 | Not found | 对应资源不存在 |
405 | Method not allowed | 不支持该方法 |
500 | Internal server error | 服务器内部错误 |
404 和 500 状态码可能会让客户端困惑,因此可以在错误处理程序中改写响应,这种技术称为内容协商,发送 JSON 格式响应。
认证
- 基于密令认证:Flask-HTTPAuth,将密令包含在请求的 Authorization 中。
- 基于令牌认证:发送登录密令获取认证令牌,有过期时间,通过令牌代替登录密令。
测试
记录慢查询
- 数据库慢查询日志
- 结合 Flask-SQLAlchemy 和
after_app_request()
将慢查询写入日志,视情况发送邮件(前者获取慢查询,后者发邮件)
部署
- 用 500 错误页面取代 Flask 交互式调试器,即错误栈跟踪,同时记录日志,发送邮件(SMTPHandler)
日志
Python 日志级别由小到大分别是:DEBUG < INFO < WARNING < ERROR < CRITICAL
Flask 默认日志级别同 Python,为 warning()
记录接口请求参数,便于 Postman 请求,此处只解析常见的 mimetype
import json
import logging
from flask.logging import default_handler
from flask import Flask, request, has_request_context
app = Flask(__name__)
app.logger.setLevel(logging.INFO)
class RequestFormatter(logging.Formatter):
def format(self, record):
record.method = ''
record.url = ''
record.request_data = ''
record.cookie = ''
if has_request_context():
record.method = request.method
record.url = request.url
mimetype = request.mimetype
if mimetype == 'multipart/form-data':
request_data = request.form
request_data = '\n'.join(['{}:{}'.format(k, v) for k, v in request_data.items()])
elif mimetype == 'application/x-www-form-urlencoded':
request_data = json.loads(next(iter(request.form.keys())))
request_data = '\n'.join(['{}:{}'.format(k, v) for k, v in request_data.items()])
elif mimetype == 'application/json':
request_data = request.data.decode()
else:
request_data = request.data.decode()
record.request_data = request_data
if record.request_data:
record.request_data = '\n{}\n{}'.format(request.mimetype, record.request_data)
record.cookie = ';'.join(['{}={}'.format(k, v) for k, v in request.cookies.items()])
if record.cookie:
record.cookie = '\nCookie:{}'.format(record.cookie)
return super().format(record)
formatter = RequestFormatter('[%(asctime)s|%(method)s|%(url)s]%(request_data)s%(cookie)s')
default_handler.setFormatter(formatter)
@app.before_request
def before_request():
"""打印请求参数"""
app.logger.info('')
@app.route('/', methods=['GET', 'POST'])
def ping():
return 'pong\n'
if __name__ == '__main__':
app.run()
测试
请求方法 | mimetype | 命令 |
---|---|---|
GET | curl http://127.0.0.1:5000/ | |
GET | curl "http://127.0.0.1:5000/?a=1&b=2" | |
POST | multipart/form-data | curl -X POST "http://127.0.0.1:5000/" -F c=3 -F d=4 |
POST | application/x-www-form-urlencoded | curl -X POST "http://127.0.0.1:5000/" --data "{\"b\":2, \"c\":3}" |
POST | application/json | curl -X POST -H "Content-Type: application/json" "http://127.0.0.1:5000/" --data "{\"b\":2, \"c\":3}" |
POST | text/plain | curl -X POST -H "Content-Type: text/plain" "http://127.0.0.1:5000/" --data "Hello" |
效果
2022-06-30 22:36:25,597|GET|http://127.0.0.1:5000/
2022-06-30 22:36:28,488|GET|http://127.0.0.1:5000/?a=1&b=2
2022-06-30 22:36:31,235|POST|http://127.0.0.1:5000/
multipart/form-data
c:3
d:4
2022-06-30 22:36:33,731|POST|http://127.0.0.1:5000/
application/x-www-form-urlencoded
b:2
c:3
2022-06-30 22:36:36,931|POST|http://127.0.0.1:5000/
application/json
{"b":2, "c":3}
2022-06-30 22:36:40,723|POST|http://127.0.0.1:5000/
text/plain
Hello
普通日志
import logging
logger = logging.getLogger()
def f():
logger.warning('hello')
下载文件
from flask import Flask, send_file
app = Flask(__name__)
@app.route('/download/')
def download():
file = '测试.txt'
with open(file, mode='w') as f:
f.write('Hello World!')
return send_file(file, as_attachment=True)
if __name__ == '__main__':
app.run()
访问 http://127.0.0.1:5000/download/
上传文件
获取参数
POST:request.form.get(key, default=None, type=None)
GET:request.args.get(key, default=None, type=None)
所有:request.values.get(key, default=None, type=None)
重定向
from werkzeug.utils import redirect
性能分析
Python高并发服务部署——Nginx+Gunicorn+gevent+Flask+Supervisor
Flask 扩展
Flask-Bootstrap好看的页面
安装
pip install flask-bootstrap
templates/user.html
{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle"
data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="container">
<div class="page-header">
<h1>Hello, {{ name }}!</h1>
</div>
</div>
{% endblock %}
main.py
from flask import Flask, render_template
from flask_bootstrap import Bootstrap
app = Flask(__name__)
Bootstrap(app)
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
if __name__ == '__main__':
app.run(debug=True)
访问 http://127.0.0.1:5000/user/XerCis
Flask-Moment本地化日期和时间
如果用户来自世界各地,需要统一时间单位,一般使用地理位置无关的协调世界时( Coordinated Universal Time, UTC),在渲染的时候转换为当地时间。
安装
pip install flask-moment
templates/index.html
{{ moment.include_moment() }}
<p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>
main.py
from datetime import datetime
from flask import Flask, render_template
from flask_moment import Moment
app = Flask(__name__)
moment = Moment(app)
@app.route('/')
def index():
return render_template('index.html', current_time=datetime.utcnow())
if __name__ == '__main__':
app.run(debug=True)
Flask-WTF表单验证和渲染
安装
pip install flask-wtf
Flask-SQLAlchemy管理数据库
安装
pip install flask-sqlalchemy
Flask-Migrate迁移数据库
安装
pip install flask-migrate
Flask-Mail电子邮件
安装
pip install flask-mail
Flask-Login用户认证
安装
pip install flask-login
Flask-HTTPAuth接口认证
REST Web 服务要求无状态,服务器再两次请求间不能记住客户端的任何信息。
可通过 HTTP 认证发送密令,将密令包含在请求的 Authorization 中。
安装
pip install flask-httpauth
可结合装饰器 auth.login_required()
和 api.before_request()
进行所有路由的自动认证,甚至能认证角色。
Flask-Talisman安全HTTP
安装
pip install flask-talisman
Flask-RESTful构建RESTAPI
安装
pip install flask-restful
Flask-OAuth第三方登录
Flask-OpenID开放式认证
安装
pip install flask-openid
Flask-WhooshAlchemy全文搜索
安装
pip install flask_whooshalchemy
Flask-Admin后台管理
安装
pip install flask-admin
Flask-Security安全机制
安装
pip install flask-security
Flask-JWT-Extended用户认证
安装
pip install flask-jwt-extended
Flask-Limiter接口频率限制
安装
pip install flask-limiter
Flask-Babel国际化
安装
pip install flask-babel
Flask-Caching缓存
安装
pip install flask-caching
Flask-DebugToolbar调试工具
安装
pip install flask-debugtoolbar
Flask-Redis
安装
pip install flask-redis
Flask-SQLAcodegen自动生成模型
安装
pip install flask-sqlacodegen
Flask-SSE向前端发数据
安装
pip install flask-sse
Flask-SocketIO实时双向通讯
安装
pip install flask-socketio
Flask-Uploads文件上传
安装
pip install flask-uploads
Flask-Dropzone拖拽上传
安装
pip install flask-dropzone
Flask-CKEditor富文本编辑器
安装
pip install flask-ckeditor
Flask-FlatPages代码高亮
安装
pip install flask-flatPages
Flask-Opentracing分布式跟踪
安装
pip install flask-opentracing
Flasgger文档生成
安装
pip install flasgger
更多扩展
自动化部署
自动化测试
日志处理
高级框架
APIFlask
安装
pip install apiflask
代码
from apiflask import APIFlask, Schema, abort
from apiflask.fields import Integer, String
from apiflask.validators import Length, OneOf
app = APIFlask(__name__)
pets = [
{'id': 0, 'name': 'Kitty', 'category': 'cat'},
{'id': 1, 'name': 'Coco', 'category': 'dog'}
]
class PetInSchema(Schema):
"""入参"""
name = String(required=True, validate=Length(0, 10))
category = String(required=True, validate=OneOf(['dog', 'cat']))
class PetOutSchema(Schema):
"""出参"""
id = Integer()
name = String()
category = String()
@app.get('/')
def say_hello():
# 返回dict相当于使用jsonify()
return {'message': 'Hello!'}
@app.get('/pets/<int:pet_id>')
@app.output(PetOutSchema)
def get_pet(pet_id):
if pet_id > len(pets) - 1:
abort(404)
# 也可以直接返回ORM/ODM实例,会自动将对象序列化为JSON格式
return pets[pet_id]
@app.patch('/pets/<int:pet_id>')
@app.input(PetInSchema(partial=True), location='json_or_form')
@app.output(PetOutSchema)
def update_pet(pet_id, data):
# 经过验证和解析的输入数据将作为dict注入视图函数
if pet_id > len(pets) - 1:
abort(404)
for attr, value in data.items():
pets[pet_id][attr] = value
return pets[pet_id]
if __name__ == '__main__':
app.run()
测试
curl http://127.0.0.1:5000/
curl http://127.0.0.1:5000/pets/0
curl -X PATCH http://127.0.0.1:5000/pets/0 --data "{'name':'Butterfly', 'category':'cat'}"
文档
推荐阅读
后台任务
import time
import threading
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
def start_runner():
def start_loop():
while True:
print('执行后台任务...')
time.sleep(3)
thread = threading.Thread(target=start_loop)
thread.start()
if __name__ == '__main__':
start_runner()
app.run()
解决循环引用
Flask+RocketMQ
不建议 Flask 和 RocketMQ 放在一起
消费者
extension.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
model.py
from extension import db
class Record(db.Model):
id = db.Column(db.Integer, primary_key=True)
message_id = db.Column(db.String, unique=True) # 唯一索引用于消费消息幂等性
message_body = db.Column(db.String)
mq.py
import time
import logging
from rocketmq.client import PushConsumer, ConsumeStatus
from model import Record
from extension import db
logger = logging.getLogger()
def callback(message):
"""消费逻辑"""
print(message.id, message.body)
from app import app
with app.app_context():
try:
record = Record(message_id=message.id, message_body=message.body.decode('utf-8'))
db.session.add(record)
db.session.commit()
except Exception as e: # 可能会重复消费
print(e)
db.session.rollback()
n = db.session.query(Record.id).count()
print(f'现在有{n}条记录')
return ConsumeStatus.CONSUME_SUCCESS # 一定要返回
def run():
from uuid import uuid4
logger.warning('开始消费')
consumer = PushConsumer(str(uuid4()))
# consumer = PushConsumer('CID_XXX')
consumer.set_name_server_address('127.0.0.1:9876')
consumer.subscribe('TestTopic', callback)
consumer.start()
while True: # 一定要加这两句,否则Segmentation fault
time.sleep(30)
consumer.shutdown()
app.py
import os
import threading
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from mq import run
from model import Record
from extension import db
if os.path.exists('test.db'):
os.remove('test.db')
def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
db.init_app(app)
return app
app = create_app()
with app.app_context():
db.create_all()
threading.Thread(target=run).start()
@app.route('/')
def ping():
return 'pong\n'
if __name__ == '__main__':
app.run()
遇到的坑
1. The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a list.
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return [1, 2, 3]
if __name__ == '__main__':
app.run()
Flask 不支持返回 list
from flask import jsonify
return jsonify([1, 2, 3])
2. 报错RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
循环引用
请求 url 加上 /
4. 跨域请求下前端获取不到 Response Headers
response.headers['Access-Control-Expose-Headers'] = 'Header名1,Header名2'
Chrome 浏览器访问 chrome://flags/#block-insecure-private-network-requests
Edge 浏览器访问 edge://flags/#block-insecure-private-network-requests
设置 Block insecure private network requests.
为 Disabled
Flask, add Access-Control-Allow-Private-Network true to CORS preflight
该问题本人在测试环境中遇到,将 http 全部换成 https 即可解决
6. 其他系统调用本系统报错514 Frequency capped,后端日志没有收到请求,且Postman可正常请求
找运维帮忙看,可能是域名解析出了问题
参考文献
- Flask Documentation
- Flask 中文文档
- Python Flask快速入门与进阶
- Flask Web 开发:基于 Python 的 Web 应用开发实战
- Flask Web Development GitHub
- Flask-Bootstrap Documentation
- Python数据库ORM框架——SQLAlchemy
- Python通过163和QQ收发邮件
- flask mail ConnectionRefusedError: WinError 10061 由于目标计算机积极拒绝,无法连接。
- 有哪些使用Python和Flask搭建的博客、论坛等开源项目推荐?
- Python Flask request获取参数几种方式
- 请不要把 Flask 和 FastAPI 放到一起比较
- Flask 中最受欢迎的扩展插件
- postman接口测试工具的基本使用
- HelloFlask - Flask 资源集合地
- JavaWeb 分层结构 总结
- 分层明确高度定制化的 Python Flask MVC
- Python:改造flask接口服务提供统一格式的返回数据
- Change Flask logs from INFO to DEBUG
- 玩转Python日志模块(logging)
- Flask request 属性详解
- flask获取post请求参数
- Flask - How do I read the raw body in a POST request
- tenable/flask-logging-demo
- flask踩坑记录:flask的响应结果总是按首字母排序
- 在Flask启动时启动后台任务
- Circular import of db reference using Flask-SQLAlchemy and Blueprints
- 一个Flask应用运行过程剖析
- 跨域资源共享 CORS 详解
- 跨源资源共享(CORS) - HTTP | MDN
- 如何解决Chrome禁止发送不安全的内网网络请求
- CORS跨域请求中Preflight Request
- Flask, add Access-Control-Allow-Private-Network true to CORS preflight
- How to fix a website with blocked mixed content
- 后端工程师需要了解的跨域知识
- 浏览器踩坑:浏览器访问已发布服务器的网址显示跨域,但微信浏览器可以正常访问,别人的浏览器也可以正常访问