[Python] Flask에서 Redis를 사용하여 세션 관리하는 샘플 Develop Tip

플래스크에서 세션 정보는 브라우저의 쿠키를 이용합니다.
해당 정보를 플래스크의 세션객체에 넣고 이용하는데
별도의 레디스 서버를 이용합니다.

레디스가 특별히 좋은 이유는 자동으로 파기 조건을 줄 수 있기 때문입니다.
혹시나 비정상 종료로 사용자가 브라우저를 종료하고 나갔다고 하더라도
해당 레디스에서는 자동으로 해당 세션정보를 시간이 지나면 파기합니다.

우선 아래의 코드와 같습니다.

from uuid import uuid4
from datetime import datetime, timedelta

from flask.sessions import SessionInterface, SessionMixin
from werkzeug.datastructures import CallbackDict
from flask import Flask, session, url_for, redirect

import redis
import cPickle

#########################################################################################
# This is a session object. It is nothing more than a dict with some extra methods
class RedisSession(CallbackDict, SessionMixin):
def __init__(self, initial=None, sid=None):
CallbackDict.__init__(self, initial)
self.sid = sid
self.modified = False

#########################################################################################
# Session interface is responsible for handling logic related to sessions
# i.e. storing, saving, etc
class RedisSessionInterface(SessionInterface):
#====================================================================================
# Init connection
def __init__(self, host='localhost', port=6379, db=0, timeout=3600):
self.store = redis.StrictRedis(host=host, port=port, db=db)
self.timeout = timeout
#====================================================================================
def open_session(self, app, request):
# Get session id from the cookie
sid = request.cookies.get(app.session_cookie_name)

# If id is given (session was created)
if sid:
# Try to load a session from Redisdb
stored_session = None
ssstr = self.store.get(sid)
if ssstr:
stored_session = cPickle.loads(ssstr)
if stored_session:
# Check if the session isn't expired
if stored_session.get('expiration') > datetime.utcnow():
return RedisSession(initial=stored_session['data'],
sid=stored_session['sid'])

# If there was no session or it was expired...
# Generate a random id and create an empty session
sid = str(uuid4())
return RedisSession(sid=sid)
#====================================================================================
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)

# We're requested to delete the session
if not session:
response.delete_cookie(app.session_cookie_name, domain=domain)
return

# Refresh the session expiration time
# First, use get_expiration_time from SessionInterface
# If it fails, add 1 hour to current time
if self.get_expiration_time(app, session):
expiration = self.get_expiration_time(app, session)
else:
expiration = datetime.utcnow() + timedelta(hours=1)

# Update the Redis document, where sid equals to session.sid
ssd = {
'sid': session.sid,
'data': session,
'expiration': expiration
}
ssstr = cPickle.dumps(ssd)
self.store.setex(session.sid, self.timeout, ssstr)

# Refresh the cookie
response.set_cookie(app.session_cookie_name, session.sid,
expires=self.get_expiration_time(app, session),
httponly=True, domain=domain)

#########################################################################################
# Create an application
app = Flask(__name__)
app.session_interface = RedisSessionInterface()

#########################################################################################
@app.route("/")
def index():
session.permanent = False
if not 'refreshed'in session:
session['refreshed'] = 0

text = "You refreshed the page %d times" % ( session['refreshed'] )
text += '<br/><a href="/kill">Reset</a>'
text += '<br/>dbname="%s"' % session.get('dbname','NULL')
session['refreshed'] = session['refreshed'] + 1
return text

#########################################################################################
@app.route("/dbset/<dbname>")
def dbset(dbname):
session['dbname']=dbname
return redirect(url_for('index'))

#########################################################################################
@app.route("/kill")
def kill():
session.clear()
return redirect(url_for('index'))

app.debug = True

#########################################################################################
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)


위의 코드를 redissess.py 라는 파일로 저장합니다.

물론 우분투 시스템에서 필요한 패키지를 설치하고,

$ sudo apt-get install redis-server
$ sudo pip install flask
$ sudo pip install redis

$ sudo python redissess.py 
라고 실행시키면...

 * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
 * Restarting with stat
와 같이 화면에 해당 서비스가 돌고 있다고 나오고,

다른 시스템에서 브라우저를 띄워,

http://{해당주소}:8080
로 접속을 하면

위와 같이 나옵니다.
화면을 Refresh 시키면,

위와 같이 동일 세션이기 때문에 page 카운트가 증가합니다.

dbset 이라는 redirect 명령으로 foodb 라고 dbset() 함수를 돌리면,

위와 같이 다른 탭에서도 동일한 세션 정보가 먹어 앞서 설정한 dbname이 foodb라고 나오는 군요...
동일한 브라우저의 다른 창을 띄워서 해보아도 동일한 세션으로 인식하는 군요.

그러나 다른 브라우저에서 새로 띄우면,

새로운 세션으로 인식합니다.

다른 bardb 라고 dbset()을 시키면,

해당 bardb 라는 이름이 그 해당 브라우저에서는 동일하게 가지고 있게 됩니다.

이렇게 하여 세션 정보를 gnuicorn 등의 몇 개의 인스턴스를 돌리던지 상관없이 
세션을 하나로 관리할 수 있게 되겠군요.


어느 분께는 도움이 되셨기를 바랍니다...

핑백

  • 지훈현서 : [Python] Flask & flask-restplus && swagger ui 2017-10-13 09:47:07 #

    ... K 로 간단한 HTTPS REST API 제공- Flask RESTful API를 gunicorn WSGI 이용 및 supervisor 활용- Flask에서 Redis를 사용하여 세션 관리하는 샘플- Flask 파일 업로드- Flask-Login 을 이용한 API 함수 인증 구현 예제 위와 같은 정도로 플래스크 관련 블로깅을 했던 ... more

덧글

  • 멍멍아 2015/11/24 15:12 # 삭제 답글

    잘 보고 갑니다. ㅎ 저는 만료시간 및 회원 정보 몇 가지 양방향 암호화한 데이터를 base64로 encode하면 쿠키정보에 사용하고 있습니다.
    따로 데이터베이스를 사용하지 않아서 좋아요~!
  • 지훈현서아빠 2015/11/24 15:50 #

    네~ 그러시군요~ ^^
  • 압도적감사 2018/04/30 16:57 # 삭제 답글

    캐쉬랑 세션으로 레디스를 쓰려고 했는데 덕분에 도움이 되었습니다.
  • 지훈현서아빠 2018/05/01 06:47 #

    도움이 되셨다니 저의 보람입니다~ ^^
  • 궁금한점 질문 2020/09/05 10:40 # 삭제 답글

    글 너무 잘보았습니다! redis로 session clustering 작업하고있는데, 써주신 코드로 작업해보니 cookie의 value는 변함이 없는 것 같던데 혹시 맞나요?? 주니어 개발자라서.. 질문드립니다!
댓글 입력 영역

구글애드텍스트