commit
9416e691e5
|
@ -0,0 +1,85 @@
|
||||||
|
====================
|
||||||
|
Store sessions in DB
|
||||||
|
====================
|
||||||
|
|
||||||
|
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
|
!! changes will be overwritten. !!
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||||
|
:target: https://odoo-community.org/page/development-status
|
||||||
|
:alt: Beta
|
||||||
|
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
|
||||||
|
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
|
||||||
|
:alt: License: LGPL-3
|
||||||
|
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github
|
||||||
|
:target: https://github.com/OCA/server-tools/tree/16.0/session_db
|
||||||
|
:alt: OCA/server-tools
|
||||||
|
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
|
||||||
|
:target: https://translation.odoo-community.org/projects/server-tools-16-0/server-tools-16-0-session_db
|
||||||
|
:alt: Translate me on Weblate
|
||||||
|
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
|
||||||
|
:target: https://runbot.odoo-community.org/runbot/149/16.0
|
||||||
|
:alt: Try me on Runbot
|
||||||
|
|
||||||
|
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||||
|
|
||||||
|
Store sessions in a database instead of the filesystem.
|
||||||
|
|
||||||
|
**Table of contents**
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
Set this module in the server wide modules.
|
||||||
|
|
||||||
|
Set a ``SESSION_DB_URI`` environment variable as a full postgresql connection string,
|
||||||
|
like ``postgres://user:passwd@server/db`` or ``db``.
|
||||||
|
|
||||||
|
It is recommended to use a dedicated database for this module, and possibly a dedicated
|
||||||
|
postgres user for additional security.
|
||||||
|
|
||||||
|
Known issues / Roadmap
|
||||||
|
======================
|
||||||
|
|
||||||
|
This module does not work with multi-threaded workers, so it requires workers > 0.
|
||||||
|
|
||||||
|
Bug Tracker
|
||||||
|
===========
|
||||||
|
|
||||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
|
||||||
|
In case of trouble, please check there if your issue has already been reported.
|
||||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed
|
||||||
|
`feedback <https://github.com/OCA/server-tools/issues/new?body=module:%20session_db%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||||
|
|
||||||
|
Do not contact contributors directly about support or help with technical issues.
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
Authors
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
* Odoo SA
|
||||||
|
* ACSONE SA/NV
|
||||||
|
|
||||||
|
Maintainers
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module is maintained by the OCA.
|
||||||
|
|
||||||
|
.. image:: https://odoo-community.org/logo.png
|
||||||
|
:alt: Odoo Community Association
|
||||||
|
:target: https://odoo-community.org
|
||||||
|
|
||||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||||
|
mission is to support the collaborative development of Odoo features and
|
||||||
|
promote its widespread use.
|
||||||
|
|
||||||
|
This module is part of the `OCA/server-tools <https://github.com/OCA/server-tools/tree/16.0/session_db>`_ project on GitHub.
|
||||||
|
|
||||||
|
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
|
@ -0,0 +1 @@
|
||||||
|
from . import pg_session_store
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "Store sessions in DB",
|
||||||
|
"version": "16.0.1.0.0",
|
||||||
|
"author": "Odoo SA,ACSONE SA/NV,Odoo Community Association (OCA)",
|
||||||
|
"license": "LGPL-3",
|
||||||
|
"website": "https://github.com/OCA/server-tools",
|
||||||
|
"maintainers": ["sbidoul"],
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
# Copyright (c) Odoo SA 2017
|
||||||
|
# @author Nicolas Seinlet
|
||||||
|
# Copyright (c) ACSONE SA 2022
|
||||||
|
# @author Stéphane Bidoul
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
|
||||||
|
import odoo
|
||||||
|
from odoo import http
|
||||||
|
from odoo.tools._vendor import sessions
|
||||||
|
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(sessions.SessionStore):
|
||||||
|
def __init__(self, uri, session_class=None):
|
||||||
|
super().__init__(session_class)
|
||||||
|
self._uri = uri
|
||||||
|
self._cr = None
|
||||||
|
# FIXME This class is NOT thread-safe. Only use in worker mode
|
||||||
|
if odoo.tools.config["workers"] == 0:
|
||||||
|
raise ValueError("session_db requires multiple workers")
|
||||||
|
self._open_connection()
|
||||||
|
self._setup_db()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if self._cr is not None:
|
||||||
|
self._cr.close()
|
||||||
|
|
||||||
|
def _open_connection(self):
|
||||||
|
cnx = odoo.sql_db.db_connect(self._uri, allow_uri=True)
|
||||||
|
self._cr = cnx.cursor()
|
||||||
|
self._cr._cnx.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("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)
|
||||||
|
|
||||||
|
# This method is not part of the Session interface but is called nevertheless,
|
||||||
|
# so let's get it from FilesystemSessionStore.
|
||||||
|
rotate = http.FilesystemSessionStore.rotate
|
||||||
|
|
||||||
|
@with_cursor
|
||||||
|
def vacuum(self):
|
||||||
|
self._cr.execute(
|
||||||
|
"DELETE FROM http_sessions "
|
||||||
|
"WHERE now() at time zone 'UTC' - write_date > %s",
|
||||||
|
(f"{http.SESSION_LIFETIME} seconds",),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_original_session_store = http.root.__class__.session_store
|
||||||
|
|
||||||
|
|
||||||
|
@lazy_property
|
||||||
|
def session_store(self):
|
||||||
|
session_db_uri = os.environ.get("SESSION_DB_URI")
|
||||||
|
if session_db_uri:
|
||||||
|
_logger.debug("HTTP sessions stored in: db")
|
||||||
|
return PGSessionStore(session_db_uri, session_class=http.Session)
|
||||||
|
return _original_session_store.__get__(self, self.__class__)
|
||||||
|
|
||||||
|
|
||||||
|
# Monkey patch of standard methods
|
||||||
|
_logger.debug("Monkey patching session store")
|
||||||
|
http.root.__class__.session_store = session_store
|
||||||
|
# Reset the lazy property cache
|
||||||
|
vars(http.root).pop("session_store", None)
|
|
@ -0,0 +1,3 @@
|
||||||
|
Store sessions in a database instead of the filesystem. This simplifies the
|
||||||
|
configuration of horizontally scalable deployments, by avoiding the need for a
|
||||||
|
distributed filesystem to store the Odoo sessions.
|
|
@ -0,0 +1 @@
|
||||||
|
This module does not work with multi-threaded workers, so it requires workers > 0.
|
|
@ -0,0 +1,7 @@
|
||||||
|
Set this module in the server wide modules.
|
||||||
|
|
||||||
|
Set a ``SESSION_DB_URI`` environment variable as a full postgresql connection string,
|
||||||
|
like ``postgres://user:passwd@server/db`` or ``db``.
|
||||||
|
|
||||||
|
It is recommended to use a dedicated database for this module, and possibly a dedicated
|
||||||
|
postgres user for additional security.
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../session_db
|
|
@ -0,0 +1,6 @@
|
||||||
|
import setuptools
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
setup_requires=['setuptools-odoo'],
|
||||||
|
odoo_addon=True,
|
||||||
|
)
|
Loading…
Reference in New Issue