flask文档整理的笔记 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 >{% block content %}{% endblock %}</div>
    <div >
        {% block footer %}
        &copy; 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
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
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安全