Flask基础
一 概述
Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
“微”(micro) 并不表示我们需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。
默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用。
补充--Flask和Django的比较:
Django:无socket,依赖第三方模块wsgi,中间件,路由系统(CBV,FBV),视图函数,ORM,cookie,session,Admin,Form,缓存,信号,序列化
Flask:无socket,中间件(扩展),路由系统,视图(CBV),第三方模块(依赖jinja2),cookie,session功能比较差
二 入门
2.1 安装
pip3 install flask
2.2 werkzeug模块
# Flask依赖一个实现wsgi协议的模块:werkzeug
from werkzeug.wrappers import Request, Response
@Request.application
def hello(request):
return Response('Hello World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, hello)
2.3 基本使用
from flask import Flask
app = Flask(__name__) # 名称可以自定义,一般写__name__
@app.route('/')
def hello():
return 'Hello World!'
if __name__ == '__main__':
app.run()
提醒:实例化Flask对象,里面是有参数的
app = Flask(__name__,template_folder='templates',static_url_path='/xxxxxx')
为什么是__name__?
这样两种方式调用本模块时都能使代码运行
1.直接运行本模块时,__name__的值为__main__;
2.通过其他模块调用时,__name__的值为本模块的名称
2.4 实例
from flask import Flask,render_template,request,redirect,session app = Flask(__name__) app.secret_key = "abdfadaff" # 加盐 @app.route('/login',methods=["GET","POST"]) def login(): if request.method == "GET": return render_template('login.html') # request.args # 对应Django中request.get() # request.form # 对应Django中request.post() user = request.form.get("user") pwd = request.form.get("pwd") if user == 'joe' and pwd =='123': session['user'] = user # session.pop('user') 删除session return redirect("/index") return render_template('login.html',error="用户名或密码错误!!!") # return render_template('login.html',**{"error":"用户名或密码错误!!!"}) @app.route('/index') def index(): user = session.get('user') if not user: return redirect('/login') return render_template('index.html') if __name__ == '__main__': app.run()
注意:保存session的数据存到了浏览器上:
- 优点:减轻了服务端的压力
- 缺点:不安全
2.5 常见异常
1. POST提交数据时报错:

默认访问方式为GET,当通过POST方式提交数据时,会报错。解决方法:
@app.route("/login",methods=["GET","POST"])
2.设置session时报错:

解决方法:设置秘钥
app.secret_key = "abdfadaff"
三 配置文件
3.1 默认配置
flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为:
{
'DEBUG': get_debug_flag(default=False), 是否开启Debug模式
'TESTING': False, 是否开启测试模式
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
'USE_X_SENDFILE': False,
'LOGGER_NAME': None,
'LOGGER_HANDLER_POLICY': 'always',
'SERVER_NAME': None,
'APPLICATION_ROOT': None,
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False,
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True,
'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None,
}
3.2 修改方式
# 方式一:
app.config['DEBUG'] = True
PS:由于Config对象本质上是字典,所以还可以使用app.config.update(...)
# 方式二:
app.config.from_pyfile("python文件名称")
如:
settings.py
DEBUG = True
app.config.from_pyfile("settings.py")
app.config.from_envvar("环境变量名称") # 我们可以通过os.environ.get(‘环境变量名称’)获取环境变量的值
环境变量的值为python文件名称,内部调用from_pyfile方法
app.config.from_json("json文件名称")
JSON文件名称,必须是json格式,因为内部会执行json.loads
app.config.from_mapping({'DEBUG':True})
字典格式
app.config.from_object("python类或类的路径")
app.config.from_object('pro_flask.settings.TestingConfig')
常见配置文件
# settings.py文件 class Config(object): DEBUG = False TESTING = False DATABASE_URI = 'sqlite://:memory:' class ProductionConfig(Config): DATABASE_URI = 'mysql://user@localhost/foo' class DevelopmentConfig(Config): DEBUG = True class TestingConfig(Config): TESTING = True PS: 从sys.path中已经存在路径开始写 PS: settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录
import os class Config(object): XXX = ooo @staticmethod def init_app(app): pass class DevelopmentConfig(Config): DEBUG = True DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or '指定数据库URL' class TestingConfig(Config): TESTING = True DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or '指定数据库URL' class ProductionConfig(Config): SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or '指定数据库URL' config = { 'development': DevelopmentConfig, 'testing': TestingConfig, 'production': ProductionConfig, 'default': DevelopmentConfig } # 使用 def create_app(config_name): app = Flask(__name__) app.config.from_object(config[config_name]) config[config_name].init_app(app) return app
知识点回顾--指定路径找到相应的类
import importlib path = "settings.Foo" p,c = path.rsplit('.',maxsplit=1) m = importlib.import_module(p) cls = getattr(m,c) # 获取到类
四 路由系统
4.1 路由传参
@app.route('/user/<username>') # 不指定类型默认是字符串形式
@app.route('/post/<int:post_id>')
@app.route('/post/<float:post_id>')
@app.route('/post/<path:path>')
@app.route('/login', methods=['GET', 'POST'])
常用路由系统有以上五种,所有的路由系统都是基于以下对应关系来处理:
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
4.2 反向生成URL:url_for
from flask import Flask, url_for
@app.route('/index',endpoint='n1') # endpoint:类似Django中的name,不写则默认为函数名,我们可以从源码中找到原因
def index():
print(url_for('n1')) # url_for反向生成url,结果为:/index,有参数加nid
return 'Index'
@app.route('/func1')
def func1():
print(url_for('func1')) # /func1
return 'Func1'
@app.route('/func2/<int:nid>')
def func2(nid):
print(url_for('func2',nid=nid)) # /func2/nid
return 'Func2'
4.3 设置路由的两种方式
#方式一(推荐使用)
@app.route('/xxx')
def index():
return "Index"
#方式二
def index():
return "Index"
app.add_url_rule('/xxx', None, index) # None代表别名为None
为什么存在方式二?我们先看一下app.route()函数源码:
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
分析方式一执行步骤(原理):
- 先执行decorator = app.route('/index')
- 实际上给index函数加了一个装饰器:@decorator
- 再执行decorator(index),实际执行的就是self.add_url_rule(rule, endpoint, f, **options)。self—>app;rule—>/xxx;endpoint—>None;f—>index
注意事项:
- 不用让endpoint重名
- 如果endpoint重名,函数也一定要相同
def auth(func): def inner(*args, **kwargs): print('before') result = func(*args, **kwargs) print('after') return result return inner @app.route('/index.html',methods=['GET','POST'],endpoint='index') @auth def index(): return 'Index' 或 def index(): return "Index" self.add_url_rule(rule='/index.html', endpoint="index", view_func=index, methods=["GET","POST"]) or app.add_url_rule(rule='/index.html', endpoint="index", view_func=index, methods=["GET","POST"]) app.view_functions['index'] = index 或 def auth(func): def inner(*args, **kwargs): print('before') result = func(*args, **kwargs) print('after') return result return inner class IndexView(views.View): methods = ['GET'] decorators = [auth, ] def dispatch_request(self): print('Index') return 'Index!' app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint 或 class IndexView(views.MethodView): methods = ['GET'] decorators = [auth, ] def get(self): return 'Index.GET' def post(self): return 'Index.POST' app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint
4.4 @app.route和app.add_url_rule参数
rule, URL规则
view_func, 视图函数名称
defaults=None, 默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数
endpoint=None, 名称,用于反向生成URL,即: url_for('名称')
methods=None, 允许的请求方式,如:["GET","POST"]
strict_slashes=None, 对URL最后的 / 符号是否严格要求,
如:
@app.route('/index',strict_slashes=False),
访问 http://www.xx.com/index/ 或 http://www.xx.com/index均可
@app.route('/index',strict_slashes=True)
仅访问 http://www.xx.com/index
redirect_to=None, 重定向到指定地址
如:
@app.route('/index/<int:nid>', redirect_to='/home/<nid>')
或
def func(adapter, nid):
return "/home/888"
@app.route('/index/<int:nid>', redirect_to=func)
subdomain=None, 子域名访问
from flask import Flask, views, url_for
app = Flask(import_name=__name__)
app.config['SERVER_NAME'] = 'joe1991.com:5000' # 必须添加
@app.route("/", subdomain="admin")
def static_index():
"""Flask supports static subdomains
This is available at static.your-domain.tld"""
return "admin.xxx.com"
#动态生成
@app.route("/dynamic", subdomain="<username>")
def username_index(username):
"""Dynamic subdomains are also supported
Try going to user1.your-domain.tld/dynamic"""
return username + ".your-domain.tld"
if __name__ == '__main__':
app.run()
所有的域名都得与IP做一个域名解析:
如果想通过域名去访问,有两种解决方式:
方式一:
1、租一个域名 joe1991.com
2、租一个公网IP 127.xxx.xxx.xxx
3、域名解析 joe1991.com 127.xxx.xxx.xxx
4、把代码放在127.xxx.xxx.xxx这个服务器上,程序运行起来用户可以通过IP进行访问
方式二:如果是自己测试用的就可以用这种方式。先在自己本地的文件中找
C:\Windows\System32\drivers\etc 找到hosts,修改配置
然后把域名修改成自己的本地服务器127.0.0.1
加上配置:app.config["SERVER_NAME"] = "joe1991.com:5000"
from flask import Flask, views, url_for app = Flask(__name__) app.config['SERVER_NAME'] = 'joe1991.com:5000' # 必须进行设置 @app.route('/') def index(): return 'Index' # 静态子域名 @app.route("/", subdomain="admin") def static_domain(): return "Static Domain" # 动态子域名 @app.route("/dynamic", subdomain="<subdomain>") def dynamic_subdomain(subdomain): return subdomain + ".domain" if __name__ == '__main__': app.run() # C:\Windows\System32\drivers\etc\hosts文件添加上以下设置 127.0.0.1 joe1991.com 127.0.0.1 admin.joe1991.com
访问效果:
4.5 自定制正则路由匹配
from flask import Flask,url_for from werkzeug.routing import BaseConverter app = Flask(import_name=__name__) # 步骤一:定制类 class RegexConverter(BaseConverter): # 必须继承BaseConverter """ 自定义URL匹配正则表达式 """ def __init__(self, map, regex): super(RegexConverter, self).__init__(map) self.regex = regex def to_python(self, value): """ 路由匹配时,匹配成功后传递给视图函数中参数的值 :param value: :return: """ return int(value) def to_url(self, value): """ 使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数 :param value: :return: """ val = super(RegexConverter, self).to_url(value) return val # 步骤二:添加到转换器 app.url_map.converters['reg'] = RegexConverter """ 1. 用户发送请求 2. flask内部进行正则匹配 3. 调用to_python(正则匹配的结果)方法 4. to_python方法的返回值会交给视图函数的参数 5. 如果需要反向生成,再执行to_url()方法 """ # 步骤三:使用自定义正则 @app.route('/index/<reg("\d+"):nid>',endpoint='xxx') # <reg("\d+")可以指定参数类型 def index(nid): print(url_for('xxx', nid=nid)) return 'Index' if __name__ == '__main__': app.run()
4.6 其他补充
添加路由关系的本质:将url和视图函数封装成一个Rule对象,添加到Flask的url_map字段中
五 视图
Flask中的视图也存在FBV和CBV两种方式。
5.1 CBV
我们先简单回顾以下Django中的CBV模式:
# urls.py
url(r^login_cvb/$, views.Login_cbv.as_view()), # Login_cbv是类名,必须在后面调用as_views()
# views.py
from django.views import View
class Login_cbv(View):
def get(self, request): # get请求执行代码
return render(request,'login_cbv.html')
def post(self, request): # post请求执行代码
return HttpReSponse('...')
def delete(self,request):
pass
Flask中的CBV与其类似,具体如下:
import functools
from flask import Flask,views,url_for
app = Flask(__name__)
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
print('before')
result = func(*args, **kwargs)
print('after')
return result
return inner
class IndexView(views.MethodView):
# methods = ['GET'] # 只允许GET请求访问
decorators = [auth, ] # 如果想给所有的请求加装饰器,就可以这样来写,也可以单个指定
def get(self,*args,**kwargs): # 如果是get请求需要执行的代码
v = url_for('xxx')
print(v)
return "GET"
def post(self,*args,**kwargs): # 如果是post请求执行的代码
return "POST"
app.add_url_rule('/index',None, view_func=IndexView.as_view(name='xxx')) # name指定的是别名,会当做endpoint使用
if __name__ == '__main__':
app.run()
5.2 FBV
参考第四节
六 请求和响应
from flask import Flask from flask import request from flask import render_template from flask import redirect from flask import jsonify from flask import make_response app = Flask(__name__) @app.route('/index.html', methods=['GET', "POST"]) def index(): # 请求相关信息 # request.method # HTTP请求方法,例如:GET或POST # request.args # GET请求,字典形式,存储通过URL查询字符串传递的所有参数 request.querystring这个也是GET请求,bytes形式 # request.form # POST请求,字典形式,存储请求提交的所有表单字段 # request.values # 字典形式,form和args的合集 # request.cookies # 字典形式,存储请求的所有cookie # request.headers # 字典形式,存储请求的所有HTTP首部 # request.path # URL的路径部分 http://localhost/app/index.html?x=y返回/index.html # request.full_path # URL的路径和查询字符串部分 http://localhost/app/index.html?x=y返回/index.html?x=y # request.script_root # http://localhost/app/index.html?x=y返回/app # request.url # 请求的完整URL http://localhost/app/index.html?x=y返回http://localhost/app/index.html?x=y # request.base_url # 同url,但没有查询字符串部分 # request.url_root # 同host_url # request.host_url # http://127.0.0.1:5000/index?x=y返回http://127.0.0.1:5000/ # request.host # 请求定义的主机名,如果客户端定义了的端口号,还包括端口号 http://127.0.0.1:5000/index?x=y返回127.0.0.1:5000 # request.files # 字典形式,存储请求上传的所有文件 # obj = request.files['the_file_name'] # obj.save('/var/www/uploads/' + secure_filename(f.filename)) # 响应相关信息 # 响应体 # return "字符串" # return render_template('html模板路径',**{}) # return redirect('/index.html') # return jsonify({'k1': 'v1'}) 相当于Django中的JsonResponse # 定制响应头 # response = make_response("字符串") # response是flask.wrappers.Response类型 # response.delete_cookie('key') # response.set_cookie('key', 'value') # response.headers['X-Something'] = 'value' # return response if __name__ == '__main__': app.run()
补充1:错误处理
如果仅仅想终止一个请求并返回错误,而不是重定向到其他地址,我们可以使用abort()函数。
from flask import Flask,abort app = Flask(__name__) @app.route("/") def index(): abort(403)
补充2:自定义错误页面
from flask import Flask,abort app = Flask(__name__) @app.route("/") def index(): abort(404) # errorhandler装饰器添加了自定义的错误处理器,当程序中返回404错误时,系统会自动执行page_not_found()函数。 @app.errorhandler(404) def page_not_found(e): # 必须接受参数,错误信息 return render_template('404.html'), 404
补充说明:如果使用了蓝图,则装饰器为@蓝图名称.app_errorhandler(xxx)
七 模板
模板是包含响应文本的文件,其中包含用占位变量表示的动态部分,其具体值只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程被称为渲染。为了渲染模板,Flask使用一个名为Jinja2的强大模板引擎。
7.1 渲染模板
hello.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)
默认情况下,Flask在应用目录中的templates子目录里寻找模板,所以我们得新建一个templates子目录,然后在里面定义模板文件。
Jinja2模板:index.html
<h1>Hello World!</h1>
Jinja2模板:user.html
<h1>Hello {{ name }}!</h1>
视图函数user()返回的响应包含一个使用变量表示的动态部分,user.html使用模板实现了这个响应。
Flask提供的render_template()函数把Jinja2模板引擎集成到了应用中。这个函数的第一个参数是模板的文件名,随后的参数都是键值对,表示模板中的变量对应的具体值。
上例中的name=name是经常使用的关键字参数,左边的name表示参数名,就是模板中使用的占位符;右边的name是当前作用域中的变量,表示同名参数的值。两侧使用相同的变量名很常见,但不是强制要求。
7.2 变量
在上面user.html模板中{{ name }}结构表示一个变量,这是一种特使的占位符,告诉模板引擎这个位置的值从渲染模板时使用的数据中获取。
Jinja2能识别所有类型的变量,甚至一些复杂的类型,比如:列表、字典和对象。可以执行Python语法,如:dict.get(),dict[‘xx’], list[index]。简单示例如下:
#后端 def index(): context = { 'l1':['joe1', 'joe2', 'joe3'], 'd1':{'name': 'joe', 'age': 18}, } return render_template('index.html', **context) #前端index.html {{l1.0}} {{l1[0]}} {{d1.name}} {{d1.get('name')}} {{d1['name']}}
变量的值可以使用过滤器修改。过滤器添加在变量名之后,二者之间用竖线分割。例如:需要将name变量的值变成首字母大写的形式:
<h1>Hello {{ name|capitalize }}!</h1>
Jinja2常见过滤器列表如下:
|
过滤器名 |
说明 |
|
safe |
渲染值时不转义 |
|
capitalize |
把值的首字母转换为大写,其他字母转换为小写 |
|
lower |
把值转换为小写形式 |
|
upper |
把值转换为大写形式 |
|
title |
把值中每个单词的首字母转换为大写 |
|
trim |
把值的首尾空格删掉 |
|
striptags |
渲染之前把值的所有的HTML标签都删掉 |
7.3 控制结构
Jinja2提供了多种控制结构,可以用来改变模板的渲染流程。简单介绍入下:
7.3.1 条件判断语句
{% if user %}
Hello,{{ name }}!
{% else %}
Hello,Stranger!
{% endif %}
7.3.2 for循环
<ul>
{% for comment in comments %}
<li{{ comment }}></li>
{% endfor %}
</ul>
7.3.3 宏
宏类似于Python代码中的函数。应用场景:定义的东西需要在很多地方使用时。例如:
{% macro render_comment(comment) %} <!--render_comment指宏名,可以自定义-->
<li>{{ comment }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_comment(comment) }}>
{% endfor %}
</ul>
为了重复使用宏,可以把宏保存在单独的文件中,然后在需要使用的模板中导入:
{% import 'macros.html' as macros %}
<ul>
{% for comment in comments %}
{{ macros.render_comment(comment) }}>
{% endfor %}
</ul>
7.4 include
需要在多处重复使用的模板代码片段可以写入单独的文件,再引入所有模板中,以避免重复,这时我们需要使用include。
# form.html <form> asdfasdf asdfasdf asdf asdf </form> # index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {{l1.0}} {{l1[0]}} {{d1.name}} {{d1.get('name')}} {{d1['name']}} {% include "form.html" %} </body> </html>
7.5 模板继承
处理Include外,还有一种重复使用代码的强大方式——模板继承。
创建一个基模板:base.html
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - My Application</title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
基模板中定义的区块可在衍生模板中覆盖。jinja2使用block和endblock指令在基模板中定义内容区块。在本示例中,我们定义了head、title和body区块。其中,title包含在head内。下面展示基模板的衍生模板:
{% extends "base.html" %}
{% block title %}
Index
{% endblock %}
{% block head %}
{{ super() }}
<style></style>
{% endblock %}
{% block body %}
<h1>Hello World!</h1>
{% endblock %}
extends指令声明这个模板衍生自base.html。在extends指令之后,基模板中的3个区块被重新定义,模板引擎会将其插入适当的位置。如果基模板和衍生模板中的同名区块中都有内容,衍生模板中的内容将显示出来。在衍生模板的区块里可以调用super(),引用基模板中同名区块中的内容。
7.6 自定义方法
#后端 def func(a,b): return a+b @app.route('/index') def index(): return render_template("index.html",func=func) #前端index.html <h1>{{ func(1,2)}}</h1>
Flask和Django中传入函数的区别:
- Django,自动执行
- Flask,不自动执行
7.7 全局函数(template_global和template_filter)
@app.template_global()
def func1(a1, a2):
return a1 + a2
@app.template_filter()
def func2(a1, a2, a3):
return a1 + a2 + a3
#调用方式:{{func1(1,2)}} {{ 1|func2(2,3)}}
补充:可用于if判断
7.8 安全
为了防止xss攻击,有以下两种解决办法:
#后端解决
from flask import Markup
t1 = Markup("<input type='text' />")
{{t1}}
#前端解决
t2 = "<input type='text'/>",
{{ t2|safe }}
注意:Markup等价django的mark_safe
简单说明:向前端传递的不仅可以是单纯的字符串,还可以包含HTML特殊字符(比如:<、>、空格、/等),这给模板参数提供了更好的灵活性。同时,由于这些特殊字符会被HTML客户端解释成特殊含义,因此会给网站带来一定程度的安全隐患。Flask允许我们自己控制jinja2是否需要解释这些特殊字符。如果这些字符应该被解释成特殊含义,则将这些参数直接传递给前端,如果这些参数应该被解释成字符串,则应该通过Markup()函数将这些字符串做转义处理,再传递给前端。
八 Session
除请求对象之外,还有一个 session 对象。它允许在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,需要设置一个密钥。
- 设置:session['username'] = 'xxx'
- 删除:session.pop('username', None)
from flask import Flask,url_for,session
app = Flask(__name__)
app.secret_key = "abcdefg" # 重点
@app.route('/ses')
def ses():
# session本质上操作的是字典。session是否还有其他方法?答案是与字典方法相同
# ession的原理:当请求到来时,flask读取cookie中session对应的值:xxx,将该值解密并反序列化成字典,放入内存以便视图函数使用
# 假设session保存在数据库,每执行一次链接一次数据库,每次都要时时更新的话会非常损耗内存
print(session) # <SecureCookieSession {}>
print(type(session)) # <class 'werkzeug.local.LocalProxy'>
session["k1"] = 123
session["k2"] = 456
session["k3"] = 789
del session["k2"] # 在这删除后,真正存储的时候是没有k2的
return "Session"
# 当请求结束时,flask会读取内存中字典的值,进行序列化+加密,写入到用户cookie中。
if __name__ == '__main__':
app.run()
原理简图如下:
关于session的配置
'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_REFRESH_EACH_REQUEST': True, #是否每次访问更新超时时间 'PERMANENT_SESSION_LIFETIME': timedelta(days=31) # 超时时间
from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) @app.route('/') def index(): if 'username' in session: 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['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(): # remove the username from the session if it's there session.pop('username', None) return redirect(url_for('index')) # set the secret key. keep this really secret: app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
pip3 install Flask-Session
run.py
from flask import Flask
from flask import session
from pro_flask.utils.session import MySessionInterface
app = Flask(__name__)
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
app.session_interface = MySessionInterface()
@app.route('/login.html', methods=['GET', "POST"])
def login():
print(session)
session['user1'] = 'alex'
session['user2'] = 'alex'
del session['user2']
return "内容"
if __name__ == '__main__':
app.run()
session.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import uuid
import json
from flask.sessions import SessionInterface
from flask.sessions import SessionMixin
from itsdangerous import Signer, BadSignature, want_bytes
class MySession(dict, SessionMixin):
def __init__(self, initial=None, sid=None):
self.sid = sid
self.initial = initial
super(MySession, self).__init__(initial or ())
def __setitem__(self, key, value):
super(MySession, self).__setitem__(key, value)
def __getitem__(self, item):
return super(MySession, self).__getitem__(item)
def __delitem__(self, key):
super(MySession, self).__delitem__(key)
class MySessionInterface(SessionInterface):
session_class = MySession
container = {}
def __init__(self):
import redis
self.redis = redis.Redis()
def _generate_sid(self):
return str(uuid.uuid4())
def _get_signer(self, app):
if not app.secret_key:
return None
return Signer(app.secret_key, salt='flask-session',
key_derivation='hmac')
def open_session(self, app, request):
"""
程序刚启动时执行,需要返回一个session对象
"""
sid = request.cookies.get(app.session_cookie_name)
if not sid:
sid = self._generate_sid()
return self.session_class(sid=sid)
signer = self._get_signer(app)
try:
sid_as_bytes = signer.unsign(sid)
sid = sid_as_bytes.decode()
except BadSignature:
sid = self._generate_sid()
return self.session_class(sid=sid)
# session保存在redis中
# val = self.redis.get(sid)
# session保存在内存中
val = self.container.get(sid)
if val is not None:
try:
data = json.loads(val)
return self.session_class(data, sid=sid)
except:
return self.session_class(sid=sid)
return self.session_class(sid=sid)
def save_session(self, app, session, response):
"""
程序结束前执行,可以保存session中所有的值
如:
保存到resit
写入到用户cookie
"""
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)
val = json.dumps(dict(session))
# session保存在redis中
# self.redis.setex(name=session.sid, value=val, time=app.permanent_session_lifetime)
# session保存在内存中
self.container.setdefault(session.sid, val)
session_id = self._get_signer(app).sign(want_bytes(session.sid))
response.set_cookie(app.session_cookie_name, session_id,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)
#!/usr/bin/env python # -*- coding:utf-8 -*- """ pip3 install redis pip3 install flask-session """ from flask import Flask, session, redirect from flask.ext.session import Session app = Flask(__name__) app.debug = True app.secret_key = 'asdfasdfasd' app.config['SESSION_TYPE'] = 'redis' from redis import Redis app.config['SESSION_REDIS'] = Redis(host='192.168.0.94',port='6379') Session(app) @app.route('/login') def login(): session['username'] = 'alex' return redirect('/index') @app.route('/index') def index(): name = session['username'] return name if __name__ == '__main__': app.run()
九 闪现(flash)
session存在在一个字典里面,session保存起来,取一次里面还是有的,直到删除之后才没有。
闪现flash的本质:flash是基于session创建的,在session中存储一个数据,读取时通过pop将数据移除,也就是说,只能取一次值。
仅调用flash()函数并不能把消息显示出来,必须要结合get_flash_message()函数。
from flask import Flask, flash, get_flashed_messages app = Flask(__name__) app.secret_key = 'abcdefg' # 重要,否则报错 @app.route('/page1') def page1(): flash('临时数据存储') return 'Session' @app.route('/page2') def page2(): print(get_flashed_messages()) return 'Session' if __name__ == '__main__': app.run()
from flask import Flask, flash, get_flashed_messages app = Flask(__name__) app.secret_key = 'abcdefg' # 重要,否则报错 @app.route('/page1') def page1(): flash('xxxxx',category='xxx') flash('xxxxxxx',category='xxx') flash('ooooo',category='ooo') return 'Session' @app.route('/page2') def page2(): print(get_flashed_messages(category_filter=['xxx'])) # ['xxxxx', 'xxxxxxx'] return 'Session' if __name__ == '__main__': app.run()
# test.py from flask import Flask, flash, get_flashed_messages,render_template,url_for app = Flask(__name__) app.secret_key = 'abcdefg' # 重要,否则报错 @app.route('/page1') def page1(): flash('xxxxx',category='xxx') flash('xxxxxxx',category='xxx') flash('ooooo',category='ooo') return render_template('test.html') if __name__ == '__main__': app.run() # test.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>flash测试</h1> {% for message in get_flashed_messages() %} {# {% for message in get_flashed_messages(category_filter=['xxx']) %}#} <P>{{ message }}</P> {% endfor %} </body> </html>
补充说明:我们最好在基模板中渲染闪现消息,这样所有页面都能显示需要闪现的消息。
十 中间件
首先,我们知Flask的运行基于werkzeug模块中的run_simple()。先看一个简单的例子:
from flask import Flask
app = Flask(__name__)
@app.route('/index')
def index():
print('index')
return 'Index'
if __name__ == '__main__':
app.run() # 实际执行run_simple(host, port, self, **options) self指当前的Flask对象(即为app),最终执行的该对象的__call__方法(用户端访问时,第三个参数加括号运行)
# __call__方法什么时候执行?
# 当有用户访问时执行,实际上执行的是:
'''
def __call__(self, environ, start_response):
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app` which can be
wrapped to applying middleware."""
return self.wsgi_app(environ, start_response)
'''
# 所以程序的开始是wsgi_app()
所以,如果我们需要在执行__call__方法前后加入其它操作,我们可以修改源码中的__call__方法:
def __call__(self, environ, start_response):
print('before')
ret = self.wsgi_app(environ, start_response)
print('after')
return ret
但是不建议这么做,我们可以这样做:
from flask import Flask
app = Flask(__name__)
@app.route('/index')
def index():
print('index')
return 'Index'
class Middleware(object):
def __init__(self,old):
self.old = old
def __call__(self, *args, **kwargs): # 参数本质上就是environ,start_response
print('before')
ret = self.old(*args, **kwargs)
print('after')
return ret
if __name__ == '__main__':
app.wsgi_app = Middleware(app.wsgi_app)
app.run() # 依旧先执行__call__()方法,再执行app.wsgi_app(),但此时的app.wsgi_app=Middleware(app.wsgi_app),即是执行Middleware(app.wsgi_app)(),实则执行Middleware类中的__call__方法,当然得先实例化。
最后说明一点:Flask中的中间件和Django中的中间件是完全不同的。
十一 扩展(特殊的装饰器)
11.1 before_request&after_request(***)
from flask import Flask app = Flask(__name__) @app.before_request # 先定义,先执行 def before1(): print('before1') # return '在执行第一个before_request返回' @app.before_request def before2(): print('before2') @app.after_request # 先定义,后执行 def after1(response): # 有参数,且必须返回值 print('after1') return response @app.after_request def after2(response): # 有参数,且必须返回值 print('after2') return response @app.route('/index') def index(): print('index') return 'Index' if __name__ == '__main__': app.run()
上述代码执行顺序为:before1—>before2—>index—>after2—>after1
当在执行before1时出现return,执行效果为:before1—>after2—>after1
重点说明:这个功能才是类似Django中的中间件,图示一下Django与Flask的执行:
11.2 template_global&template_filter
请参考:7.7 全局函数
11.3 before_first_request
from flask import Flask app = Flask(__name__) @app.before_first_request # 当程序运行起来,第一次请求来的时候执行,只执行一次 def x1(): print('before_first_request1') @app.route('/index') def index(): print('index') return 'Index' if __name__ == '__main__': app.run()
11.4 errorhandler
请参考:第六节补充二
十二 其他
12.1 实现验证用户是否登录的几种方式
方式一:普通方式
@app.route('/index')
def index():
if not session.get('user'):
return redirect(url_for('login'))
return render_template('index.html')
方式二:装饰器方式
import functools
def auth(func):
@functools.wraps(func) # 不加该语句,会报错,因为名称重合
def inner(*args,**kwargs):
if not session.get('user'):
return redirect(url_for('login'))
ret = func(*args,**kwargs)
return ret
return inner
@app.route('/index')
@auth
def index():
return render_template('index.html')
应用场景:比较少的函数中需要额外添加功能
版本三:before_request方式
@app.before_request
def xxx():
if request.path == '/login':
return None # 代表通过
if session.get('user'):
return None
return redirect('/login')
12.2 wsgi补充
wsgi即为web服务网关接口,它就是一个协议,目前接触到的实现该协议的模块:
- wsgiref
- werkzeug
实现该协议模块的本质上就是socket服务端用于接收用户请求,并处理。一般web框架基于wsgi实现,这样可以很方便的实现关注点分离(Tornado除外)。
from wsgiref.simple_server import make_server def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8000, run_server) # 请求一进来,run_server函数加()运行 httpd.serve_forever()
from werkzeug.wrappers import Response from werkzeug.serving import run_simple def run_server(environ, start_response): response = Response('hello') return response(environ, start_response) #必须作为对象返回 if __name__ == '__main__': run_simple('127.0.0.1', 8000, run_server) # 当客户端访问时,run_server加括号运行
from werkzeug.wrappers import Response from werkzeug.serving import run_simple class Flask(object): def __call__(self,environ, start_response): response = Response('hello') return response(environ, start_response) def run(self): run_simple('127.0.0.1', 8000, self) # self指app这个对象,加()运行,即执行Flask中的__call__方法 app = Flask() if __name__ == '__main__': app.run() # 执行run()
12.3 静态文件
Web应用不是仅由Python代码和模板组成。多数应用还会使用静态文件,例如:模板中HTML代码应用的图像、JavaScript源码文件和CSS。示例如下:
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon"> <!--结果是:http://localhost:5000/static/favicon.ico-->
<!--<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/styles.css') }}">-->
<link rel="stylesheet" type="text/css" href="/static/css/styles.css/">
十三 Flask插件
- WTForms
- SQLAchemy
- 更多可参考 http://flask.pocoo.org/extensions/




浙公网安备 33010602011771号