From 80573a182b06325c3fcbbcdce4c7f10daed2f4fa Mon Sep 17 00:00:00 2001 From: Danny Staple Date: Fri, 26 Jun 2020 19:25:50 +0100 Subject: [PATCH 1/3] Allow Vpython to be used in a headless context. --- README.md | 9 +++++++++ vpython/no_notebook.py | 21 ++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4baa5b3..717c88a 100644 --- a/README.md +++ b/README.md @@ -92,3 +92,12 @@ If you execute build_original_no_overload.py, and change the statement "if True: Note that in site-packages/vpython/vpython_libraries it is glowcomm.html that is used by launchers such as idle or spyder; glowcomm.js is used with Jupyter notebook (and a modified version is used in Jupyterlab). Placing console.log(....) statements in the GlowScript code or in the JavaScript section of glowcomm.html can be useful in debugging. You may also need to put debugging statements into site-packages/vpython/vpython.py. + + +## Environment options + +The following options make sense for running VPython in a headless environment: + +* VPYTHON_PORT -> Specify the TCP/IP port number for the HTTP server. +* VPYTHON_NOBROWSER -> Set this to disable trying to launch a browser. + The link will be printed instead. diff --git a/vpython/no_notebook.py b/vpython/no_notebook.py index 8aacb4f..959aa93 100755 --- a/vpython/no_notebook.py +++ b/vpython/no_notebook.py @@ -64,7 +64,7 @@ def find_free_port(): return s.getsockname()[1] -__HTTP_PORT = find_free_port() +__HTTP_PORT = int(os.getenv("VPYTHON_PORT", find_free_port())) __SOCKET_PORT = find_free_port() try: @@ -248,18 +248,25 @@ def onClose(self, wasClean, code, reason): try: if platform.python_implementation() == 'PyPy': - server_address = ('', 0) # let HTTPServer choose a free port + server_address = ('', int(os.getenv("VPYTHON_PORT", 0))) # let HTTPServer choose a free port __server = HTTPServer(server_address, serveHTTP) port = __server.server_port # get the chosen port # Change the global variable to store the actual port used __HTTP_PORT = port - _webbrowser.open('http://localhost:{}'.format(port) - ) # or webbrowser.open_new_tab() + url = 'http://localhost:{}'.format(__HTTP_PORT) + if os.getenv("VPYTHON_NOBROWSER"): + print(url) + else: + _webbrowser.open(url) # or webbrowser.open_new_tab() else: __server = HTTPServer(('', __HTTP_PORT), serveHTTP) - # or webbrowser.open_new_tab() - if _browsertype == 'default': # uses default browser - _webbrowser.open('http://localhost:{}'.format(__HTTP_PORT)) + + url = 'http://localhost:{}'.format(__HTTP_PORT) + if os.getenv("VPYTHON_NOBROWSER"): + print(url) + else: + if _browsertype == 'default': # uses default browser + _webbrowser.open(url) # or webbrowser.open_new_tab() except: pass From 422ea25d7269424063fef05293eb6ed5582d34bb Mon Sep 17 00:00:00 2001 From: Danny Staple Date: Sun, 28 Jun 2020 00:04:34 +0100 Subject: [PATCH 2/3] Allow Vpython to be used in a headless context. Use the hostname to make the socket name. --- vpython/vpython_libraries/glowcomm.html | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/vpython/vpython_libraries/glowcomm.html b/vpython/vpython_libraries/glowcomm.html index 4ade8a6..8c88bd5 100644 --- a/vpython/vpython_libraries/glowcomm.html +++ b/vpython/vpython_libraries/glowcomm.html @@ -58,7 +58,15 @@ "use strict"; //var sock = socket_port() // created by no_notebook.py - ws = new WebSocket("ws://localhost:"+sock) + let ws_url; + if (document.location.hostname.includes("localhost")){ + ws_url = "ws://localhost:"+sock; + } + else { + ws_url = 'ws://' + document.location.hostname + ":" + sock; + } + + ws = new WebSocket(ws_url); ws.binaryType = "arraybuffer"; ws.onopen = function() { From 318fdbaa2bbd7977ded538355fd5c3b5471f2720 Mon Sep 17 00:00:00 2001 From: Danny Staple Date: Thu, 13 Aug 2020 16:12:48 +0100 Subject: [PATCH 3/3] Add some optional debug and logging to see what is going on. --- vpython/no_notebook.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/vpython/no_notebook.py b/vpython/no_notebook.py index 959aa93..ff05683 100755 --- a/vpython/no_notebook.py +++ b/vpython/no_notebook.py @@ -14,6 +14,7 @@ import copy import socket import multiprocessing +import logging import signal @@ -21,6 +22,7 @@ from .rate_control import rate +logger = logging.getLogger(__name__) # Redefine `Thread.run` to not show a traceback for Spyder when stopping # the server by raising a KeyboardInterrupt or SystemExit. @@ -119,6 +121,7 @@ class serveHTTP(BaseHTTPRequestHandler): 'ico': ['image/x-icon', serverdata]} def do_GET(self): + logger.debug("Doing get") global httpserving httpserving = True html = False @@ -135,6 +138,7 @@ def do_GET(self): mime = self.mimes[fext] # For example, mime[0] is image/jpg, # mime[1] is C:\Users\Bruce\Anaconda3\lib\site-packages\vpython\vpython_data + logger.debug("Sending response") self.send_response(200) self.send_header('Content-type', mime[0]) self.end_headers() @@ -153,6 +157,7 @@ def do_GET(self): self.wfile.write(glowcomm.encode('utf-8')) def log_message(self, format, *args): # this overrides server stderr output + logger.debug(format, *args) return # Requests from client to websocket server can be the following: @@ -245,6 +250,7 @@ def onClose(self, wasClean, code, reason): else: os.kill(os.getpid(), signal.SIGINT) +logger.info("Creating server") try: if platform.python_implementation() == 'PyPy': @@ -271,6 +277,7 @@ def onClose(self, wasClean, code, reason): except: pass +logger.info("Server created") if _browsertype == 'pyqt': if platform.python_implementation() == 'PyPy': @@ -294,7 +301,7 @@ def start_Qapp(port): filename = filepath + '/qtbrowser.py' os.system('python ' + filename + ' http://localhost:{}'.format(port)) - +logger.info("Starting serve forever loop") # create a browser in its own process if _browsertype == 'pyqt': __m = multiprocessing.Process(target=start_Qapp, args=(__HTTP_PORT,)) @@ -302,7 +309,7 @@ def start_Qapp(port): __w = threading.Thread(target=__server.serve_forever) __w.start() - +logger.info("Started") def start_websocket_server(): """ @@ -329,13 +336,13 @@ def start_websocket_server(): __interact_loop.run_until_complete(__coro) __interact_loop.run_forever() - +logger.debug("Starting WS server") # Put the websocket server in a separate thread running its own event loop. # That works even if some other program (e.g. spyder) already running an # async event loop. __t = threading.Thread(target=start_websocket_server) __t.start() - +logger.debug("WS Started") def stop_server(): """Shuts down all threads and exits cleanly."""