session_db : store sessions in a database rather than in filestore
parent
26616f2155
commit
5cc37f95b1
|
@ -0,0 +1 @@
|
||||||
|
import models
|
|
@ -0,0 +1,24 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': "Store sessions in DB",
|
||||||
|
'description': """
|
||||||
|
Storing sessions in DB
|
||||||
|
- workls only with workers > 0
|
||||||
|
- set the session_db parameter in the odoo config file
|
||||||
|
- session_db parameter value is a full postgresql connection string, like user:passwd@server/db
|
||||||
|
- choose another DB than the odoo db itself, for security purpose
|
||||||
|
- it also possible to use another PostgreSQL user for the same security reasons
|
||||||
|
|
||||||
|
Set this module in the server wide modules
|
||||||
|
""",
|
||||||
|
'category': '',
|
||||||
|
'version': '1.0',
|
||||||
|
|
||||||
|
'depends': [
|
||||||
|
],
|
||||||
|
|
||||||
|
'data': [
|
||||||
|
],
|
||||||
|
'demo': [
|
||||||
|
],
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
import session
|
|
@ -0,0 +1,125 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import psycopg2
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import werkzeug.contrib.sessions
|
||||||
|
import time
|
||||||
|
|
||||||
|
import odoo
|
||||||
|
from odoo import http
|
||||||
|
from odoo.tools.func import lazy_property
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def with_cursor(func):
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
tries = 0
|
||||||
|
while True:
|
||||||
|
tries += 1
|
||||||
|
try:
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
except psycopg2.InterfaceError as e:
|
||||||
|
_logger.info("Session in DB connection Retry %s/5" % tries)
|
||||||
|
if tries>4:
|
||||||
|
raise e
|
||||||
|
self._open_connection()
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
class PGSessionStore(werkzeug.contrib.sessions.SessionStore):
|
||||||
|
# FIXME This class is NOT thread-safe. Only use in worker mode
|
||||||
|
def __init__(self, uri, session_class=None):
|
||||||
|
super(PGSessionStore, self).__init__(session_class)
|
||||||
|
self._uri = uri
|
||||||
|
self._open_connection()
|
||||||
|
self._setup_db()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self._cr.close()
|
||||||
|
|
||||||
|
def _open_connection(self):
|
||||||
|
cnx = odoo.sql_db.db_connect(self._uri, allow_uri=True)
|
||||||
|
self._cr = cnx.cursor()
|
||||||
|
self._cr.autocommit(True)
|
||||||
|
|
||||||
|
@with_cursor
|
||||||
|
def _setup_db(self):
|
||||||
|
self._cr.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS http_sessions (
|
||||||
|
sid varchar PRIMARY KEY,
|
||||||
|
write_date timestamp without time zone NOT NULL,
|
||||||
|
payload text NOT NULL
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
@with_cursor
|
||||||
|
def save(self, session):
|
||||||
|
payload = json.dumps(dict(session))
|
||||||
|
self._cr.execute("""
|
||||||
|
INSERT INTO http_sessions(sid, write_date, payload)
|
||||||
|
VALUES (%(sid)s, now() at time zone 'UTC', %(payload)s)
|
||||||
|
ON CONFLICT (sid)
|
||||||
|
DO UPDATE SET payload = %(payload)s,
|
||||||
|
write_date = now() at time zone 'UTC'
|
||||||
|
""", dict(sid=session.sid, payload=payload))
|
||||||
|
|
||||||
|
@with_cursor
|
||||||
|
def delete(self, session):
|
||||||
|
self._cr.execute("DELETE FROM http_sessions WHERE sid=%s", [session.sid])
|
||||||
|
|
||||||
|
@with_cursor
|
||||||
|
def get(self, sid):
|
||||||
|
self._cr.execute("UPDATE http_sessions SET write_date = now() at time zone 'UTC' WHERE sid=%s", [sid])
|
||||||
|
self._cr.execute("SELECT payload FROM http_sessions WHERE sid=%s", [sid])
|
||||||
|
try:
|
||||||
|
data = json.loads(self._cr.fetchone()[0])
|
||||||
|
except Exception:
|
||||||
|
return self.new()
|
||||||
|
|
||||||
|
return self.session_class(data, sid, False)
|
||||||
|
|
||||||
|
@with_cursor
|
||||||
|
def gc(self):
|
||||||
|
self._cr.execute(
|
||||||
|
"DELETE FROM http_sessions WHERE now() at time zone 'UTC' - write_date > '7 days'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def session_gc(session_store):
|
||||||
|
"""
|
||||||
|
Global cleaning of sessions using either the standard way (delete session files),
|
||||||
|
Or the DB way.
|
||||||
|
"""
|
||||||
|
if random.random() < 0.001:
|
||||||
|
# we keep session one week
|
||||||
|
if hasattr(session_store, 'gc'):
|
||||||
|
session_store.gc()
|
||||||
|
return
|
||||||
|
last_week = time.time() - 60*60*24*7
|
||||||
|
for fname in os.listdir(session_store.path):
|
||||||
|
path = os.path.join(session_store.path, fname)
|
||||||
|
try:
|
||||||
|
if os.path.getmtime(path) < last_week:
|
||||||
|
os.unlink(path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Root(http.Root):
|
||||||
|
@lazy_property
|
||||||
|
def session_store(self):
|
||||||
|
"""
|
||||||
|
Store sessions in DB rather than on FS if parameter permit so
|
||||||
|
"""
|
||||||
|
# Setup http sessions
|
||||||
|
session_db = odoo.tools.config.get('session_db')
|
||||||
|
if session_db:
|
||||||
|
_logger.debug("Sessions in db %s" % session_db)
|
||||||
|
return PGSessionStore(session_db, session_class=http.OpenERPSession)
|
||||||
|
path = odoo.tools.config.session_dir
|
||||||
|
_logger.debug('HTTP sessions stored in: %s', path)
|
||||||
|
return werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=http.OpenERPSession)
|
||||||
|
|
||||||
|
# #Monkey patch of standard methods
|
||||||
|
_logger.debug("Monkey patching sessions")
|
||||||
|
http.session_gc = session_gc
|
||||||
|
http.root = Root()
|
Loading…
Reference in New Issue