Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Multi User server with session management #391

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion jupyter_server/base/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
from jupyter_server.utils import ensure_async, url_path_join, url_is_absolute, url_escape, urldecode_unix_socket_path
from jupyter_server.services.security import csp_report_uri

from ..torndsession.session import SessionMixin

#-----------------------------------------------------------------------------
# Top-level handlers
#-----------------------------------------------------------------------------
Expand All @@ -52,7 +54,7 @@ def log():
else:
return app_log

class AuthenticatedHandler(web.RequestHandler):
class AuthenticatedHandler(SessionMixin, web.RequestHandler):
"""A RequestHandler with an authenticated user."""

@property
Expand Down Expand Up @@ -88,6 +90,18 @@ def set_default_headers(self):
# for example, so just ignore)
self.log.debug(e)

def get_session_id(self):
return self.web_session.id

def get_session(self):
return self.web_session

def get_sessions(self):
return self.web_session.driver._data_handler

def get_sessions_count(self):
return len(self.web_session.driver._data_handler)

def force_clear_cookie(self, name, path="/", domain=None):
"""Deletes the cookie with the given name.

Expand Down
11 changes: 9 additions & 2 deletions jupyter_server/serverapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,15 @@ def init_settings(self, jupyter_app, kernel_manager, contents_manager,
allow_password_change=jupyter_app.allow_password_change,
server_root_dir=root_dir,
jinja2_env=env,
terminals_available=terminado_available and jupyter_app.terminals_enabled,
serverapp=jupyter_app
terminals_available=False, # Set later if terminals are available
serverapp=jupyter_app,
session_settings = dict(
driver='memory',
driver_settings={'host': self},
force_persistence=True,
sid_name='torndsessionID',
session_lifetime=1800
),
)

# allow custom overrides for the tornado web app.
Expand Down
11 changes: 11 additions & 0 deletions jupyter_server/torndsession/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-

# Copyright 2014 Mitchell Chu

"""This is a Tornado Session Extension """

from __future__ import absolute_import, division, print_function, with_statement

version = "1.1.5.1"
version_info = (1, 1, 5, 1)
67 changes: 67 additions & 0 deletions jupyter_server/torndsession/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# Copyright (c) 2014 Mitchell Chu

import sys

# __all__ = (
# 'text_type', 'string_types', 'izip', 'iteritems', 'itervalues',
# 'with_metaclass',
# )

PY3 = sys.version_info >= (3,)

if PY3:
text_type = str
string_types = (str, )
integer_types = int
izip = zip
_xrange = range
MAXSIZE = sys.maxsize

def iteritems(o):
return iter(o.items())

def itervalues(o):
return iter(o.values())

def bytes_from_hex(h):
return bytes.fromhex(h)

def reraise(exctype, value, trace=None):
raise exctype(str(value)).with_traceback(trace)

def _unicode(s):
return s
else:
text_type = unicode
string_types = (basestring, )
integer_types = (int, long)
from itertools import izip
_xrange = xrange
MAXSIZE = sys.maxint

def b(s):
# See comments above. In python 2.x b('foo') is just 'foo'.
return s

def iteritems(o):
return o.iteritems()

def itervalues(o):
return o.itervalues()

def bytes_from_hex(h):
return h.decode('hex')

# "raise x, y, z" raises SyntaxError in Python 3
exec("""def reraise(exctype, value, trace=None):
raise exctype, str(value), trace
""")

_unicode = unicode


def with_metaclass(meta, base=object):
return meta("NewBase", (base,), {})
48 changes: 48 additions & 0 deletions jupyter_server/torndsession/driver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright @ 2014 Mitchell Chu

from __future__ import (absolute_import, division, print_function,
with_statement)


class SessionDriver(object):
'''
abstact class for all real session driver implements.
'''
def __init__(self, **settings):
self.settings = settings

def get(self, session_id):
raise NotImplementedError()

def save(self, session_id, session_data, expires=None):
raise NotImplementedError()

def clear(self, session_id):
raise NotImplementedError()

def remove_expires(self):
raise NotImplementedError()

class SessionDriverFactory(object):
'''
session driver factory
use input settings to return suitable driver's instance
'''
@staticmethod
def create_driver(driver, **settings):
module_name = 'jupyter_server.torndsession.%ssession' % driver.lower()
module = __import__(module_name, globals(), locals(), ['object'])
# must use this form.
# use __import__('torndsession.' + driver.lower()) just load torndsession.__init__.pyc
cls = getattr(module, '%sSession' % driver.capitalize())
if not 'SessionDriver' in [base.__name__ for base in cls.__bases__]:
raise InvalidSessionDriverException(
'%s not found in current driver implements ' % driver)
return cls


class InvalidSessionDriverException(Exception):
pass
80 changes: 80 additions & 0 deletions jupyter_server/torndsession/memorysession.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright @ 2014 Mitchell Chu

from __future__ import (absolute_import, division, print_function,
with_statement)

from datetime import datetime

from .driver import SessionDriver
from .session import SessionConfigurationError
from .compat import iteritems


class MemorySession(SessionDriver):
"""
save session data in process memory
"""

MAX_SESSION_OBJECTS = 1024
"""The max session objects save in memory.
when session objects count large than this value,
system will auto to clear the expired session data.
"""

def __init__(self, **settings):
# check settings
super(MemorySession, self).__init__(**settings)
host = settings.get("host")
if not host:
raise SessionConfigurationError(
'memory session driver can not found persistence position')
if not hasattr(host, "session_container"):
setattr(host, "session_container", {})
self._data_handler = host.session_container

def get(self, session_id):
"""
get session object from host.
"""
if session_id not in self._data_handler:
return {}

session_obj = self._data_handler[session_id]
now = datetime.utcnow()
expires = session_obj.get('__expires__', now)
if expires > now:
return session_obj
return {}

def save(self, session_id, session_data, expires=None):
"""
save session data to host.
if host's session objects is more then MAX_SESSION_OBJECTS
system will auto to clear expired session data.
after cleared, system will add current to session pool, however the pool is full.
"""
session_data = session_data or {}
if expires:
session_data.update(__expires__=expires)
if len(self._data_handler) >= self.MAX_SESSION_OBJECTS:
self.remove_expires()
if len(self._data_handler) >= self.MAX_SESSION_OBJECTS:
print("system session pool is full. need more memory to save session object.")
self._data_handler[session_id] = session_data

def clear(self, session_id):
if self._data_handler.haskey(session_id):
del self._data_handler[session_id]

def remove_expires(self):
keys = []
for key, val in iteritems(self._data_handler):
now = datetime.utcnow()
expires = val.get("__expires__", now)
if now >= expires:
keys.append(key)
for key in keys:
del self._data_handler[key]
Loading