diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index ec27754..616f22d 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -18,11 +18,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 + - uses: actions/checkout@v6 + - name: Set up Python 3.11 + uses: actions/setup-python@v6 with: - python-version: "3.10" + python-version: "3.11" - name: Install dependencies run: | python -m pip install --upgrade pip @@ -35,10 +35,35 @@ jobs: # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics -# TODO to run tests we need bwrap, timeout and all executables: -# executables/clingo/clingo, executables/dlv/dlv, executables/dlv2/dlv2, executables/idlv/idlv -# sudo apt-get update -# sudo apt-get -y install bubblewrap + - name: Install bwrap + run: | + sudo apt-get update + sudo apt-get -y install bubblewrap + - name: Download timeout + run: | + mkdir -p executables + wget https://github.com/DeMaCS-UNICAL/PythonESE/releases/download/v0.1.0/timeout -O executables/timeout + chmod +x executables/timeout + - name: Download clingo + run: | + mkdir -p executables/clingo + wget https://github.com/DeMaCS-UNICAL/EmbASP-test-resources/blob/master/asp/executables/clingo/clingo_linux?raw=true -O executables/clingo/clingo + chmod +x executables/clingo/clingo + - name: Download dlv + run: | + mkdir -p executables/dlv + wget https://github.com/DeMaCS-UNICAL/EmbASP-test-resources/blob/master/asp/executables/dlv/dlv.x86-64-linux-elf-static.bin?raw=true -O executables/dlv/dlv + chmod +x executables/dlv/dlv + - name: Download dlv2 + run: | + mkdir -p executables/dlv2 + wget https://github.com/DeMaCS-UNICAL/EmbASP-test-resources/blob/master/asp/executables/dlv2/dlv2.linux.64?raw=true -O executables/dlv2/dlv2 + chmod +x executables/dlv2/dlv2 + - name: Download idlv + run: | + mkdir -p executables/idlv + wget https://github.com/DeMaCS-UNICAL/EmbASP-test-resources/blob/master/datalog/executables/idlv/idlv.linux.64?raw=true -O executables/idlv/idlv + chmod +x executables/idlv/idlv # - name: Test with unittest # run: | # python -m unittest discover -s tests -p '*_test.py' diff --git a/embasp_server_executor/ese_utils.py b/embasp_server_executor/ese_utils.py index e6ca32d..9cf99d2 100644 --- a/embasp_server_executor/ese_utils.py +++ b/embasp_server_executor/ese_utils.py @@ -120,6 +120,7 @@ def process_program_and_options(websocket, message: str): input_json = message websocket.write_message(get_output_data(model="Received command")) input_data = loads(input_json) + # FIXME As soon as we have the JSON schema, we should validate the input structure before processing `input_data` print("Message received: %s\n" % input_data) if not check_presence_of_parameters(input_data): diff --git a/embasp_server_executor/ese_websocket.py b/embasp_server_executor/ese_websocket.py index e874f58..eac63b0 100644 --- a/embasp_server_executor/ese_websocket.py +++ b/embasp_server_executor/ese_websocket.py @@ -1,13 +1,13 @@ -from asyncio import set_event_loop, new_event_loop +from asyncio import new_event_loop, set_event_loop from datetime import datetime from urllib.parse import urlparse -from tornado.websocket import WebSocketHandler - from embasp.base.callback import Callback +from tornado.ioloop import IOLoop +from tornado.websocket import WebSocketHandler from .ese_config import config as ec -from .ese_utils import process_program_and_options, get_output_data +from .ese_utils import get_output_data, process_program_and_options class ESEWebSocket(WebSocketHandler, Callback): @@ -15,13 +15,19 @@ class ESEWebSocket(WebSocketHandler, Callback): This is the WebSocket handler class. It has been extended to call EmbASP when a message is received. """ - # Uncomment this if you want to test out the script on localhost + io_loop: IOLoop = None + def check_origin(self, origin): - parsed_origin = urlparse(origin) - # # parsed_origin.netloc.lower() gives localhost:3333 - return parsed_origin.hostname in ec.cors_origins + try: + parsed_origin = urlparse(origin) + normalized_hostname = parsed_origin.hostname.lower().strip('.') + return normalized_hostname in (o.lower().strip('.') for o in ec.cors_origins) + except Exception: + return False def open(self): + # Capture the *current* IOLoop instance - this is the one running the WebSocket + self.io_loop = IOLoop.current() print("\n\n", datetime.now(), "\nWebSocket opened") def on_close(self): @@ -35,17 +41,30 @@ def callback(self, o): o_output, o_errors = "", "" if o.get_output() is not None: - if len(o.get_output()) > ec.max_chars_output: # Check if the solver output is too long + # Check if the solver output is too long + if len(o.get_output()) > ec.max_chars_output: o_output = (o.get_output()[:ec.max_chars_output] + "\n[...]\nDo you need more? Let us know") else: o_output = o.get_output() if o.get_errors() is not None: - if len(o.get_output()) > ec.max_chars_output: # Check if the solver output is too long + # Check if the solver error is too long + if len(o.get_errors()) > ec.max_chars_output: o_errors = (o.get_errors()[:ec.max_chars_output] + "\n[...]\nDo you need more? Let us know") else: o_errors = o.get_errors() - set_event_loop(new_event_loop()) - self.write_message(get_output_data(model=o_output, error=o_errors)) - print("Sent data", get_output_data(model=o_output, error=o_errors)) + # Use the stored loop + if self.io_loop is None: + print("Callback called before WebSocket opened!", file=sys.stderr) + return + + self.io_loop.add_callback(self._write_result, o_output, o_errors) + + def _write_result(self, model: str, error: str): + try: + data = get_output_data(model=model, error=error) + self.write_message(data) + print("Sent data", data) + except Exception as e: + print(f"Failed to write message: {e}", file=sys.stderr)