diff options
author | Martin <martin@leo.fri1.uni-lj.si> | 2015-09-15 12:14:30 +0200 |
---|---|---|
committer | Martin <martin@leo.fri1.uni-lj.si> | 2015-09-15 12:14:30 +0200 |
commit | 9df933ebc3ade50628a26af691693254ed1daa22 (patch) | |
tree | 26fa4689f581eda562fcfd881ea3c9cca7b4ecc5 | |
parent | b2b4e40e5333b3182d0a57052640e8a7ecae8619 (diff) | |
parent | cc3a807157d6d2d2c4830afcec90d6614e486b70 (diff) |
Merge branch 'master' of ssh://212.235.189.51:22122/codeq-server
-rw-r--r-- | client/__init__.py | 37 | ||||
-rw-r--r-- | readme.md | 34 | ||||
-rw-r--r-- | server/__init__.py | 41 | ||||
-rw-r--r-- | server/socket.py | 6 | ||||
-rw-r--r-- | web/main.js | 2 | ||||
-rw-r--r-- | wsgi_server.py | 252 |
6 files changed, 32 insertions, 340 deletions
diff --git a/client/__init__.py b/client/__init__.py deleted file mode 100644 index 1c5d693..0000000 --- a/client/__init__.py +++ /dev/null @@ -1,37 +0,0 @@ -# coding=utf-8 - -# TODO: this module is deprecated, remove it - -import multiprocessing.managers - -__all__ = ['get_session_by_id', 'get_or_create_session'] - -class CodeqManager(multiprocessing.managers.BaseManager): - pass - -CodeqManager.register('PrologSession') -CodeqManager.register('PythonSession') -CodeqManager.register('UserSession') -#CodeqManager.register('get_session_by_id') -#CodeqManager.register('get_or_create_session') -#CodeqManager.register('authenticate_and_create_session') -CodeqManager.register('Codeq') - -m = CodeqManager(address=('localhost', 16231), authkey=b'c0d3q3y') -m.connect() -codeq = m.Codeq() - -def get_session_by_id(sid): -# return m.get_session_by_id(sid) - return codeq.get_session_by_id(sid) - -def get_or_create_session(uid, username, sid=None): -# return m.get_or_create_session(uid, username, sid) - return codeq.get_or_create_session(uid, username, sid) - -def authenticate_and_create_session(username, password): -# return m.authenticate_and_create_session(username, password) - return codeq.authenticate_and_create_session(username, password) - -def list_problems(): - return codeq.list_problems() @@ -1,21 +1,41 @@ Deployment ========== +To use the correct SWI prolog package (>= 7.2) on Debian, add a custom +repository by creating the file /etc/apt/sources.list.d/prolog.list +containing the following 2 lines: + +deb http://ppa.launchpad.net/swi-prolog/stable/ubuntu trusty main +deb-src http://ppa.launchpad.net/swi-prolog/stable/ubuntu trusty main + +After that run the following sequence of shell statements to update +the package cache, register the new repository's key, and again refresh +the package cache using the additional key: + +apt-get update +apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EF8406856DBFCA18 +apt-get update + Install the following packages: - apache2 - - python3-falcon + - python3 (>= 3.4) - python3-ply - python3-psycopg2 - python3-termcolor - - python3-waitress - - swi-prolog-nox + - swi-prolog-nox (>= 7.2) - nodejs (>= 10.0.22) Settings: - point webroot to codeq-web - set up reverse proxy for /ws/ to the node server: + - using the command a2enmod enable apache modules: proxy, proxy_http, proxy_wstunnel, rewrite + - add to the apache configuration the following directives: + RewriteEngine on + RewriteCond %{REQUEST_URI} ^/ws/ [NC] + RewriteCond %{QUERY_STRING} transport=websocket [NC] + RewriteRule /(.*) ws://localhost:8083/$1 [P,L] ProxyPass /ws/ http://localhost:8083/ws/ ProxyPassReverse /ws/ http://localhost:8083/ws/ - set _path_prefix in server.problems @@ -25,10 +45,10 @@ Settings: Running: - - run prolog/runner/main.pl - - run python/runner/main.py - - run daemon.py - - start the node process (node web/main.js) + - run the prolog interpreter server: swipl prolog/runner/main.pl + - run the python interpreter server: python3 python/runner/main.py + - run the session daemon: python3 daemon.py + - run the web server for client communication: node web/main.js Misc. ===== diff --git a/server/__init__.py b/server/__init__.py index 861a7df..0007e0a 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -1,6 +1,5 @@ # coding=utf-8 -import multiprocessing.managers from . import user_session from . import prolog_session from . import python_session @@ -8,43 +7,3 @@ from . import socket import server.problems __all__ = ['socket', 'handlers', 'user_session', 'prolog_session', 'python_session', 'problems', 'start'] - - -# TODO: everything below is deprecated, remove it - -class Codeq(object): - _method_to_typeid_ = { - 'get_session_by_id': 'UserSession', - 'get_or_create_session': 'UserSession', - 'authenticate_and_create_session': 'UserSession' - } - - def get_session_by_id(self, sid): - return user_session.get_session_by_id(sid) - - def get_or_create_session(self, uid, username, sid=None): - return user_session.get_or_create_session(uid, username, sid) - - def authenticate_and_create_session(self, username, password): - return user_session.authenticate_and_create_session(username, password) - - def list_problems(self): - return server.problems.list_problems() - -class CodeqManager(multiprocessing.managers.BaseManager): - pass - -class UserSessionProxy(multiprocessing.managers.BaseProxy): - _method_to_typeid_ = {'get_prolog': 'PrologSession', 'get_python': 'PythonSession'} - -CodeqManager.register('PrologSession') -CodeqManager.register('PythonSession') -CodeqManager.register('UserSession', proxytype=UserSessionProxy) -#CodeqManager.register('get_session_by_id', callable=user_session.get_session_by_id, proxytype=UserSessionProxy) -#CodeqManager.register('get_or_create_session', callable=user_session.get_or_create_session, proxytype=UserSessionProxy) -#CodeqManager.register('authenticate_and_create_session', callable=user_session.authenticate_and_create_session, proxytype=UserSessionProxy) -CodeqManager.register('Codeq', callable=Codeq, method_to_typeid=Codeq._method_to_typeid_) - -def start(): - m = CodeqManager(address=('localhost', 16231), authkey=b'c0d3q3y') - m.get_server().serve_forever() diff --git a/server/socket.py b/server/socket.py index c248056..b184d4b 100644 --- a/server/socket.py +++ b/server/socket.py @@ -20,8 +20,10 @@ _transactions_to_socket = {} # keyed by tid, used only when there is no sid ava def processIncomingPacket(receiving_socket, packet): print('Decocoding JSON: {}'.format(packet)) obj = json.loads(packet) - if obj.get('type') == 'connect': + req_type = obj.get('type') # private (meta) requests have the 'type' + if req_type == 'connect': return # TODO: first packet is 'connect', providing the list of connected sessions to the peer + tid = obj.get('tid') # transaction ID if tid is None: raise Exception('Transaction ID is missing from the request') @@ -270,7 +272,7 @@ class Communication(SocketHandler): lock.release() for handler in handlers: handler.destroy() # destroy them all, even the server socket - # this is where everything is destroyed but us, so what follows is self-destruct + # this is where everything has been destroyed but us, so what follows is self-destruct lock.acquire() try: self._destroying = True diff --git a/web/main.js b/web/main.js index d2b84af..7f1b2e4 100644 --- a/web/main.js +++ b/web/main.js @@ -195,7 +195,7 @@ server.on('connection', function (socket) { }; socket.on('close', function (reason, description) { // description is optional - logger.debug('GUI socket closed'); + logger.debug('GUI socket closed: ' + reason); if (session.sid !== null) { if (sessions[session.sid] === session) delete sessions[session.sid]; sendDataToPython({'type': 'unregister', 'sid': session.sid}); diff --git a/wsgi_server.py b/wsgi_server.py deleted file mode 100644 index f754b14..0000000 --- a/wsgi_server.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/python3 -# coding=utf-8 - -# TODO: this module is deprecated, remove it -# TODO: all new development should occur in server/handlers.py instead - -import falcon -import json -import client -from errors.session import NoSuchSession, AuthenticationFailed - -MAX_REQUEST_LENGTH = 1024 * 1024 # 1 MB - -api = application = falcon.API() - - -def get_json_payload(req, session_is_optional=False): - content_type_parts = req.content_type.split(';') - content_type = content_type_parts[0].strip() - encoding = 'utf-8' - if len(content_type_parts) > 1: - for part in content_type_parts[1:]: - subparts = part.strip().split('=') - if (len(subparts) == 2) and subparts[0] == 'charset': - encoding = subparts[1] - if not ((content_type == 'application/json') or (content_type == 'text/json')): - raise falcon.HTTPUnsupportedMediaType('Unsupported content-type: {0}'.format(req.content_type)) - length = req.get_header('Content-Length') - if length is None: - raise falcon.HTTPLengthRequired('Requests without content-length are not accepted') - try: - l = int(length) - except: - raise falcon.HTTPBadRequest('Invalid Content-Length', 'The given Content-Length is not a number: {0}'.format(length)) - if l > MAX_REQUEST_LENGTH: - raise falcon.HTTPError(falcon.HTTP_413, 'Request Entity Too Large', 'Cannot accept the request of length {0}: maximum allowed content-length is {1}'.format(length, MAX_REQUEST_LENGTH)) - try: - txt = req.stream.read() - except Exception as e: - raise falcon.HTTPBadRequest('Error reading request', 'Error while reading the request body: {0}'.format(e.message)) - try: - js = json.loads(txt.decode(encoding=encoding, errors='replace')) - except ValueError as e: - raise falcon.HTTPBadRequest('Error parsing JSON payload', 'Error while parsing the request as JSON: {0}'.format(e.message)) - sid = js.get('sid') - if sid is None: - if session_is_optional: - return js, None - raise falcon.HTTPBadRequest('No Session', 'Request is missing a session-ID') - del js['sid'] - try: - session = client.get_session_by_id(sid) - except NoSuchSession: - if session_is_optional: - return js, None - raise falcon.HTTPBadRequest('Session Expired', 'This user session has expired. Please log-in again.') - return js, session - - -class CodeqService(object): - """Base class for all CodeQ services. - It only support the POST method and JSON data. - Handles JSON decoding and encoding, and session retrieval which can be optional. - """ - session_is_optional = False - - def on_post(self, req, resp): - js, session = get_json_payload(req, self.session_is_optional) - resp.body = json.dumps(self.process(js, session)) - resp.cache_control = 'private, no-cache, no-store' - resp.content_type = 'application/json' - resp.status = falcon.HTTP_200 - - def process(self, js, session): - raise falcon.HTTPNotFound() - -class ProblemList(CodeqService): - session_is_optional = True - - def process(self, js, session): - return {'code': 0, 'message': 'ok', 'problems': client.list_problems()} - -class Login(CodeqService): - session_is_optional = True - - def process(self, js, old_session): - username = js.get('username') - password = js.get('password') - if username is None: - return {'code': 1, 'message': 'Username was not provided'} - elif password is None: - return {'code': 2, 'message': 'Password was not provided'} - else: - try: - session = client.authenticate_and_create_session(username, password) - except AuthenticationFailed: - return {'code': 3, 'message': 'Username or password do not match'} - else: - if old_session: - old_session.destroy() - return {'code': 0, 'message': 'OK', 'sid':session.get_sid()} - -class Activity(CodeqService): - def process(self, js, session): - 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: - return {'code': 1, 'message': 'Problem ID is missing'} - else: - session.update_solution(problem_id, trace, solution) - -class Query(CodeqService): - def process(self, js, session): - step = js.get('step') - if step is None: - return {'code': 1, 'message': '"step" is not set'} - else: - problem_id = js.get('problem_id') - if problem_id is None: - return {'code': 4, 'message': 'Problem ID not given'} - 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) - return result - -# Pull stdout/stderr from the session's Python interpreter. -class PythonPull(CodeqService): - def process(self, js, session): - python = session.get_python() - output = python.pull() - return {'code': 0, 'message': 'ok', 'terminal': {'text': output if output else ''}} - -# Push stdin to the session's Python interpreter. -class PythonPush(CodeqService): - def process(self, js, session): - text = js.get('text') - if text is None: - return {'code': 1, 'message': 'No input specified'} - - python = session.get_python() - python.push(text) - return {'code': 0, 'message': 'ok'} - -class Hint(CodeqService): - def process(self, js, session): - language = js.get('language') - problem_id = js.get('problem_id') - program = js.get('program') - - if problem_id is None: - return {'code': 1, 'message': 'No problem ID specified'} - if program is None: - return {'code': 2, 'message': 'No program specified'} - - if language == 'prolog': - lang_session = session.get_prolog() - elif language == 'python': - lang_session = session.get_python() - else: - return {'code': 3, 'message': 'Unknown language specified'} - - hints = lang_session.hint(session.get_sid(), problem_id, program) - return {'code': 0, 'message': 'ok', 'hints': hints} - -class Test(CodeqService): - def process(self, js, session): - language = js.get('language') - problem_id = js.get('problem_id') - program = js.get('program') - - if problem_id is None: - return {'code': 1, 'message': 'No problem ID specified'} - if program is None: - return {'code': 2, 'message': 'No program specified'} - - if language == 'prolog': - lang_session = session.get_prolog() - elif language == 'python': - lang_session = session.get_python() - else: - return {'code': 3, 'message': 'Unknown language specified'} - - hints = lang_session.test(session.get_sid(), problem_id, program) - return {'code': 0, 'message': 'ok', 'hints': hints} - -class GetProblem(CodeqService): - def process(self, js, session): - language = js.get('language') - problem_group = js.get('problem_group') - problem = js.get('problem') - if language is None: - return {'code': 1, 'message': 'Language identifier not given'} - elif problem_group is None: - return {'code': 2, 'message': 'Problem group identifier not given'} - elif problem is None: - return {'code': 3, 'message': 'Problem identifier not given'} - else: - return {'code': 0, 'message': 'ok', 'data': session.get_problem_data(language, problem_group, problem)} - -problem_list = ProblemList() -api.add_route('/list_problems', problem_list) - -login = Login() -api.add_route('/login', login) - -activity = Activity() -api.add_route('/activity', activity) - -query = Query() -api.add_route('/query', query) - -api.add_route('/python_pull', PythonPull()) -api.add_route('/python_push', PythonPush()) - -hint = Hint() -api.add_route('/hint', hint) - -test = Test() -api.add_route('/test', test) - -get_problem = GetProblem() -api.add_route('/get_problem', get_problem) - -if __name__ == '__main__': - import logging - from waitress import serve - logger = logging.getLogger('waitress') - logger.setLevel(logging.DEBUG) - serve(application, host='0.0.0.0', port=8082) |