use redis for session caching
This commit is contained in:
parent
3abc11d873
commit
8969cff842
4 changed files with 93 additions and 30 deletions
15
README.org
15
README.org
|
|
@ -60,9 +60,7 @@
|
||||||
|
|
||||||
A WSGI server like [[https://gunicorn.org/][Gunicorn]] can then be used to serve the app for example like this:
|
A WSGI server like [[https://gunicorn.org/][Gunicorn]] can then be used to serve the app for example like this:
|
||||||
|
|
||||||
: $ gunicorn "raincloud:create_app(base_path='public')"
|
: $ gunicorn "raincloud:create_app(base_path='public', secret_key='i_am_a_key', redis_url='redis://127.0.0.1:6379/0')"
|
||||||
|
|
||||||
*Note* that currently only one worker makes sense due to server side session caching.
|
|
||||||
|
|
||||||
*** NixOS
|
*** NixOS
|
||||||
|
|
||||||
|
|
@ -81,13 +79,16 @@
|
||||||
All configuration options are:
|
All configuration options are:
|
||||||
|
|
||||||
| Option | Description | Type | Default value | Example |
|
| Option | Description | Type | Default value | Example |
|
||||||
|-----------------+-----------------------------------+-------+---------------+----------------------|
|
|-----------------+---------------------------------------------------------------+-------+----------------------------+-------------------------------|
|
||||||
| =address= | Bind address of the server | =str= | =127.0.0.1= | =0.0.0.0= |
|
| =address= | Bind address of the server | =str= | =127.0.0.1= | =0.0.0.0= |
|
||||||
| =port= | Port on which the server listens | =int= | =8000= | =5000= |
|
| =port= | Port on which the server listens | =int= | =8000= | =5000= |
|
||||||
| =user= | User under which the server runs | =str= | =raincloud= | =alice= |
|
| =user= | User under which the server runs | =str= | =raincloud= | =alice= |
|
||||||
| =group= | Group under which the server runs | =str= | =raincloud= | =users= |
|
| =group= | Group under which the server runs | =str= | =raincloud= | =users= |
|
||||||
| =cloudName= | Name of the raincloud | =str= | =raincloud= | =bobsCloud= |
|
| =cloudName= | Name of the raincloud | =str= | =raincloud= | =bobsCloud= |
|
||||||
| =basePath= | Base path of the raincloud | =str= | | =/var/lib/raincloud= |
|
| =basePath= | Base path of the raincloud | =str= | | =/var/lib/raincloud= |
|
||||||
|
| =secretKey= | Flask secret key | =str= | | =i_am_a_key= |
|
||||||
|
| =redisUrl= | URL of redis database | =str= | =redis://127.0.0.1:6379/0= | =redis://my_db_server:6379/0= |
|
||||||
|
| =numWorkers= | Number of Gunicorn workers (recommendation is: 2 x #CPUs + 1) | =int= | =5= | =17= |
|
||||||
| =workerTimeout= | Gunicorn worker timeout | =int= | =300= | =360= |
|
| =workerTimeout= | Gunicorn worker timeout | =int= | =300= | =360= |
|
||||||
|
|
||||||
*** Docker
|
*** Docker
|
||||||
|
|
@ -111,13 +112,15 @@
|
||||||
|
|
||||||
** Configuration
|
** Configuration
|
||||||
|
|
||||||
/raincloud/ provides two configuration options which can be passed to =raincloud.create_app()=:
|
/raincloud/ provides four configuration options which can be passed to =raincloud.create_app()=:
|
||||||
|
|
||||||
- =base_path= :: Base path of the raincloud
|
- =base_path= :: Base path of the raincloud
|
||||||
|
- =secret_key= :: Flask secret key
|
||||||
|
- =redis_url= :: URL of redis database (default: =redis://127.0.0.1:6379/0=)
|
||||||
- =cloud_name= :: Cloud name (default: =raincloud=)
|
- =cloud_name= :: Cloud name (default: =raincloud=)
|
||||||
|
|
||||||
Set them for example like this:
|
Set them for example like this:
|
||||||
: >>> app = raincloud.create_app(base_path='/home/alice/public', cloud_name='myCloud')
|
: >>> app = raincloud.create_app(base_path='/home/alice/public', secret_key='i_am_a_key', redis_url='redis://127.0.0.1:6379/0', cloud_name='raincloud')
|
||||||
|
|
||||||
*** =rc.conf=
|
*** =rc.conf=
|
||||||
:properties:
|
:properties:
|
||||||
|
|
|
||||||
22
flake.nix
22
flake.nix
|
|
@ -62,6 +62,24 @@
|
||||||
description = "Base path of the raincloud";
|
description = "Base path of the raincloud";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
secretKey = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Flask secret key";
|
||||||
|
};
|
||||||
|
|
||||||
|
redisUrl = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "redis://127.0.0.1:6379/0";
|
||||||
|
description = "URL of redis database";
|
||||||
|
};
|
||||||
|
|
||||||
|
numWorkers = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 5;
|
||||||
|
example = 17;
|
||||||
|
description = "Number of Gunicorn workers (recommendation is: 2 x #CPUs + 1)";
|
||||||
|
};
|
||||||
|
|
||||||
workerTimeout = mkOption {
|
workerTimeout = mkOption {
|
||||||
type = types.int;
|
type = types.int;
|
||||||
default = 300;
|
default = 300;
|
||||||
|
|
@ -97,7 +115,8 @@
|
||||||
PermissionsStartOnly = true;
|
PermissionsStartOnly = true;
|
||||||
|
|
||||||
ExecStart = ''
|
ExecStart = ''
|
||||||
${gunicorn}/bin/gunicorn "raincloud:create_app('${cfg.basePath}', '${cfg.cloudName}')" \
|
${gunicorn}/bin/gunicorn "raincloud:create_app('${cfg.basePath}', '${cfg.secretKey}', '${cfg.redisUrl}', '${cfg.cloudName}')" \
|
||||||
|
--workers ${toString cfg.numWorkers} \
|
||||||
--timeout ${toString cfg.workerTimeout} \
|
--timeout ${toString cfg.workerTimeout} \
|
||||||
--bind=${cfg.address}:${toString cfg.port}
|
--bind=${cfg.address}:${toString cfg.port}
|
||||||
'';
|
'';
|
||||||
|
|
@ -143,6 +162,7 @@
|
||||||
python3
|
python3
|
||||||
python3Packages.flask
|
python3Packages.flask
|
||||||
python3Packages.gunicorn
|
python3Packages.gunicorn
|
||||||
|
python3Packages.redis
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,17 @@ import os
|
||||||
import werkzeug
|
import werkzeug
|
||||||
|
|
||||||
|
|
||||||
def create_app(base_path, cloud_name="raincloud"):
|
def create_app(
|
||||||
|
base_path, secret_key, redis_url="redis://127.0.0.1:6379/0", cloud_name="raincloud"
|
||||||
|
):
|
||||||
|
|
||||||
# Create app
|
# Create app
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config["SECRET_KEY"] = os.urandom(24)
|
app.config["SECRET_KEY"] = secret_key
|
||||||
|
|
||||||
# Create handlers
|
# Create handlers
|
||||||
dh = DirectoryHandler(base_path)
|
dh = DirectoryHandler(base_path)
|
||||||
sh = SessionHandler()
|
sh = SessionHandler(redis_url)
|
||||||
|
|
||||||
@app.route("/<directory>", methods=["GET", "POST"])
|
@app.route("/<directory>", methods=["GET", "POST"])
|
||||||
@app.route("/<directory>/<path:filename>", methods=["GET"])
|
@app.route("/<directory>/<path:filename>", methods=["GET"])
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,74 @@
|
||||||
from datetime import datetime, timedelta
|
import json
|
||||||
|
from redis import from_url
|
||||||
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class RaincloudNetworkException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SessionHandler:
|
class SessionHandler:
|
||||||
def __init__(self):
|
def __init__(self, redis_url="redis://127.0.0.1:6379/0"):
|
||||||
self.sessions = []
|
try:
|
||||||
|
self.redis = from_url(redis_url)
|
||||||
|
except Exception as ex:
|
||||||
|
raise RaincloudNetworkException(
|
||||||
|
f"Exception while connecting to redis: {ex}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.redis.get("raincloud_sessions"):
|
||||||
|
self.redis.set("raincloud_sessions", json.dumps([]))
|
||||||
|
|
||||||
|
def _get_sessions(self):
|
||||||
|
"""Get sessions from redis server."""
|
||||||
|
try:
|
||||||
|
return json.loads(self.redis.get("raincloud_sessions"))
|
||||||
|
except Exception as ex:
|
||||||
|
raise RaincloudNetworkException(
|
||||||
|
f"Exception while getting sessions from redis: {ex}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _save_sessions(self, sessions):
|
||||||
|
"""Save 'sessions' to redis."""
|
||||||
|
try:
|
||||||
|
self.redis.set("raincloud_sessions", json.dumps(sessions))
|
||||||
|
except Exception as ex:
|
||||||
|
raise RaincloudNetworkException(
|
||||||
|
f"Exception while saving sessions to redis: {ex}"
|
||||||
|
)
|
||||||
|
|
||||||
def create_session_id(self):
|
def create_session_id(self):
|
||||||
"""Create a new session ID."""
|
"""Create a new session ID."""
|
||||||
ids = [s[2] for s in self.sessions]
|
ids = [s[2] for s in self._get_sessions()]
|
||||||
id_ = uuid.uuid4()
|
id_ = uuid.uuid4()
|
||||||
while id_ in ids:
|
while id_ in ids:
|
||||||
id_ = uuid.uuid4()
|
id_ = uuid.uuid4()
|
||||||
return id_
|
return str(id_)
|
||||||
|
|
||||||
def add_session(self, directory, id_):
|
def add_session(self, directory, id_):
|
||||||
"""Add session with 'id_' allowing access to 'directory'."""
|
"""Add session with 'id_' allowing access to 'directory'."""
|
||||||
self.sessions.append((datetime.now(), directory, id_))
|
sessions = self._get_sessions()
|
||||||
|
sessions.append((time.time(), directory, id_))
|
||||||
|
self._save_sessions(sessions)
|
||||||
|
|
||||||
def clean_sessions(self):
|
def clean_sessions(self):
|
||||||
"""Remove all sessions which are older than one day."""
|
"""Remove all sessions which are older than one day."""
|
||||||
self.sessions = [
|
sessions = self._get_sessions()
|
||||||
s for s in self.sessions if s[0] > datetime.now() - timedelta(days=1)
|
sessions = [s for s in sessions if s[0] > time.time() - 86400]
|
||||||
]
|
self._save_sessions(sessions)
|
||||||
|
|
||||||
def validate_session(self, directory, id_):
|
def validate_session(self, directory, id_):
|
||||||
"""check if session with 'id_' is allowed to access 'directory'."""
|
"""check if session with 'id_' is allowed to access 'directory'."""
|
||||||
valid_dates = [s[0] for s in self.sessions if s[1] == directory and s[2] == id_]
|
valid_dates = [
|
||||||
if len(valid_dates) > 0 and valid_dates[0] > datetime.now() - timedelta(days=1):
|
s[0] for s in self._get_sessions() if s[1] == directory and s[2] == id_
|
||||||
|
]
|
||||||
|
if len(valid_dates) > 0 and valid_dates[0] > time.time() - 86400:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def delete_session(self, id_):
|
def delete_session(self, id_):
|
||||||
"""Delete session with 'id_'."""
|
"""Delete session with 'id_'."""
|
||||||
self.sessions = [s for s in self.sessions if s[2] != id_]
|
sessions = self._get_sessions()
|
||||||
|
sessions = [s for s in sessions if s[2] != id_]
|
||||||
|
self._save_sessions(sessions)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue