from http.cookies import SimpleCookie from collections import namedtuple from collections.abc import Mapping from itertools import product, chain import time import random from . import httpd from . import Context, context_activate # Name generation for contexts and sessions def name_parts(): """ This generator will create an endless list of steadily increasing name part lists. """ # name parts letters = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'omicron', 'pi', 'rho', 'sigma', 'tau', 'upsilon', 'phi', 'chi', 'psi', 'omega'] colours = ['red', 'orange', 'yellow', 'green', 'blue', 'violet'] # randomize order random.shuffle(letters) random.shuffle(colours) # yield initial sequence (letter-colour) parts = [letters, colours] yield parts # forever generate longer sequences by appending the letter list # over and over. Note that this is the *same* letter list, so it will have # the exact order. while True: random.shuffle(letters) random.shuffle(colours) parts.append(letters) yield parts # construct an iterator that will endlessly generate names: # 1) for each parts list p in name_parts() we take the cartesian product # 2) the product iterators are generated by the for...in generator # 3) we chain these iterators so that when the first is exhausted, we can # continue with the second, etc. # 4) we map the function '-'.join over the list of parts from the chain names = map('-'.join, chain.from_iterable((product(*p) for p in name_parts()))) class SessionCookie(httpd.Filter): """ The actual HTTP filter that will apply the cookie handling logic to each request. This filter defers to the SessionManager with respect to the cookie name to use and the activation of sessions. """ def bind(self, manager): """Post constructor configuration of filter.""" self.manager = manager def handle(self): """ Determine if a cookie needs to be set and let the session manager handle activation. """ cookies = self.request.cookies morsel = cookies.get(self.manager.cookie) if not morsel: # Determine new cookie value = self.manager.generate_name() # Set new cookie cookies[self.manager.cookie] = value cookies[self.manager.cookie]['path'] = '/' # Send the new cookie as header self.request.send_header('Set-Cookie', cookies[self.manager.cookie].OutputString()) else: value = morsel.value self.manager.activate(value) class Session: """ The Session bookkeeping data. """ def __init__(self, context, seen): self.context = context self.seen = seen def activate(self): """Activate the session. Updates last seen time.""" self.seen = time.time() context_activate(self.context) class SessionManager: """ The SessionManager class. This class is callable so it can be used in place of a constructor in the configuration. """ def __init__(self, cookie_name): self.sessions = {} self.cookie = cookie_name def __call__(self, *args, **kwargs): handler = SessionCookie(*args, **kwargs) handler.bind(self) return handler def generate_name(self): result = next(names) while result in self.sessions: result = next(names) return result def _new_session(self, name): result = Session(Context(name=name, init_data={'name': name}), time.time()) result.context.start() return result def activate(self, name): if name not in self.sessions: self.sessions[name] = self._new_session(name) self.sessions[name].activate()