文章目录
君子之交淡如水,小人之交甘若醴。
君子之间的交情,并不因利益驱使而怎样,小人之间的交往,却多因利益驱使,利益过后,人与人如过眼云烟。君子之间,不因无利益不互相关心,小人之间,却多因利益关系而互相勾结。
一、Cookie的使用
1.Cookie的基本概念
在网站中,http请求是无状态的,也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。
cookie的出现就是为了解决这个问题,类型为小型文本文件,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。
第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动将上次请求存储的cookie数据自动传送给服务器,服务器通过浏览器携带的数据来判断用户。
cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB,因此使用cookie只能存储一些小量的数据。
2.Flask中使用Cookie
在Flask中操作cookie,是通过Response对象来操作,可以在response返回之前,通过response.set_cookie()
方法来设置,这个方法有以下几个参数:
- key
cookie的键 - value
cookie的键对应的值。 - max_age
cookie的过期时间,如果不设置,则浏览器关闭后就会自动过期。 - expires
过期时间,时间戳的形式(1970到现在的时间)。 - domain
该cookie在哪个域名中有效,一般设置子域名,比如cms.example.com。 - path
该cookie在哪个路径下有效,即当前主域名。
cookie简单测试:
flask_app.py如下:
from flask import Flask, Response
app = Flask(__name__)
app.config['TEMPLATE_AUTO_RELOAD'] = True
@app.route('/')
def index():
return '首页'
@app.route('/cookietest/')
def cookietest():
res = Response('Hello World!!')
res.set_cookie('username', value='corley', max_age=3)
return res
if __name__ == '__main__':
app.run(debug=True)
显示:
二、Flask-session的介绍和使用
1.Session的基本概念
session和cookie的作用有点类似,都是为了存储用户相关的信息;
session可以类比字典,每个键对应着指定的值。
不同的是,cookie存储在本地浏览器,session是一个思路、一个概念、一个服务器存储授权信息的解决方案,不同的服务器、不同的框架、不同的语言有不同的实现。虽然实现不一样,但是目的都是为了在服务器更方便地存储数据。
session是为了解决cookie存储数据不安全的问题。
在现实中,cookie和session是可以结合使用的,有两种方式:
- 存储在服务端
通过cookie存储一个session_id,具体的数据则是保存在session中。如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会携带该session_id,服务器根据session_id在session库中获取用户的session数据,就能判断该用户到底是谁,以及之前保存的一些状态信息,这称为server side session。
数据存储在服务器会更加安全,不容易被窃取,包括Django在内的很多框架都是采用的这种形式。
但存储在服务器也有一定的弊端,就是会占用一定的服务器资源,但现在服务器存储量一般较大,存储session信息是不成问题的。 - 存储在客户端
将session数据加密,然后存储在cookie中,这种称为client side session。
flask采用的就是这种方式,但是也可以替换成其他形式。
2.Flask中使用Session
Flask中中使用session需要先通过from flask import session
语句导入,再添加key和value。
Flask中的session机制是将session信息加密,然后存储在cookie中,因此需要通过配置信息app.config['SECRET_KEY']
设置加密密钥。
session的基本使用测试如下:
from flask import Flask, session
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24) # os.urandom()生成指定字节数的随机字符串
@app.route('/')
def index():
session['username'] = 'Corley'
session['user_id'] = 1
return '这是首页'
if __name__ == '__main__':
app.run(debug=True)
显示:
显然,上传了session的一些信息;
其中,os.urandom()
用于生成指定字节数的随机字符串,用于表示密钥;
同时可以看到,到期时间是浏览器会话结束时。
可以指定会话到期时间,如下:
from flask import Flask, session
from datetime import timedelta
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
# 自定义过期时间
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)
@app.route('/')
def index():
session['username'] = 'Corley'
session['user_id'] = 1
# 设置持久化
session.permanent = True
return '这是首页'
if __name__ == '__main__':
app.run(debug=True)
显示:
显然,此时session的过期时间为2个小时。
如果想自定义过期时间,需要进行两方面配置:
(1)设置持久化
session.permanent = True
设置了持久化后,默认的过期时间为1个月,如果想要自定义过期时间,还需要进行第二步配置。
(2)自定义过期时间
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)
还可以获取session值,测试如下:
from flask import Flask, session
from datetime import timedelta
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)
@app.route('/')
def index():
username = session.get('username')
return '这是首页' + str(username)
@app.route('/login/')
def login():
session['username'] = 'Corley'
session['user_id'] = 1
session.permanent = True
return '登录页面'
if __name__ == '__main__':
app.run(debug=True)
显示:
显然,获取到了session的信息。
可以删除session,如下:
from flask import Flask, session
from datetime import timedelta
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)
@app.route('/')
def index():
username = session.get('username')
return '这是首页' + str(username)
@app.route('/login/')
def login():
session['username'] = 'Corley'
session['user_id'] = 1
session.permanent = True
return '登录页面'
@app.route('/logout/')
def logout():
# 删除session
session.clear()
return '退出登录'+ str(session.get('username'))
if __name__ == '__main__':
app.run(debug=True)
显示:
显然,在访问http://127.0.0.1:5000/logout/后,session已经清空;
除了session.clear()
,还可以指定key删除session,如删除username可以用session.pop('username')
。
三、Flask上下文
Flask项目中上下文包括两类:
- 应用上下文
用于记录和应用相关的数据,包括current_app和g。 - 请求上下文
用于记录和请求相关的数据,包括request和session。
请求上下文request和应用上下文current_app都是全局变量,所有请求都是共享的。
Flask中使用特殊的机制来保证每次请求的数据都是隔离的,即用户A请求所产生的数据不会影响到用户B的请求,所以直接导入request对象也不会被一些脏数据影响,并且不需要像Django一样在每个函数中使用request的时候传入request参数。
4类上下文对象如下:
- request
请求上下文对象,一般用来保存一些请求的变量,如method、args、form等。 - session
请求上下文对象,一般用来保存一些会话信息。 - current_app
应用上下文对象,返回当前的app。 - g
应用上下文对象,处理请求时用于临时存储的对象。
current_app使用示意如下:
from flask import Flask, current_app
app = Flask(__name__)
@app.route('/')
def index():
return '这是首页'
@app.route('/login/')
def login():
ca = current_app.name
cfg = current_app.config['DEBUG']
return '登录页面:' + ca + '-' + str(cfg)
if __name__ == '__main__':
app.run(debug=True)
显示:
显然,得到了当前所处的app,即初始化Flask对象时传入的参数__name__
,也就是当前Python文件名;
除了获取当前app名称,还可以获取绑定到app中的配置参数。
注意:current_app只能在视图函数中使用,在视图函数之外使用会报错。
一般情况测试:
新建utils.py如下:
'''
工具文件
'''
def log_a(username):
print("Log A %s" % username)
def log_b(username):
print("Log B %s" % username)
主程序flask_context.py如下:
from flask import Flask, session
from utils import log_a, log_b
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
@app.route('/')
def index():
username = session.get('username')
log_a(username)
log_b(username)
return '这是首页'
@app.route('/login/')
def login():
session['username'] = 'Corley'
session['user_id'] = 1
return '登录页面'
if __name__ == '__main__':
app.run(debug=True)
运行后,先访问http://127.0.0.1:5000/,再访问http://127.0.0.1:5000/login/,再访问http://127.0.0.1:5000/,控制台打印如下:
127.0.0.1 - - [13/May/2020 11:57:59] "GET / HTTP/1.1" 200 -
Log A None
Log B None
127.0.0.1 - - [13/May/2020 11:58:14] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 11:58:23] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley
显然,在访问登录页面之后,控制台打印出了username信息。
此时使用g对象进行改进:
主程序文件修改如下:
from flask import Flask, current_app, g, session
from utils import log_a, log_b
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
@app.route('/')
def index():
g.username = session.get('username')
log_a()
log_b()
return '这是首页'
@app.route('/login/')
def login():
session['username'] = 'Corley'
session['user_id'] = 1
return '登录页面'
if __name__ == '__main__':
app.run(debug=True)
utils.py修改如下:
'''
工具文件
'''
from flask import g
def log_a():
print("Log A %s" % g.username)
def log_b():
print("Log B %s" % g.username)
运行之后,按之前的顺序访问,控制台打印:
127.0.0.1 - - [13/May/2020 12:04:34] "GET / HTTP/1.1" 200 -
Log A None
Log B None
127.0.0.1 - - [13/May/2020 12:04:42] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:04:52] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley
显然,达到了同样的效果,并且此时不需要再在log_a()
和log_b()
函数中传递参数;
每次刷新页面后,g对象都会被重置,是临时存储的对象;
g对象一般用于解决频繁传参的问题。
可以在一个视图函数中实现调用另一个函数,如下:
from flask import Flask, current_app, g, session
from utils import log_a, log_b
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
@app.route('/')
def index():
g.username = session.get('username')
log_a()
log_b()
hello()
return '这是首页'
def hello():
print('Hello %s' % g.username)
@app.route('/login/')
def login():
session['username'] = 'Corley'
session['user_id'] = 1
return '登录页面'
if __name__ == '__main__':
app.run(debug=True)
按之前的顺序访问,控制台打印如下:
127.0.0.1 - - [13/May/2020 12:13:41] "GET / HTTP/1.1" 200 -
Log A None
Log B None
Hello None
127.0.0.1 - - [13/May/2020 12:13:48] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:13:52] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley
Hello Corley
四、常用的钩子函数
钩子函数是指在执行函数和目标函数之间挂载的函数,框架开发者给调用方提供一个point即挂载点,至于挂载什么函数由调用方决定, 从而大大提高了灵活性。
1.before_first_request
第一次请求之前执行。
练习如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
print('这是首页')
return '这是首页'
@app.before_first_request
def handle_first_request():
print('在第一次请求之前执行')
if __name__ == '__main__':
app.run(debug=True)
运行后,访问http://127.0.0.1:5000/,控制台打印:
127.0.0.1 - - [13/May/2020 12:39:01] "GET / HTTP/1.1" 200 -
在第一次请求之前执行
这是首页
2.before_request
在每次请求之前执行,通常可以用这个装饰器来给视图函数增加一些变量。
练习如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
print('这是首页')
return '这是首页'
@app.before_first_request
def handle_first_request():
print('在第一次请求之前执行')
@app.before_request
def handle_before():
print("在每一次请求之前执行")
if __name__ == '__main__':
app.run(debug=True)
运行后,多次访问http://127.0.0.1:5000/,控制台打印:
在第一次请求之前执行
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:36] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:38] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:39] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:40] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:41:41] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
3.after_request
在每次请求之后执行。
练习如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
print('这是首页')
return '这是首页'
@app.before_first_request
def handle_first_request():
print('在第一次请求之前执行')
@app.before_request
def handle_before():
print("在每一次请求之前执行")
@app.after_request
def handle_after(response):
print("在每一次请求之后执行")
return response
if __name__ == '__main__':
app.run(debug=True)
运行后,多次访问http://127.0.0.1:5000/,控制台打印:
127.0.0.1 - - [13/May/2020 12:51:05] "GET / HTTP/1.1" 200 -
在第一次请求之前执行
在每一次请求之前执行
这是首页
在每一次请求之后执行
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:06] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:07] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:08] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:09] "GET / HTTP/1.1" 200 -
4.teardown_appcontext
不管是否有异常,注册的函数都会在每次请求之后执行。
练习如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
1/0
print('这是首页')
return '这是首页'
@app.before_first_request
def handle_first_request():
print('在第一次请求之前执行')
@app.before_request
def handle_before():
print("在每一次请求之前执行")
@app.after_request
def handle_after(response):
print("在每一次请求之后执行")
return response
@app.teardown_appcontext
def handle_teardown(response):
print('teardown被执行')
return response
if __name__ == '__main__':
app.run()
运行后,访问http://127.0.0.1:5000/,显示:
控制台打印:
在第一次请求之前执行
在每一次请求之前执行
在每一次请求之后执行
teardown被执行
[2020-05-13 12:58:48,206] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "XXX\Test-gftU5mTd\lib\site-packages\flask\_compat.py", line 39, in reraise
raise value
File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "XXXX\flask_hook.py", line 8, in index
1/0
ZeroDivisionError: division by zero
127.0.0.1 - - [13/May/2020 12:58:48] "GET / HTTP/1.1" 500 -
显然, 在出现异常的情况下也执行了handle_teardown()
函数;
虚造关闭Debug模式才能有明显的现象,在Debug模式下是不能执行handle_teardown()
函数的。
5.context_processor
上下文处理器,返回的字典中的键可以在模板上下文中使用。
假如在多个视图函数中渲染模板时都需要传入同样的参数,flask_hook.py如下:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html', username='Corley')
@app.route('/list/')
def list():
return render_template('list.html', username='Corley')
if __name__ == '__main__':
app.run(debug=True)
在模板目录templates中新建index.html和list.html,index.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<p>这是首页</p>
<p>{{ username }}</p>
</body>
</html>
list.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>列表</title>
</head>
<body>
<p>这是列表</p>
<p>{{ username }}</p>
</body>
</html>
显示:
显然,达到了效果,但是在每个视图函数中都要传入参数,很麻烦冗余,可以用context_processor装饰器定义钩子函数传递公共参数:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/list/')
def list():
return render_template('list.html')
@app.context_processor
def context_process():
return {'username': 'Corley'}
if __name__ == '__main__':
app.run(debug=True)
效果与之前一样;
此时在每个视图函数中不需要再传递参数,而是在context_processor装饰器定义的钩子函数中返回参数。
6.errorhandler
errorhandler接收状态码作为参数,可以自定义处理返回这个状态码的响应的方法。
测试如下:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
1/0
return render_template('index.html')
@app.route('/list/')
def list():
return render_template('list.html')
@app.context_processor
def context_process():
return {'username': 'Corley'}
@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def server_error(error):
return '<p>服务器内部错误...</p>', 500
if __name__ == '__main__':
app.run()
新建404.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>页面404</title>
</head>
<body>
<p>页面跑到火星去了...</p>
</body>
</html>
显示:
显然,捕获到了404和500错误。
还可以在视图函数中用abort()
方法主动抛出异常,如下:
from flask import Flask, render_template, abort
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/list/')
def list():
abort(404)
return render_template('list.html')
@app.context_processor
def context_process():
return {'username': 'Corley'}
@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def server_error(error):
return '<p>服务器内部错误...</p>', 500
if __name__ == '__main__':
app.run()
显示:
显然,此时再访问http://127.0.0.1:5000/list/,就会报错;
因为在list()
视图函数中使用sort()
方法抛出404错误,被钩子函数page_not_found()
捕获到,因此访问会出现404错误。
五、Flask-Restful
1.Restful API规范
Restful API是用于在前端与后台进行通信的一套规范,使用这个规范可以让前后端开发变得更加简单。
协议
采用http或者https协议。
数据传输格式
数据之间传输的格式应该使用json形式,而不是xml。
url链接
url链接中不能有动词,只能有名词;
对于一些名词,如果出现复数,应该在后面加s。
HTTP请求的方法
方法 | 含义 | 举例 |
---|---|---|
GET | 从服务器上获取资源 | /users/ 获取所有用户 |
… | … | /user/id/ 根据id获取一个用户 |
POST | 在服务器上新创建一个资源 | /user/ 新建一个用户 |
PUT | 在服务器上更新资源(客户端提供所有改变后的数据) | /user/id/ 更新某个id的用户的信息(需要提供用户的所有信息) |
PATCH | 在服务器上更新资源(客户端只提供需要改变的属性) | /user/id/ 更新某个id的用户信息(只需要提供需要改变的信息) |
DELETE | 从服务器上删除资源 | /user/id/ 删除一个用户 |
状态码
状态码 | 描述 | 含义 |
---|---|---|
200 | OK | 服务器成功响应客户端的请求。 |
400 | INVALID REQUEST | 用户发出的请求有错误,服务器没有进行新建或修改数据的操作 |
401 | Unauthorized | 用户没有权限访问这个请求 |
403 | Forbidden | 因为某些原因禁止访问这个请求 |
404 | NOT FOUND | 用户发送的请求的url不存在 |
406 | NOT Acceptable | 用户请求不被服务器接收(比如服务器期望客户端发送某个字段,但是没有发送) |
500 | Internal server error | 服务器内部错误,比如出现了bug |
2.Flask-Restful插件
基本概念
Flask-Restful是Flask中专门用来写restful api的一个插件,使用它可以快速集成restful api功能。
在app的后台以及纯API的后台中,这个插件可以帮助我们节省很多时间;
在普通的网站中,这个插件显得有些鸡肋,因为在普通的网页开发中,是需要去渲染HTML代码的,而Flask-Restful在每个请求中都返回json格式的数据。
安装
Flask-Restful的环境要求:
- Flask版本>=0.80.8
- Python版本为2.6、2.7,或3.3及以上。
在虚拟环境中使用pip install flask-restful
命令安装。
基本使用
使用Flask-Restful定义视图函数时,要继承自flask_restful.Resource
类,再根据具体的请求的方法来定义相应的方法。
例如,如果期望客户端使用get方法发送请求,那么就定义一个get方法,类似于MethodView。
简单使用测试如下:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class IndexView(Resource):
def get(self):
return {'username': 'Corley'}
def post(self):
return {'info': 'Login Successfully!!'}
api.add_resource(IndexView, '/', endpoint='index')
if __name__ == '__main__':
app.run(debug=True)
运行之后,使用Postman模拟请求如下:
显然,用get方法和post方法请求得到的结果是不一样的。
说明:
- endpoint是用来给
url_for()
方法反转url的时候指定的;如果不设置endpoint参数,会使用视图的名字的小写作为endpoint的值。 add_resource()
方法的第二个参数是视图函数的路由地址,这个地址与视图函数的route一样,可以传递参数;有一点不同的是,这个方法可以传递多个url来指定视图函数。