129 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			129 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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()
 | |
| 
 |