[python-flask]Flask框架笔记

Flask框架学习笔记

一个非常简单、轻量级的python web应用框架。

所谓框架:一套完整的技术解决方案

看的网课学习,18年的慕课的Flask高级编程,老师是七月,我觉得讲的很好

后来看b站有搬运的一个视频,也很不错...

笔记

最简单的应用

from flask import Flask

app = Flask(__name__)

@app.route('/index')
def hello():
    return 'hello'
    
app.run(host='0.0.0.0', debug=True, port=81)

0.0.0.0ip可以监听所有ip,81端口
所谓镜像:两份代码是一模一样的,镜像!

flask所有的扩展包都可以直接传入app实现初始化,或者用都有的一个方法init_app(app)!可以先实例化一个对象,不传入app,e.g.db = SQLAlchemy(),然后在恰当地方(app创建好了之后)加入db.init(app)

favicon.ico原来是网页的图标,才知道。。。浏览器会向域名请求一个静态资源:127.0.0.1:8000/favicon.ico来获取这个icon。原来如此,所以每次开启flask服务用浏览器都会有一个favicon.ico是404的请求......

flask的request对象

获取请求中的数据
1 post过来的表单数据
request.form获得一个ImmutableMultiDict的对象可以用get(key)的方法来获得key所对应的value

request.remote_addr可以获得ip地址

扫描二维码关注公众号,回复: 9267343 查看本文章

2 post过来的文件数据
首先Postman的Post请求头部要改

Content-Type : multipart/form-data

然后body里面form-data要选择是file,然后填写key选择file就行

flask的request对象通过request.files获得一个字典,具体[key]或者.get(key)获取key所对应的文件。
多文件情况request.files.getlist(key)获得key都为key的文件list

3 get方法加在url后面的参数?xxx=xxx
通过request.args来获取

4 request.values相当于args和form一起

__name__的作用

if __name__ == '__main__':
    # 生产环境这很重要,作为主函数入口,uwsgi来加载这个module的
    # 这个if 只会在 python xxx.py的时候使用起作用,会执行判断里面的
    # 语句
    # 而在生产环境中用uwsgi来开启web服务的时候,uwsgi启动整个模块
    # 就不会把这个文件当做主函数入口执行,就不会执行app.run()
    # app.run()是flask自带的服务器,很low的
    app.run()

flask中的session

flask的session是放到cookie中,而不是后端的数据库中。可以把session放到后端redis中。用的时候还是from flask import session

引入扩展flask-session
Flask-Session官网
需要配置
SESSION_TYPE => session存放在哪,可以是redis,mongodb,sqlalchemy....我们选择放到redis中
SESSION_REDIS => 表示redis的实例,default connect to 127.0.0.1:6379
SESSION_USE_SIGNER => 是否要对session签名为cookie中的sid,如果True必须要有secret key配置
SESSION_PERMANENT => session是否永久有效
PERMANENT_SESSION_LIFETIME =>设置session“永久”有效的时长传入datetime.timedelta类型的对象或者传入一个整数秒

利用flask-wtf扩展包的CSRFProtect

提供全局CSRF保护
from flask_wtf import CSRFProtect
CSRFProtect(app)

csrf验证机制简介: 从cookie中获取一个token,从请求体中也获取一个token,如果两个值相同,则检验通过,进入视图函数,如果不相同则失败,flask会向前端返回400错误。上面两个token都是自己来写,CSRFProtect只是做验证。POST,PUT,DELETE都会带body。同源策略,资源的操作只能是同一个origin可以。

设置cookie

from flask import make_response
from flask_wtf import csrf

# 生成一个csrf_token
csrf_token = csrf.generate_csrf()

# 构造响应并设置cookie
resp  = make_response(xxx)
resp.set_cookie("csrf_token", csrf_token)

flask-cors配置跨域请求

所谓跨域请求(Cross-origin Resource Sharing):浏览器向配置静态资源的服务器A请求了静态资源html,css,js文件,但是js文件中的所有动态请求的服务器不是静态资源所在服务器,而是在另一台服务器B上,这样的请求就叫做跨域请求,因为Ajax不能跨域。flask中可以通过flask_cors扩展包中的CORS
配置制定视图函数可跨域,用@cross_origin装饰器

@app.route("/")
@cross_origin()
def helloWorld():
  return "Hello, cross-origin-world!"

配置全局

CORS(app, resources={
        r'api/{}/*'.format(app.config['VERSION']) : {'origins' : '*'}
    })

视图函数

和普通函数区别,返回的东西有:
1.状态码status code 200, 404, 301, ...状态码只是告诉浏览器的标识,不影响内容
2.内容类型content-type 在返回的头部http headers,很重要

视图函数返回的对象是一个Response对象
from flask import make_response
response = make_response('<div></div>', 404)
为response修改头部response.headers = {自定义的headers}
也可以通过return "字符串", 404, headers不需要make_response

关于重定向:状态码301/302,在headers加入location,浏览器看到是301之后会去找location做重定向

headers = {
    'location' : '需要重定向的url'
}

作为api的时候返回json格式
'content-type' : 'application/json'
web交互的信息类型本质都是字符串

日志功能

导入python自带的模块loggin,然后在app的init的部分加入以下代码

# 开启日志功能
# 设置日志等级 
logging.basicConfig(level=logging.DEBUG)
# 创建日志记录器 指明日志保存的路径 每个文件的最大字节数 5mb  可存在的最大文件数量 5 
file_log_handler = RotatingFileHandler("webox/logs/webox_log.txt", maxBytes=1024*1024*5, backupCount=5)
# 创建日志记录的格式                 时间-[日志等级]--|产生记录的文件|->第几行: 日志信息
formatter = logging.Formatter("%(asctime)s-[%(levelname)s]--|%(filename)s|->%(lineno)d: %(message)s")
# 为日志记录器设置日志格式
file_log_handler.setFormatter(formatter)
# 为全局的日志工具对象(flask app使用的) 添加日志记录器
logging.getLogger().addHandler(file_log_handler)

之后可以用logging.info/debug/...()来记录日志

其实flask自己本身也用到了logging这个模块来输出日志,但是由于每个系统都只可以有一个logger,所以上面代码的最后一行是获取到了整个系统的rootLogger,对他添加Handler,来实现我们想要的日志功能,如果在调试模式下DEBUG=True,Flask会忽略所设置的日志等级,非调试模式下才可。

关于ORM模型 & flask-sqlalchemy

1 利用flask-sqlacodegen模块生成python的orm模型
终端输入flask-sqlacodegen --outfile models.py --flask 'mysql://user:password@ip/database'
将models.py放入models文件夹,修改db,from application import db

2 使用flask-sqlalchemy(快速入门)
2.1 查询(query)
一个查询对象ClassName.query
User.query.get()接收的就是id!

过滤器:
filter()可以模糊查找,参数是布尔表达式,例:Major.m_name.like("%%%s%%"%data),返回一个新的查询(query),再举个例子Major.query.filter(Major.id.in_([2, 3, 5, 12))
filter_by()精确查找,参数为字段=值
order_by()根据指定条件对原查询结果进行排序, 返回一个新查 倒序情况Goods.create_time.desc(),或者直接传送字段的字符串
group_by()根据指定条件对原查询结果进行分组, 返回一个新查询
limit()
offset()
(用到再说)
过滤器起到对被查询对象的筛选作用,筛选结果还是被查询对象,所以过滤器可以叠加使用
执行函数:(操作被查询结果)
all()返回查询到的所有结果(list)
first()返回第一个查询结果,如果没有返回None

2.2 会话管理,事务管理

提交单个(添加一个对象)
db.session.add(instance)
db.session.commit()

提交多个
db.session.add_all(list_of_instance)
db.session.commit()

事务回滚(在操作尚未commit的时候使用可以恢复到未操作前)
在commit出现异常的时候必须回滚
db.session.rollback

优化try/except的方法来自csdn
封装自动commit

更新对象
UserData.query.filter_by(username='name').update({'password':'newdata'})
然后commit
或者直接query出来之后修改对象属性再commit

DM锁,控制并发,db.session.query.filter().with_for_update().all()
之后跟上commit

2.3 创建ORM模型
官网-声明模型
直接上完整models模块代码吧

flask-migrate数据库迁移

首先安装flask-migrate库

from flask_migrate import Migrate, MigrateCommand
from application import app, db, manager
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)

生成orm模型文件之后,可以在命令行使用python manage.py db init生成数据库文件,然后python manage.py db migrate -m "leave a msg"来迁移数据库文件,最后python manage.py db upgrade更新数据库,这样数据库中就有表了。
python manage.py db history查看历史状态
python manage.py db downgrade xxx回到xxx历史状态。
注意: 出错或是警告可能是没有配置数据库SQLALCHEMY_DATABASE_URI或者没有export变量。。还有要在别的地方import models。

第一次migrate相当于db.create_all(),但是
upgrade可以修改数据库并不影响数据。

补充数据库utf8mb4字符集

首先修改mysql的文档,另一个笔记里有,不多赘述

主要是flask中的配置SQLALCHEMY_DATABASE_URI需要在最后加上?charset=utf8mb4

flask中使用redis

在python中简要记录过redis的笔记,真的十分简要

redis缓存机制在web应用中的适用场景:
有些资源前端会一直请求,每次从数据库中拿其实挺慢的,不如在第一次数据库操作后直接放在redis这个快速内存键值对数据库中,每次请求直接从redis中获取,返回。比如一些首页上面的资源,商品类别,等等。

基于token的登录验证

登录成功后,返回一个token(令牌)给前端,之后所有需要登录的请求都让前端在请求头中加入Authorization : token让后端做验证。

token技术用到了非对称加密和数字签名技术。

关于OAuth 2

这是一个关于第三方应用向服务器授权信息权限的技术
简单来说是一种授权机制
参见理解OAuth2.0 阮一峰
了解这个技术的原因是因为要通过学校的登录系统来验证用户在自己网站上的登录,而学校的登录应该用的是oauth验证。

加密算法

需求是要保存用户的密码,但是不能明文保存,只能加密保存
以下参考csdn
几种加密方法

1. 单向加密

简介:即提出数据指纹;只能加密,不能解密;主要用来验证数据是否完整。

过程:A向B发送数据以及对数据加密后的数据指纹,B接收到数据后对数据进行加密,得到的数据指纹和A传送过来的数据指纹相匹配的话,则可认为数据没有被篡改过。

特性

  • 定长输出
  • 雪崩效应;

算法

  • md5:Message Digest 5, 128bits
  • sha1:Secure Hash Algorithm 1, 160bits
  • sha224, sha256, sha384, sha512

2. 非对称加密

简介:非对称性加密,也叫公钥加密,加密解密的过程使用不同的密钥。

密钥分为公钥和私钥:
-公钥:从私钥中提取的,可公开给所有人 pubkey
-私钥:通过工具创建,使用者自己保存,必须保证私密性 secret key

特点:用公钥加密的数据只能通过与之对应的私钥来解密,反之亦然。私钥只能有一个主机拥有,公钥可多个主机拥有。

用途:

  • 数字签名
  • 密钥交换
  • 数据加密(速度慢)

常用算法:

python使用rsa

首先安装rsa库(官方文档)
pip install rsa

1.生成密钥

1.1 可以用openssl在文件夹中生成,然后在python中获取
命令行输入:
openssl genrsa -out private_key_file.pem 1024生成私钥文件
openssl rsa -in private_key_file.pem -pubout -out public_key_file.pem通过私钥生成公钥(注:-pubout)
python中you can use rsa.PrivateKey.load_pkcs1() and rsa.PublicKey.load_pkcs1() to load keys from a file:

>>> import rsa
>>> with open('private_key_file.pem', mode='rb') as privfile:
... keydata = privfile.read()
>>> privkey = rsa.PrivateKey.load_pkcs1(keydata)
>>> with open('public_key_file.pem', mode='rb') as privfile:
... keydata = privfile.read()
>>> pubkey = rsa.PublicKey.load_pkcs1(keydata)

目前这个方法遇到的问题....openssl生成的公钥的begin和end没有RSA关键字,PublicKey无法正确匹配和读取。通过vim人为加了RSA之后发现不是ascii编码(存疑)...

看了官方文档.......发现有一个方法叫load_pkcs1_openssl_pem(keyfile)(The contents of the file before the “—–BEGIN PUBLIC KEY—–” and after the “—–END PUBLIC KEY—–” lines is ignored.)专门为了读取openssl生成的公钥的,没问题了。
最后一句代码修改为

>>> pubkey = rsa.PublicKey.load_pkcs1_openssl_pem(keydata)

注意:Loads a key in PKCS#1 DER or PEM format.
所以必然还有load_pkcs1_openssl_der(keyfile)
目前没有了解两个后缀名的问题。

1.2 python直接生成

>>> import rsa
>>> (pubkey, privkey) = rsa.newkeys(512)

上面openssl和rsa.newkeys()中的1024和512表示keysize单位为bit,越大生成key的时间越多,官网列了张表

2.编码和解码(加密和解密)
用到的方法rsa.encrypt()rsa_decrypt()加密和解密

字符串需要编码成utf8(必须,因为rsa只处理二进制编码,不处理字符串)
>>> msg = 'a secret'.encode('utf8')

用公钥加密
>>> crypto = rsa.encrypt(msg, pubkey)

用私钥解密

>>> message = rsa.decrypt(crypto, privkey)
>>> print(message.decode('utf8')
a secret

解码为unicode

RSA只能加密比key小的(编码为二进制码后bit比key的小)字符串。报错(OverflowError(加密时), DecryptionError(解密时))

3.签名和验证

字符串可以经过哈希函数之后得到签名,并可验证
官网例子:
rsa.sign做签名,这里哈希函数是SHA-1

>>> (pubkey, privkey) = rsa.newkeys(512)
>>> message = 'Go left at the blue tree'
>>> signature = rsa.sign(message.encode('utf8'), privkey, 'SHA-1')

注意这里还是要编码为bytes的(官网没写....)

rsa.verify验证

>>> message = 'Go left at the blue tree'
>>> rsa.verify(message, signature, pubkey)
True

其实验证成功返回的不是True......而是hash的方法,这里应该是'SHA-1'
验证失败会raise exception: VerificationError

ok,关于RSA暂时了解到这里。

猜你喜欢

转载自www.cnblogs.com/coyote-waltz/p/12332352.html