flask官方文档总结
唯一的 URL / 重定向行为
@app.route('/projects/')
def projects():
return 'The project page'
@app.route('/about')
def about():
return 'The about page'
projects 的 URL 是中规中矩的,尾部有一个斜杠,看起来就如同一个文件夹。 访问一个没有斜杠结尾的 URL 时 Flask 会自动进行重定向,帮你在尾部加上一个斜杠。
about 的 URL 没有尾部斜杠,因此其行为表现与一个文件类似。如果访问这个 URL 时添加了尾部斜杠就会得到一个 404 错误。这样可以保持 URL 唯一,并帮助 搜索引擎避免重复索引同一页面。
url_for路由解析
@app.route('/', methods=['GET'])
def index():
# url_for 路由解析 第一个参数可以是 想跳转的函数的名字
# return redirect(url_for('func'))
return redirect(url_for('func',username='耿泽世'))
@app.route('/index/<username>', methods=['GET'])
def func(username):
print(username)
return '恭喜'
-----------------------------------------------------------------------------------------
@app.route('/', methods=['GET'])
def index():
# url_for 路由解析 第一个参数可以是 想跳转的函数的名字
# return redirect(url_for('func'))
return redirect(url_for('func', next='nihoa'))
@app.route('/index/', methods=['GET'])
def func():
name = request.args.get('next')
print(name)
return '恭喜'
flask的请求方法
from flask import request
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()
静态文件
#在项目下创建一个static的文件夹,静态文件回去里面找,在Flask实例的是后参数里面是默认的
这个参数是配置静态文件的参数 static_folder 在Flask的参数中
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
#html中的代码
</head>
<img src="/static/1.png" alt="">
</html>
#开启服务之后可以访问到图片
渲染模板
#建模板文件夹有两种方式
#Flask 会在 templates 文件夹内寻找模板。因此,如果你的应用是一个模块, 那么模板文件夹应该在模块旁边;如果是一个包,那么就应该在包里面
#情形1
/application.py
/templates
/hello.html
#情形2
/application
/__init__.py
/templates
/hello.html
{% for item in navigation %}
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
{% endfor %}
{{ foo.bar }}
{{ foo['bar'] }}
{% if True %}
yay
{% endif %}
{%+ if something %}yay{% endif %}
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>
#模块
{% block sidebar %}
<h3>Table Of Contents</h3>
...
{{ super() }}
{% endblock %}
#后端返回html代码是被包裹在Markup()中浏览器会识别html代码并且执行
flask中request是如何实现全局独立的
在介绍flask中的request之前,先简单介绍下python中的ThreadLocal对象。ThreadLocal,故名思义,就是线程中的全局变量,最近才接触这个概念,之前在C++和C#中还没遇到过这个东西。首先看下下面的代码:
import threading
localobj=threading.local()
def threadfunc(name):
localobj.name=name
print 'localobj.name is %s' %name
if __name__=='__main__':
t1=threading.Thread(target=threadfunc,args=('Hyman',))
t2=threading.Thread(target=threadfunc,args=('liuzhihui',))
t1.start()
t2.start()
t1.join()
t2.join()
localobj就是一个Threadlocal对象,他对于每个线程来说是全局的,但是对于不同线程之间又可以保持不同。而flask中的请求对象request就由是这类对象。在多线程服务器中客户端每建立一个链接,服务器就创建一个线程,每个线程中就会有一个request来表示客户端的链接请求信息。
from flask import Flask
from flask import request
app=Flask(__name__)
@app.route('/')
def index():
user_agent=request.headers.get('User_Agent')
return 'user_agent is %s' %user_agent
if __name__=='__main__':
app.run()
请求对象
from flask import request
通过使用 method 属性可以操作当前请求方法,通过使用 form 属性处理表单数据(在 POST 或者 PUT 请求 中传输的数据)。以下是使用上述两个属性的例子:
@app.route('/login', methods=['POST', 'GET'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'],
request.form['password']):
return log_the_user_in(request.form['username'])
else:
error = 'Invalid username/password'
# the code below is executed if the request method
# was GET or the credentials were invalid
return render_template('login.html', error=error)
当 form 属性中不存在这个键时会发生什么?会引发一个 KeyError 。
获取get提交的数据
要操作 URL (如 ?key=value )中提交的参数可以使用 args 属性:
searchword = request.args.get('key', '')
文件上传
用 Flask 处理文件上传很容易,只要确保不要忘记在你的 HTML 表单中设置 enctype="multipart/form-data" 属性就可以了。否则浏览器将不会传送你的文件
可以通过请求对象 files 属性来访问上传的文件
另外多出一个 用于把上传文件保存到服务器的文件系统中的 save() 方法
#例子
from flask import request
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/uploaded_file.txt')
可以使用 filename 属性 可以获取文件名字
但是请牢记这个值是 可以伪造的,永远不要信任这个值。如果想要把客户端的文件名作为服务器上的文件名, 可以通过 Werkzeug 提供的 secure_filename() 函数:
from flask import request
from werkzeug.utils import secure_filename
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/' + secure_filename(f.filename))
...
#下面补充
UPLOAD_FOLDER 是上传文 件要储存的目录
#这样写
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
ALLOWED_EXTENSIONS 是允许上传的文件扩展名的集合
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
下一个函数检查扩展名是否合法,上传文件,把用户重定向到已上传文件的 URL:
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
# if user does not select file, browser also
# submit an empty part without filename
if file.filename == '':
flash('No selected file')
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file',
filename=filename))
return '''
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<form method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=Upload>
</form>
'''
from flask import send_from_directory
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'],
filename)
那么 secure_filename() 函数到底是有什么用?有一条原 则是“永远不要信任用户输入”。这条原则同样适用于已上传文件的文件名。所有提 交的表单数据可能是伪造的,文件名也可以是危险的。此时要谨记:在把文件保存到 文件系统之前总是要使用这个函数对文件名进行安检。(上面的代码中有例子)
改进上传
如果上传的文件很小,那么会把它们储存在内 存中。否则就会把它们保存到一个临时的位置(通过 tempfile.gettempdir() 可以得到这个位置)
可以通过设置配置的 MAX_CONTENT_LENGTH 来限制文 件尺寸:
from flask import Flask, Request
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
上面的代码会把尺寸限制为 16 M 。如果上传了大于这个尺寸的文件, Flask 会抛 出一个 RequestEntityTooLarge 异常。
因为所有应用中上传文件的方案基本相同,因此可以使用 Flask-Uploads 扩展来 实现文件上传。这个扩展实现了完整的上传机制,还具有白名单功能、黑名单功能以 及其他功能。
Cookies
可以使用响应 对象 的 set_cookie 方法来设置 cookies
#获取cookie
from flask import request
@app.route('/')
def index():
username = request.cookies.get('username')
# use cookies.get(key) instead of cookies[key] to not get a
# KeyError if the cookie is missing.
#设置cookie
from flask import make_response,render_template
@app.route('/')
def index():
resp = make_response(render_template('index.html'))
resp.set_cookie('username', 'the username')
return resp
-------------------------------------------------------------
#延迟的请求回调
比如在一个 before_request() 回调函数中,我们需要根据响应对象设置一 个 cookie 。
例如可以尝试把应用逻辑移动到 after_request() 回调函数中。但是,有时候 这个方法让人不爽,或者让代码变得很丑陋
下例在 before_request() 回调函数中在 cookie 中记住了当前用户的 语言:
from flask import request, after_this_request
@app.before_request
def detect_user_language():
language = request.cookies.get('user_lang')
if language is None:
language = guess_language_from_request()
# when the response exists, set a cookie with the language
@after_this_request
def remember_language(response):
response.set_cookie('user_lang', language)
return response
g.language = language
重定向和错误
使用 redirect() 函数可以重定向。使用 abort() 可以 更早退出请求,并返回错误代码:
from flask import Flask, url_for, abort, redirect, request, render_template
app = Flask(__name__)
app.debug = False
@app.route('/')
def index():
return redirect(url_for('login'))
@app.route('/login', methods=['GET'],endpoint='login')
def func():
abort(500)
if __name__ == '__main__':
app.run()
-------------------------------------------------------------
#当访问的路径不存在的时候会走这个函数 第三号的404 下面的404提示 如果不写会 显示200
127.0.0.1 - - [20/Apr/2021 20:26:18] "GET /l HTTP/1.1" 404 -
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'),404
#还可以传递一个异常
@app.errorhandler(ZeroDivisionError)
def page_not_found(error):
return '服务器正在微信请您稍等片刻,不要放弃我们啊',500
关于响应
如果返回值是一个字典,那么会调用 jsonify() 来产生一个响应。以下是转换的规则:
1.如果视图返回的是一个响应对象,那么就直接返回它。
2.如果返回的是一个字符串,那么根据这个字符串和缺省参数生成一个用于返回的 响应对象。
3.如果返回的是一个字典,那么调用 jsonify 创建一个响应对象。
4.如果返回的是一个元组,那么元组中的项目可以提供额外的信息。元组中必须至少 包含一个项目,且项目应当由 (response, status) 、 (response, headers) 或者 (response, status, headers) 组成。 status 的值会重载状态代码, headers 是一个由额外头部值组成的列表 或字典。
5.如果以上都不是,那么 Flask 会假定返回值是一个有效的 WSGI 应用并把它转换为 一个响应对象。
6.如果想要在视图内部掌控响应对象的结果,那么可以使用 make_response() 函数。
如果想要在视图内部掌控响应对象的结果,那么可以使用 make_response() 函数。
设想有如下视图:
@app.errorhandler(404)
def not_found(error):
return render_template('error.html'), 404
可以使用 make_response() 包裹返回表达式,获得响应对象,并对该对象 进行修改,然后再返回:
@app.errorhandler(404)
def not_found(error):
resp = make_response(render_template('error.html'), 404)
resp.headers['X-Something'] = 'A value'
return resp
JSON 格式的 API
如果从视图 返回一个 dict ,那么它会被转换为一个 JSON 响应。
还需要创建其他类型的 JSON 格式响应,可以使用 jsonify() 函数。该函数会序列化任何支持的 JSON 数据类型。 也可以研究研究 Flask 社区扩展,以支持更复杂的应用。
@app.route("/users")
def users_api():
return jsonify([i for i in range(0,100)])
@app.route('/login', methods=['GET'], endpoint='login')
def func():
return {
'gzs': '格式',
'user':'genzeshi',
'age':18
}
会话
除了请求对象之外还有一种称为 session 的对象,允许你在不同请求 之间储存信息
这个对象相当于用密钥签名加密的 cookie ,即用户可以查看你的 cookie ,但是如果没有密钥就无法修改它
使用会话之前你必须设置一个密钥。举例说明:
from flask import Flask, session, redirect, url_for, request
from markupsafe import escape
app = Flask(__name__)
# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@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 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'))
-------------------------------------
from flask import Flask, escape, session, url_for, abort, redirect, request, render_template
app = Flask(__name__)
app.debug = False
app.secret_key="3423rjfjoejf3j23rfwejfwj"
@app.route('/login')
def login():
if 'username' in session:
print(session.get('username'))
return 'Logged in as %s' % escape(session['username']) #可以取值放到字符串中
return 'You are not logged in'
@app.route('/')
def index():
session['username'] = 'gzs'
return '存储完毕'
if __name__ == '__main__':
app.run()
消息闪现
闪现系统的基 本工作方式是:在且只在下一个请求中访问上一个请求结束时记录的消息。一般我们 结合布局模板来使用闪现系统。注意,浏览器会限制 cookie 的大小,有时候网络服 务器也会。这样如果消息比会话 cookie 大的话,那么会导致消息闪现静默失败
from flask import Flask, flash, redirect, render_template, \
request, url_for
app = Flask(__name__)
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@app.route('/')
def index():
return render_template('index.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != 'admin' or \
request.form['password'] != 'secret':
error = 'Invalid credentials'
else:
flash('You were successfully logged in')
return redirect(url_for('index'))
return render_template('login.html', error=error)
以下是实现闪现的 layout.html 模板:
<!doctype html>
<title>My Application</title>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block body %}{% endblock %}
以下是继承自 layout.html 的 index.html 模板:
{% extends "layout.html" %}
{% block body %}
<h1>Overview</h1>
<p>Do you want to <a href="{{ url_for('login') }}">log in?</a>
{% endblock %}
以下是同样继承自 layout.html 的 login.html 模板:
{% extends "layout.html" %}
{% block body %}
<h1>Login</h1>
{% if error %}
<p class=error><strong>Error:</strong> {{ error }}
{% endif %}
<form method=post>
<dl>
<dt>Username:
<dd><input type=text name=username value="{{
request.form.username }}">
<dt>Password:
<dd><input type=password name=password>
</dl>
<p><input type=submit value=Login>
</form>
{% endblock %}
日志
这时候就需要使用日志来记录这些不正常的东西了。自从 Flask 0.3 后就已经为你配置好 了一个日志工具。
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
配置管理
不管你使用何种方式载入配置,都可以使用 Flask 对象的 config 属性来操作配置的值
ENV 和 DEBUG 配置值是特殊的,因为它们如果在应用设置完成之 后改变,那么可以会有不同的行为表现。为了重可靠的设置环境和调试, Flask 使 用环境变量。
环境用于为 Flask 、扩展和其他程序(如 Sentry )指明 Flask 运行的情境是什么。 环境由 FLASK_ENV 环境变量控制,缺省值为 production 。
把 FLASK_ENV 设置为 development 可以打开调试模式。 在调试模式下, flask run 会缺省使用交互调试器和重载器。如果需要脱离 环境,单独控制调试模式,请使用 FLASK_DEBUG 标示。
#配置入门
app = Flask(__name__)
#可以像下面这样 书写配置
app.config['TESTING'] = True
一次更新多个配置值可以使用 dict.update() 方法:
app.config.update(
SECRET_KEY="3424fgrg",
DEBUG=True,
TESTING=True,
)
----------------------------------------------------------------------------------------
#内置配置变量
ENV
缺省值: 'production'
在生产环境中不要使用 development 。
DEBUG
在生产环境中不要开启调试模式
True
False
TESTING
你应当在自己的调试中开启本变量。
False
True
SESSION_COOKIE_PATH
认可会话 cookie 的路径。如果没有设置本变量,那么路径为 APPLICATION_ROOT ,如果 APPLICATION_ROOT 也没有设置,那么会是 / 。
缺省值: None
MAX_COOKIE_SIZE
当 cookie 头部大于本变量配置的字节数时发出警告。缺省值为 4093 。 更大的 cookie 会被浏览器悄悄地忽略。本变量设置为 0 时关闭警告。
补充
#(xss攻击)跨站脚本攻击是指在一个网站的环境中注入恶任意的 HTML (包括附带的 JavaScript )
flask预防了xss攻击:所有从后端传到前段的html代码和js代码默认是不让浏览器识别的
如果想让前段识别html或者是js代码可以使用 Markup 方法,代码写在这个函数里面
发送上传的 HTML ,永远不要这么做,使用 Content-Disposition: attachment 头部来避免这个问题。
虽然 Jinja2 可以通过转义 HTML 来保护你免受 XSS 问题,但是仍无法避免一种情况:属性注入的 XSS 。为了免受这 种攻击,必须确保在属性中使用 Jinja 表达式时,始终用单引号或双引号包裹:
#例子
<input value="{{ value }}">
这样做的目的是什么?
这样做的目的是方式攻击者使用html+js代码进行对网站的攻击
-------------------------------------------------------------
有一类 XSS 问题 Jinja 的转义无法阻止。 a 标记的 href 属性可以包含 一个 javascript: URI 。如果没有正确保护,那么当点击它时浏览器将执行其代 码。
#例子
<a href="{{ value }}">click here</a>
<a href="javascript:alert('unsafe');">click here</a>
#攻击演示案例
#html中的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script>
function one(a,b) {
alert(a+b)
}
</script>
<img src="/static/1.png" alt="">
<a href="javascript:onclick='one(1,2)'">点击</a>
</html>
#.py文件中的代码
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
为了防止发生这种问题,需要设置 Content Security Policy (CSP) 响应头部。
告诉浏览器哪里可以加载各种资源。这个头部应当尽可能使用,但是需要为网站定义 正确的政策。一个非常严格的政策是:
response.headers['Content-Security-Policy'] = "default-src 'self'"
告诉浏览器把所有 HTTP 请求转化为 HTTPS ,以防止 man-in-the-middle (MITM) 攻击。
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
--------------------------------------------------------------------------------------------------------------------------
跨站请求伪造( CSRF )
那么如何预防这个问题呢?基本思路是:对于每个要求修改服务器内容的请求,应该 使用一次性令牌,并存储在 cookie 里, 并且 在发送表单数据的同时附上它。 在服务器再次接收数据之后,需要比较两个令牌,并确保它们相等。
#这个预防测试flask没有做
--------------------------------------------------------------------------------------------------------------------------
json安全