将 flask 中的 session 存储到 SQLite 数据库中
将 flask 中的 session 存储到 SQLite 数据库中
使用 flask 构建服务器后端时,常需要在浏览器端存储 cookie 用于识别不同用户,根据不同的 cookie 判断出当前请求访问的用户。而在服务器端 flask 提供了易用的 session 代理,通过 from flask import session
, 可以引入 session 并将其作为特定用户信息的字典来用。
要使用 session 代理,首先要给 flask 实例设置 secret_key,然后就可以在请求上下文中使用 session(如在特定的路由中):
from flask import Flask, session, render_template
app = Flask( __name__ )
app.secret_key = hashlib.sha1( 'abcdefg'.encode() ).hexdigest()
# app.config[ 'SECRET_KEY' ] = hashlib.sha1( 'abcdefg'.encode() ).hexdigest() # same with previous one statement
@app.route( '/', methods = [ 'GET' ])
def index():
session[ 'user' ] = 'test'
return render_template( 'index.html' )
由于 flask 默认的 session 实现方式是 SecureCookieSessionInterface,它将 session 中的数据以加密方式存储在客户端的 cookie 中,因此在使用 session 前,需要首先设置 secret_key,这样才能避免客户端中的 cookie 泄漏信息。
将 session 信息存储在数据库中
尽管 flask 默认的 session 数据是加密之后存储在客户端的 cookie 中,但如果 session 要存储大量敏感的信息,还是将其存储在服务端较好。flask 已经提供了 SessionInterface 接口,要自己实现 session 的存储方式,需要实现该接口并将 flask 实例 app 的 session_interface 指向其实现类的实例。此外还要实现自定义的 Session 类,在自定义的 SessionInterface 实现类中使用自定义的 Session 类。
自定义 Session 类
自定义的 Session 类实现起来很简单,因为它的工作方式与 dict 很类似,可以直接将其继承 dict,这里使用 werkzeug 中提供的 CallbackDict,另外还需要继承的父类是 SessionMixin,以便在 flask 中使用该 Session。由于要使用的是 sqlite 存储方式,这里我将类命名为 DbSession:
# file: dbsessions.py
from flask.sessions import SessionMixin, SessionInterface
from werkzeug import CallbackDict
class DbSession( CallbackDict, SessionMixin ):
def __init__( self, initial = None, sid = None, new = False ):
def on_update( self ):
self.modified = True
CallbackDict.__init__( self, initial, on_update )
self.sid = sid
self.new = new
self.modified = False
在 Server-side sessions with SQLite 这篇示例中,由于实现的其实现的 SqliteSession 继承的是 MutableMapping,并且没有使用 flask-sqlalchemy,因此需要有
__getitem__
、__setitem__
等实现数据库的操作,并且需要定义查询的 SQL 语句。而我们使用 flask-sqlalchemy 处理底层的数据库事务,因此 Session 实现类较为简单。
定义 Session 数据模型
为了将 session 数据存储在 sqlite 数据库中,我们还需要定义它的 Model,flask-sqlalchemy 已经为我们处理好了底层繁琐的代码,在应用中直接使用 flask-sqlalchemy 提供的数据库实例即可。
使用 flask-sqlalchemy:
# file: myglobal.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask( __name__ )
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy( app )
# file: dbsessions.py
from .myglobal import db
from datetime import datetime, timedelta
class Sessions( db.Model ):
__tablename__ = 'sessions'
sid = db.Column( db.String( 255 ), primary_key = True )
expired = db.Column( db.DateTime )
session = db.Column( db.Text ) # it refers session content
def __init__( self, **kwargs ):
super( Sessions, self ).__init__( **kwargs )
if 'sid' in kwargs:
self.sid = kwargs[ 'sid' ]
if 'expired' in kwargs:
self.expired = kwargs[ 'expired' ]
if 'session' in kwargs:
self.session = kwargs[ 'session' ]
def __repr__( self ):
return '<Session %s>' % self.session
这里的 sid 字段为用于识别用户身份的标识,expired 字段用于设定过期时间,session 字段是实际存储的 session 内容,由于 session 的数据结构与 dict 一样,因此可以考虑使用 json 格式序列化到数据库中。
顺便提一下,如果在程序中使用 session,并要以 json 格式序列化 session,那么 session 中最好不要用到 Datetime 类,否则在运行过程中会提示 Datetime 无法被 JSON 序列化
之类的错误,使用时间戳替代即可:
# in flask app context
session[ 'login' ] = datetime.now() # 当用 JSON 格式序列化 session 时,会提示 Datetime 对象无法序列化
session[ 'login' ] = datetime.now().timestamp() # 正常
实现 SessionInterface 接口
接下来要实现 flask 中提供的 SessionInterface 接口,较前面两部分内容相对复杂,但是其实原理也很简单。这里需要在实现类中完成 sid 的分配和客户端 cookie 的读写,sid 分配需要尽可能随机,可以直接使用 python 中的 uuid 模块。序列化 session 时使用 JSON 格式,因此需要引入 json 模块(也可以使用 pickle 模块进行序列化)。
SessionInterface 的两个主要接口方法是 open_session
和 save_session
:
# file: dbsessions.py
from uuid import uuid4
import json
class DatabaseSessionInterface( SessionInterface ):
session_class = DbSession
serializer = json # or pickle
def __init__( self, db = db, prefix = 'session:' ):
self.db = db
self.prefix = prefix
def generate_sid( self ):
return str( uuid4() )
def get_expiration_time( self, app, session = None ):
"""
可以直接使用父类的默认超时时间方法,为了自定义默认超时时间进行重写
"""
if session.permanent:
return datetime.now() + app.permanent_session_lifetime
return datetime.now() + timedelta( minutes = 10 )
def open_session( self, app, request ):
sid = request.cookies.get( app.session_cookie_name ) # 尝试从请求中获取存储的 cookie(与 session 相关的 cookie)
if not sid:
# 没有从 cookie 中找到相关 sid,说明客户端是第一次访问,需要构造 sid
sid = self.generate_sid()
# create new Session Model instance
dbrecord = Sessions( sid = sid )
sc = self.session_class( sid = sid, new = True )
return sc
# if client have a session_cookie, take relative one from database
dbrecord = Sessions.query.filter_by( sid = sid ).first()
if dbrecord is None:
return self.session_class( sid = sid ) # in case of wrong sid in session_cookie
else:
return self.session_class( self.serializer.loads( dbrecord.session ), sid = sid )
def save_session( self, app, session, response ):
domain = self.get_cookie_domain( app )
dbrecord = Sessions.query.filter_by( sid = session.sid ).first()
if not session:
# 内容为空并且被修改过,说明 session 已经被删除
if session.modified:
db.session.delete( dbrecord )
db.session.commit()
response.delete_cookie( app.session_cookie_name, domain = domain )
return
if dbrecord is None:
dbrecord = Sessions( sid = session.sid )
db.session.add( dbrecord )
# update database record
dbrecord.session = self.serializer.dumps( session )
sexp = self.get_expiration_time( app, session )
dbrecord.expired = sexp
db.session.commit()
response.set_cookie( app.session_cookie_name, session.sid, expires = sexp, httponly = True, domain = domain )
实现了 open_session 和 save_session 方法的实现类就可以在 flask 应用中使用了:
app.session_interface = DatabaseSessionInterface()
这里有必要说一下处理 session 的流程:
若客户端第一次请求服务器(服务器使用了 session,但服务器上没有相应的 session),首先调用
open_session
方法,返回一个新创建的 DbSession 对象。在open_session
方法中最终一定需要返回一个字典结构的对象,我们这里也就是 DbSession 实例。接下来 flask 会将
open_session
方法中返回的 DbSession 对象的 sid 写入到客户端的 cookie 响应中,并将该对象作为 session 参数调用save_session
方法。根据 session 中的内容(当 session 中的内容为空时,not session 为 True)和属性执行删除或更新 session 的操作客户端存储了有 sid 的 cookie 后,后续请求到来时,在
open_session
方法将会返回在数据库中存储的 session 条目。这样就达到了识别用户的目的。
总结
总的来说实现自定义的 session 存储方式并不困难,在参考了 stackoverflow 上相关问题的解答和一些示例后再根据自己的需求进行实现和改进。其实 Server-side sessions with SQLite 这篇示例已经很接近于我的需求了,但是我的具体代码中和该示例有所区别。
比如,示例中为每个 session 创建一个单独的 sqlite 数据库文件,而我的代码中将 session 作为表的一行。另外,示例中会话结束时就会删除对应的数据库文件,而我修改了默认的超时时间方法,因此需要在会话结束后并超时时删除对应的数据库条目,但是目前的代码中并没有相应的实现。不过基本上来说已经达到了最初的目的。
envrionments or requirements
- python3.6
- flask
- flask-sqlalchemy
References
- https://stackoverflow.com/questions/17694469/flask-save-session-data-in-database-like-using-cookies
- http://flask.pocoo.org/snippets/75/
- Server-side sessions with SQLite
- flask.sessions.SessionInterface
将 flask 中的 session 存储到 SQLite 数据库中的更多相关文章
- C# 在SQLite数据库中存储图像 z
C# 在SQLite数据库中存储图像 更多 0 C# SQLite 建表语句 CREATE TABLE [ImageStore]([ImageStore_Id] INTEGER NOT NULL ...
- 在IOS中使用DES算法对Sqlite数据库进行内容加密存储并读取解密
在IOS中使用DES算法对Sqlite 数据库进行内容加密存储并读取解密 涉及知识点: 1.DES加密算法: 2.OC对Sqlite数据库的读写: 3.IOS APP文件存储的两种方式及读取方式. 以 ...
- .net的session详解 存储模式 存到数据库中 使用范围与大小限制 生命周期
Session又称为会话状态,是Web系统中最常用的状态,用于维护和当前浏览器实例相关的一些信息.举个例子来说,我们可以把已登录用户的用户名放在Session中,这样就能通过判断Session中的某个 ...
- Django session存储到redis数据库
把session存储到redis数据库,需要在setting中配置 django-redis 中文文档 http://django-redis-chs.readthedocs.io/zh_CN/lat ...
- Android数据存储之SQLite数据库
Android数据存储 之SQLite数据库简介 SQLite的相关知识,并结合Java实现对SQLite数据库的操作. SQLite是D.Richard Hipp用C语言编写的开源嵌入式数据库引擎. ...
- QQ群消息监听并将消息存储到SQLite数据库中
目录 一.前言 二.效果图 1.插件界面 2.SQLite数据库 3.QQ群消息 三.准备工作 1.CQA软件 2.CQA-SDK易语言版本 3.易语言破解版 4.使用到的相关模块 四.开始撸代码 五 ...
- 在VB中利用Nuget包使用SQLite数据库和Linq to SQLite
上午解决了在C#中利用Nuget包使用SQLite数据库和Linq to SQLite,但是最后生成的是C#的cs类文件,对于我这熟悉VB而对C#白痴的来说怎么能行呢? 于是下午接着研究,既然生成的是 ...
- Android存储之SQLite数据库
Android存储之SQLite数据库数据库 创建数据库 package --; import android.content.Context; import android.database.sql ...
- Android数据存储引擎---SQLite数据库
目标:是否可以在PC端桌面上使用SQLite数据库制作一个财务文件? 目录: 来源: 实践: 总结和比较: SQLite数据简介 是什么,内部结构是怎样的,数据库和表的关系是什么 有什么用 常用的操作 ...
随机推荐
- MVPArms MVP快速集成框架
前言 今年的Android技术圈中MVP,Dagger2,Rxjava,Retrofit这些词汇非常火,随便打开一个技术论坛都充斥着大量的关于这些技术的文章,Github也充斥着各种以基于MVP+Re ...
- 在Linux环境下的卸载Oracle11G操作
1.使用SQL*PLUS停止数据库[oracle@OracleTest oracle]$ sqlplus /nolog SQL> connect / as sysdba SQL> shut ...
- day1学python Hello Python
Hello Python 本人使用的是Pycharm编译器 ----------------------------------------------- 1.输出 2.赋值 3.‘’‘/“”“ 多行 ...
- 题解 CF520E 【Pluses everywhere】
题目链接 ps:可能组合数一不小心打错了,请发现的大佬提出,谢谢. 我们来讨论每一位数$a_{i}$被算了多少次. 总共有$n-1$个空位可以放$'+'$所以,$a_{i}$左边有$i-1$个空位,右 ...
- django日期查询出现UTC日志转换CONVERT_TZ出错的问题
select CONVERT_TZ(NOW(), 'UTC', 'UTC') 出现NULL值, 原因是MySQL少了时区表: SELECT * FROM mysql.time_zone; SELECT ...
- Shell脚本——初识
1.在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash. #! 告诉系统其后路径所指 ...
- ubuntu下QtCreator启动无响应问题解决
打开Qt后就卡死. 解决方法:删除系统配置目录下的QtProject文件夹: find / -name QtProject 输出: /root/.config/QtProject 删除QtProjec ...
- 小程序tabBar显示问题
我一直在纠结为什么小程序在有的页面显示,有的页面不显示 查了一下才知道,小程序tabBar只在tabBar中的list定义了页面的才会显示,其余页面不会显示 如下例:tabBar只在画红框的页面显示
- 洛谷 P1149 火柴棒等式
嗯.... 这道题好讨厌啊!!!! 一开始莫名RE,然后发现数组小了,然后发现后面几个点总是WA,原来推的少了.... 并且这道题的思路真的好水啊!! 先看一下题: 题目描述 给你n根 ...
- zip与unzip