# coding=utf-8 from concurrent.futures import ThreadPoolExecutor import traceback from errors.session import * import server class CodeqService(object): """Base class for all CodeQ services. """ session_is_optional = False def process(self, request): pass class ProblemList(CodeqService): """List all available problems to the client. """ session_is_optional = True def process(self, request): js = request.data language = js.get('language') if language is None: request.reply({'code': 1, 'message': 'Language was not provided'}) else: request.reply({'code': 0, 'message': 'ok', 'problems': server.problems.list_problems(language)}) class Login(CodeqService): """Logs in a client, creating a new session. """ session_is_optional = True def process(self, request): js = request.data username = js.get('username') password = js.get('password') if username is None: request.reply({'code': 1, 'message': 'Username was not provided'}) elif password is None: request.reply({'code': 2, 'message': 'Password was not provided'}) else: try: session = server.user_session.authenticate_and_create_session(username, password) except AuthenticationFailed: request.reply({'code': 3, 'message': 'Username or password do not match'}) else: if request.session: request.session.destroy() request.reply({'code': 0, 'message': 'OK', 'sid':session.get_sid()}) class Activity(CodeqService): def process(self, request): js = request.data trace = js.get('trace') solution = js.get('solution') problem_id = js.get('problem_id') if (trace is not None) or (solution is not None): # we have something to do if problem_id is None: request.reply({'code': 1, 'message': 'Problem ID is missing'}) else: request.session.update_solution(problem_id, trace, solution) request.end() # no feedback, just acknowledge the reception class Query(CodeqService): def process(self, request): js = request.data step = js.get('step') if step is None: request.reply({'code': 1, 'message': '"step" is not set'}) else: problem_id = js.get('problem_id') if problem_id is None: request.reply({'code': 4, 'message': 'Problem ID not given'}) else: session = request.session trace = js.get('trace') prolog = session.get_prolog() program = None if step == 'run': program = js.get('program') query = js.get('query') if program is None: result = {'code': 2, 'message': 'No program specified'} elif query is None: result = {'code': 3, 'message': 'No query specified'} else: messages, status, have_more = prolog.run_for_user(session.get_uid(), problem_id, program, query) result = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} elif step == 'next': messages, status, have_more = prolog.step() result = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} elif step == 'end': messages, status, have_more = prolog.end() result = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} else: result = {'code': 5, 'message': 'Unknown prolog step: {0}'.format(step)} if program or trace: session.update_solution(problem_id, trace, program) request.reply(result) # Push user program to the Python interpreter to be exec'd. class PythonExec(CodeqService): def process(self, request): program = request.data.get('program') if program is None: request.reply({'code': 1, 'message': 'No program specified'}) else: python = request.session.get_python() python.exec(program) request.reply({'code': 0, 'message': 'ok'}) # Send an interrupt to the Python interpreter. class PythonStop(CodeqService): def process(self, request): python = request.session.get_python() python.stop() request.reply({'code': 0, 'message': 'ok'}) # Push stdin to the Python interpreter. class PythonPush(CodeqService): def process(self, request): text = request.data.get('text') if text is None: request.reply({'code': 1, 'message': 'No input specified'}) else: python = request.session.get_python() python.push(text) request.reply({'code': 0, 'message': 'ok'}) class Hint(CodeqService): def process(self, request): js = request.data language = js.get('language') problem_id = js.get('problem_id') program = js.get('program') if problem_id is None: request.reply({'code': 1, 'message': 'No problem ID specified'}) elif program is None: request.reply({'code': 2, 'message': 'No program specified'}) else: session = request.session lang_session = None if language == 'prolog': lang_session = session.get_prolog() elif language == 'python': lang_session = session.get_python() if lang_session is None: request.reply({'code': 3, 'message': 'Unknown language specified'}) else: hints = lang_session.hint(session.get_sid(), problem_id, program) request.reply({'code': 0, 'message': 'ok', 'hints': hints}) class Test(CodeqService): def process(self, request): js = request.data language = js.get('language') problem_id = js.get('problem_id') program = js.get('program') if problem_id is None: request.reply({'code': 1, 'message': 'No problem ID specified'}) elif program is None: request.reply({'code': 2, 'message': 'No program specified'}) else: session = request.session lang_session = None if language == 'prolog': lang_session = session.get_prolog() elif language == 'python': lang_session = session.get_python() if lang_session is None: request.reply({'code': 3, 'message': 'Unknown language specified'}) else: hints = lang_session.test(session.get_sid(), problem_id, program) request.reply({'code': 0, 'message': 'ok', 'hints': hints}) class GetProblem(CodeqService): def process(self, request): js = request.data language = js.get('language') problem_group = js.get('problem_group') problem = js.get('problem') if language is None: request.reply({'code': 1, 'message': 'Language identifier not given'}) elif problem_group is None: request.reply({'code': 2, 'message': 'Problem group identifier not given'}) elif problem is None: request.reply({'code': 3, 'message': 'Problem identifier not given'}) else: request.reply({'code': 0, 'message': 'ok', 'data': request.session.get_problem_data(language, problem_group, problem)}) # maps actions to their handlers incoming_handlers = { 'list_problems': ProblemList(), 'login': Login(), 'get_problem': GetProblem(), 'logout': None, 'activity': Activity(), 'query': Query(), 'python_exec': PythonExec(), 'python_push': PythonPush(), 'python_stop': PythonStop(), 'hint': Hint(), 'test': Test() } class Request(object): def __init__(self, tid, original_sid, session, data): """Creates a new request :param tid: communicator-level transaction ID (global relative to the specific communicator where it originated) :param original_sid: session ID, optional :param session: the actual session with the original_sid, if it exists; the processor may swap it for a new session :param data: the request data from the client :return: new instance """ self._tid = tid self._original_sid = original_sid self.session = session self.data = data self.is_finished = False def reply(self, data): """Reply to this request. :param data: the dictionary representing the reply, that will be converted to JSON :return: None """ if data is None: self.end() if self._original_sid is not None: sid = data.get('sid') if sid is None: data['sid'] = self._original_sid elif sid != self._original_sid: data['sid'] = self._original_sid data['new_sid'] = sid # it is important to reply with the same tid and sid parameters as were in the request, so message accounting doesn't get confused send(self._tid, self._original_sid, data) self.is_finished = True def end(self): """Conclude the request, without sending a response. This is to acknowledge that the response has been received. :return: None """ send(self._tid, self._original_sid, None) self.is_finished = True ########## low-level machinery, subject to change to support more than the single socket communicator ########## _executor = ThreadPoolExecutor(max_workers=100) def _invoke_handler(handler, request): try: print('Worker thread processing data={}'.format(str(request.data))) handler.process(request) if not request.is_finished: print('ERROR: the request was not concluded!') request.reply({'code': -1, 'message': 'Request processing did not provide a reply'}) print('Processing finished') except Exception as e: print('ERROR: data processing failed: ' + str(e)) traceback.print_exc() request.reply({'code': -1, 'message': 'Internal error: ' + str(e)}) def serve_request(json_obj): if not isinstance(json_obj, dict): raise RequestProcessingError('Require a request represented as a dict, instead got: ' + str(type(json_obj))) tid = json_obj.get('tid') # session ID and transaction ID uniquely identify a transaction sid = json_obj.get('sid') action = json_obj.get('action') if action is None: raise RequestProcessingError('Request does not contain an action') if not isinstance(action, str): raise RequestProcessingError('Requested action must be a string, got: ' + str(type(action))) handler = incoming_handlers.get(action) if handler is None: raise RequestProcessingError('No handler for ' + action) print("Attempting to serve action={}".format(action)) session = None if sid is None: if not handler.session_is_optional: raise RequestProcessingError('Request is missing a session-ID') else: del json_obj['sid'] try: session = server.user_session.get_session_by_id(sid) except NoSuchSession: if not handler.session_is_optional: raise RequestProcessingError('This user session has expired. Please log-in again.') _executor.submit(_invoke_handler, handler, Request(tid, sid, session, json_obj)) def send(tid, sid, json_obj): # just a proxy function for now print('Sending reply: {}'.format(str(json_obj))) server.socket.sendPacket(tid, sid, json_obj) def stop(): global _executor _executor.shutdown() _executor = None