class Session(object): """ Holds the HTTP session data """ last_id = 0 def __init__(self, key, client_info=None, **kwargs): Session.last_id += 1 self.id = Session.last_id self.key = key self.client_info = client_info or {} self.data = {} self.identity = None self.touch() self.active = True logging.info( 'Opening a new worker gate for session %s, client %s', self.id, self.client_info['address'], ) self.gate = WorkerGate(self, name='session %i' % self.id, log_tag='worker', **kwargs) self.gate.start() logging.debug('New session %s', self.id) def destroy(self): logging.debug('Destroying session %s', self.id) self.gate.stop() def deactivate(self): """ Marks this session as dead """ self.active = False def touch(self): """ Updates the "last used" timestamp """ self.timestamp = time.time() def is_dead(self): return not self.active or (time.time() - self.timestamp) > 3600 def set_cookie(self, http_context): """ Adds headers to :class:`aj.http.HttpContext` that set the session cookie """ cookie = Cookie('session', self.key, path='/', httponly=True).render_response() http_context.add_header('Set-Cookie', cookie)
class GateMiddleware(object): def __init__(self, context): self.context = context self.sessions = {} self.restricted_gate = WorkerGate(self, gateway_middleware=self, restricted=True, name='restricted session', log_tag='restricted') self.restricted_gate.start() def generate_session_key(self, env): h = str(random.random()) h += env.get('REMOTE_ADDR', '') h += env.get('HTTP_USER_AGENT', '') h += env.get('HTTP_HOST', '') return hashlib.sha1(h.encode('utf-8')).hexdigest() def vacuum(self): """ Eliminates dead sessions """ for session in [x for x in self.sessions.values() if x.is_dead()]: session.destroy() if session.key in self.sessions: del self.sessions[session.key] def destroy(self): for session in self.sessions.values(): session.deactivate() self.vacuum() def open_session(self, env, **kwargs): """ Creates a new session for the :class:`aj.http.HttpContext` """ max_sessions = aj.config.data['max_sessions'] if max_sessions and len(self.sessions) >= max_sessions: candidates = sorted(self.sessions.keys(), key=lambda k: -self.sessions[k].get_age()) victim = self.sessions[candidates[0]] logging.info("Closing session %s due to pool overflow" % victim.id) victim.deactivate() self.vacuum() client_info = { 'address': env.get('REMOTE_ADDR', None), } session_key = self.generate_session_key(env) session_max_time = aj.config.data['session_max_time'] session = Session(session_key, gateway_middleware=self, client_info=client_info, session_max_time=session_max_time, **kwargs) self.sessions[session_key] = session return session def obtain_session(self, env): cookie_str = env.get('HTTP_COOKIE', None) session = None if cookie_str: cookie = Cookies.from_request( cookie_str, ignore_bad_cookies=True, ).get('session', None) if cookie and cookie.value: if cookie.value in self.sessions: # Session found session = self.sessions[cookie.value] if session.is_dead(): session = None return session def broadcast_config_data(self): self.restricted_gate.send_config_data() for session in self.sessions.values(): if not session.is_dead(): session.gate.send_config_data() def handle(self, http_context): start_time = time.time() self.vacuum() session = self.obtain_session(http_context.env) gate = None if not session and aj.dev_autologin: username = pwd.getpwuid(os.geteuid()).pw_name logging.warn('Opening an autologin session for user %s', username) session = self.open_session(http_context.env, initial_identity=username) session.set_cookie(http_context) if session: session.touch() gate = session.gate else: gate = self.restricted_gate if http_context.path.startswith('/socket'): http_context.fallthrough(SocketIORouteHandler.get(self.context)) http_context.respond_ok() return 'Socket closed' request_object = { 'type': 'http', 'context': http_context.serialize(), } # await response try: timeout = 600 with Timeout(timeout) as t: q = gate.q_http_replies.register() rq = gate.stream.send(request_object) while True: resp = q.get(t) if resp.id == rq.id: break # pylint: disable=E0712 except Timeout: http_context.respond('504 Gateway Timeout') return [b'Worker timeout'] # --- if 'error' in resp.object: raise WorkerError(resp.object) for header in resp.object['headers']: http_context.add_header(*header) headers = dict(resp.object['headers']) if 'X-Session-Redirect' in headers: # new authenticated session username = headers['X-Session-Redirect'] logging.info('Opening a session for user %s', username) session = self.open_session( http_context.env, initial_identity=username, auth_info=headers['X-Auth-Info'], ) session.set_cookie(http_context) http_context.respond(resp.object['status']) content = resp.object['content'] end_time = time.time() logging.debug('%.03fs %12s %s %s %s', end_time - start_time, str_fsize(len(content[0] if content else [])), str(http_context.status).split()[0], http_context.env['REQUEST_METHOD'], http_context.path) for index, item in enumerate(content): if isinstance(item, six.text_type): content[index] = item.encode('utf-8') # Touch the session in case system time has dramatically # changed during request if session: session.touch() return content
class GateMiddleware(object): def __init__(self, context): self.context = context self.sessions = {} self.restricted_gate = WorkerGate( self, gateway_middleware=self, restricted=True, name='restricted session', log_tag='restricted' ) self.restricted_gate.start() def generate_session_key(self, env): h = str(random.random()) h += env.get('REMOTE_ADDR', '') h += env.get('HTTP_USER_AGENT', '') h += env.get('HTTP_HOST', '') return hashlib.sha1(h.encode('utf-8')).hexdigest() def vacuum(self): """ Eliminates dead sessions """ for session in [x for x in self.sessions.values() if x.is_dead()]: session.destroy() if session.key in self.sessions: del self.sessions[session.key] def destroy(self): for session in self.sessions.values(): session.deactivate() self.vacuum() def open_session(self, env, **kwargs): """ Creates a new session for the :class:`aj.http.HttpContext` """ max_sessions = aj.config.data['max_sessions'] if max_sessions and len(self.sessions) >= max_sessions: candidates = sorted(self.sessions.keys(), key=lambda k: -self.sessions[k].get_age()) victim = self.sessions[candidates[0]] logging.info("Closing session %s due to pool overflow" % victim.id) victim.deactivate() self.vacuum() client_info = { 'address': env.get('REMOTE_ADDR', None), } session_key = self.generate_session_key(env) session = Session(session_key, gateway_middleware=self, client_info=client_info, **kwargs) self.sessions[session_key] = session return session def obtain_session(self, env): cookie_str = env.get('HTTP_COOKIE', None) session = None if cookie_str: cookie = Cookies.from_request( cookie_str, ignore_bad_cookies=True, ).get('session', None) if cookie and cookie.value: if cookie.value in self.sessions: # Session found session = self.sessions[cookie.value] if session.is_dead(): session = None return session def broadcast_config_data(self): self.restricted_gate.send_config_data() for session in self.sessions.values(): if not session.is_dead(): session.gate.send_config_data() def handle(self, http_context): start_time = time.time() self.vacuum() session = self.obtain_session(http_context.env) gate = None if not session and aj.dev_autologin: username = pwd.getpwuid(os.geteuid()).pw_name logging.warn('Opening an autologin session for user %s', username) session = self.open_session( http_context.env, initial_identity=username ) session.set_cookie(http_context) if session: session.touch() gate = session.gate else: gate = self.restricted_gate if http_context.path.startswith('/socket'): http_context.fallthrough(SocketIORouteHandler.get(self.context)) http_context.respond_ok() return 'Socket closed' request_object = { 'type': 'http', 'context': http_context.serialize(), } # await response try: timeout = 60 with Timeout(timeout) as t: q = gate.q_http_replies.register() rq = gate.stream.send(request_object) while True: resp = q.get(t) if resp.id == rq.id: break # pylint: disable=E0712 except Timeout: http_context.respond('504 Gateway Timeout') return 'Worker timeout' # --- if 'error' in resp.object: raise WorkerError(resp.object) for header in resp.object['headers']: http_context.add_header(*header) if header[0] == 'X-Session-Redirect': # new authenticated session username = header[1] logging.info('Opening a session for user %s', username) session = self.open_session( http_context.env, initial_identity=username ) session.set_cookie(http_context) http_context.respond(resp.object['status']) content = resp.object['content'] end_time = time.time() logging.debug( '%.03fs %12s %s %s %s', end_time - start_time, str_fsize(len(content[0] if content else [])), str(http_context.status).split()[0], http_context.env['REQUEST_METHOD'], http_context.path ) for index, item in enumerate(content): if isinstance(item, six.text_type): content[index] = item.encode('utf-8') return content
class Session(object): """ Holds the HTTP session data """ last_id = 0 def __init__(self, key, client_info=None, **kwargs): Session.last_id += 1 self.id = Session.last_id self.key = key self.client_info = client_info or {} self.data = {} self.identity = None self.touch() self.active = True logging.info( 'Opening a new worker gate for session %s, client %s', self.id, self.client_info['address'], ) self.gate = WorkerGate( self, name='session %i' % self.id, log_tag='worker', **kwargs ) self.gate.start() logging.debug('New session %s', self.id) def destroy(self): logging.debug('Destroying session %s', self.id) self.gate.stop() def deactivate(self): """ Marks this session as dead """ self.active = False def touch(self): """ Updates the "last used" timestamp """ self.timestamp = time.time() def get_age(self): return time.time() - self.timestamp def is_dead(self): return not self.active or self.get_age() > 3600 def set_cookie(self, http_context): """ Adds headers to :class:`aj.http.HttpContext` that set the session cookie """ cookie = Cookie( 'session', self.key, path='/', httponly=True ).render_response() http_context.add_header('Set-Cookie', cookie)
class GateMiddleware(object): def __init__(self, context): self.context = context self.sessions = {} self.restricted_gate = WorkerGate( self, restricted=True, name='restricted session', log_tag='restricted' ) self.restricted_gate.start() def generate_session_key(self, env): h = str(random.random()) h += env.get('REMOTE_ADDR', '') h += env.get('HTTP_USER_AGENT', '') h += env.get('HTTP_HOST', '') return hashlib.sha1(h).hexdigest() def vacuum(self): """ Eliminates dead sessions """ for session in [x for x in self.sessions.values() if x.is_dead()]: session.destroy() del self.sessions[session.key] def destroy(self): for session in self.sessions.values(): session.deactivate() self.vacuum() def open_session(self, env, **kwargs): """ Creates a new session for the :class:`aj.http.HttpContext` """ client_info = { 'address': env.get('REMOTE_ADDR', None), } session_key = self.generate_session_key(env) session = Session(session_key, client_info=client_info, **kwargs) self.sessions[session_key] = session return session def obtain_session(self, env): cookie_str = env.get('HTTP_COOKIE', None) session = None if cookie_str: cookie = Cookies.from_request( cookie_str, ignore_bad_cookies=True, ).get('session', None) if cookie and cookie.value: if cookie.value in self.sessions: # Session found session = self.sessions[cookie.value] if session.is_dead(): session = None return session def handle(self, http_context): start_time = time.time() self.vacuum() session = self.obtain_session(http_context.env) gate = None if not session and aj.dev_autologin: username = pwd.getpwuid(os.geteuid()).pw_name logging.warn('Opening an autologin session for user %s', username) session = self.open_session( http_context.env, initial_identity=username ) session.set_cookie(http_context) if session: session.touch() gate = session.gate else: gate = self.restricted_gate if http_context.path.startswith('/socket'): http_context.fallthrough(SocketIORouteHandler.get(self.context)) http_context.respond_ok() return 'Socket closed' request_object = { 'type': 'http', 'context': http_context.serialize(), } # await response try: timeout = 60 with Timeout(timeout) as t: q = gate.q_http_replies.register() rq = gate.stream.send(request_object) while True: resp = q.get(t) if resp.id == rq.id: break # pylint: disable=E0712 except Timeout: http_context.respond('504 Gateway Timeout') return 'Worker timeout' # --- if 'error' in resp.object: raise WorkerError(resp.object) for header in resp.object['headers']: http_context.add_header(*header) if header[0] == 'X-Session-Redirect': # new authenticated session username = header[1] logging.info('Opening a session for user %s', username) session = self.open_session( http_context.env, initial_identity=username ) session.set_cookie(http_context) http_context.respond(resp.object['status']) content = resp.object['content'] end_time = time.time() logging.debug( '%.03fs %12s %s %s %s', end_time - start_time, str_fsize(len(content[0] if content else [])), str(http_context.status).split()[0], http_context.env['REQUEST_METHOD'], http_context.path ) return content
class Session(object): """ Holds the HTTP session data """ last_id = 0 def __init__(self, key, gateway_middleware=None, client_info=None, auth_info=None, session_max_time=3600, **kwargs): Session.last_id += 1 self.id = Session.last_id self.key = key self.client_info = client_info or {} self.data = {} self.identity = kwargs[ 'initial_identity'] if 'initial_identity' in kwargs.keys( ) else None self.auth_info = auth_info self.touch() self.active = True logging.info( 'Opening a new worker gate for session %s, client %s', self.id, self.client_info['address'], ) self.gate = WorkerGate(self, gateway_middleware=gateway_middleware, name='session %i' % self.id, log_tag='worker', **kwargs) self.session_max_time = session_max_time self.gate.start() logging.debug('New session %s', self.id) def destroy(self): logging.debug('Destroying session %s', self.id) self.gate.stop() def deactivate(self): """ Marks this session as dead """ self.active = False def touch(self): """ Updates the "last used" timestamp """ self.timestamp = time.time() def get_age(self): return time.time() - self.timestamp def is_dead(self): return not self.active or self.get_age() > self.session_max_time def set_cookie(self, http_context): """ Adds headers to :class:`aj.http.HttpContext` that set the session cookie """ cookie = Cookie('session', self.key, path='/', httponly=True).render_response() http_context.add_header('Set-Cookie', cookie) def serialize(self): return { 'key': self.key, 'identity': self.identity, 'timestamp': self.timestamp, 'client_info': self.client_info }