diff --git a/requirements.txt b/requirements.txt index 776e3a7..5a5b474 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ flask==2.0.1 pyyaml==5.4.1 pillow==8.3.2 selenium==3.141.0 +waitress==2.0.0 click==8.0.1 diff --git a/vasl_templates/main.py b/vasl_templates/main.py index 649dd33..2d33545 100755 --- a/vasl_templates/main.py +++ b/vasl_templates/main.py @@ -143,11 +143,18 @@ def _do_main( template_pack, default_scenario, remote_debugging, debug ): #pylin return 2 # start the webapp server - port = webapp.config[ "FLASK_PORT_NO" ] + flask_port = webapp.config[ "FLASK_PORT_NO" ] def webapp_thread(): """Run the webapp server.""" try: - webapp.run( host="localhost", port=port, use_reloader=False ) + import waitress + # FUDGE! Browsers tend to send a max. of 6-8 concurrent requests per server, so we increase + # the number of worker threads to avoid task queue warnings :-/ + nthreads = webapp.config.get( "WAITRESS_THREADS", 8 ) + waitress.serve( webapp, + host="localhost", port=flask_port, + threads=nthreads + ) except Exception as ex: #pylint: disable=broad-except logging.critical( "WEBAPP SERVER EXCEPTION: %s", ex ) logging.critical( traceback.format_exc() ) @@ -172,7 +179,7 @@ def _do_main( template_pack, default_scenario, remote_debugging, debug ): #pylin if _webapp_error: break try: - url = "http://localhost:{}/ping".format( port ) + url = "http://localhost:{}/ping".format( flask_port ) with urllib.request.urlopen( url ) as resp: resp_data = resp.read().decode( "utf-8" ) # we got a response - figure out if we connected to ourself or another instance @@ -210,7 +217,7 @@ def _do_main( template_pack, default_scenario, remote_debugging, debug ): #pylin disable_browser = webapp.config.get( "DISABLE_WEBENGINEVIEW" ) # run the application - url = "http://localhost:{}".format( port ) + url = "http://localhost:{}".format( flask_port ) from vasl_templates.main_window import MainWindow #pylint: disable=cyclic-import main_window = MainWindow( url, disable_browser ) main_window.show() diff --git a/vasl_templates/webapp/__init__.py b/vasl_templates/webapp/__init__.py index 97a43e5..03c6f8d 100644 --- a/vasl_templates/webapp/__init__.py +++ b/vasl_templates/webapp/__init__.py @@ -185,9 +185,6 @@ def _on_sigint( signum, stack ): #pylint: disable=unused-argument # --------------------------------------------------------------------- -# disable the Flask startup banner -flask.cli.show_server_banner = lambda *args: None - # initialize Flask app = Flask( __name__ ) if _is_flask_child_process(): diff --git a/vasl_templates/webapp/run_server.py b/vasl_templates/webapp/run_server.py index 7fd2c69..c218fb2 100755 --- a/vasl_templates/webapp/run_server.py +++ b/vasl_templates/webapp/run_server.py @@ -20,23 +20,23 @@ def main( bind_addr, force_init_delay, flask_debug ): # initialize from vasl_templates.webapp import app - port = None + flask_port = None if bind_addr: words = bind_addr.split( ":" ) - host = words[0] + flask_host = words[0] if len(words) > 1: - port = words[1] + flask_port = words[1] else: - host = app.config.get( "FLASK_HOST", "localhost" ) - if not port: - port = app.config.get( "FLASK_PORT_NO" ) + flask_host = app.config.get( "FLASK_HOST", "localhost" ) + if not flask_port: + flask_port = app.config.get( "FLASK_PORT_NO" ) if not flask_debug: flask_debug = app.config.get( "FLASK_DEBUG", False ) # validate the configuration - if not host: + if not flask_host: raise RuntimeError( "The server host was not set." ) - if not port: + if not flask_port: raise RuntimeError( "The server port was not set." ) # monitor extra files for changes @@ -61,15 +61,30 @@ def main( bind_addr, force_init_delay, flask_debug ): # it's useful to send a request (any request), since this will trigger "first request" initialization # (in particular, starting the download thread). time.sleep( force_init_delay ) - url = "http://{}:{}/ping".format( host, port ) + url = "http://{}:{}/ping".format( flask_host, flask_port ) with urllib.request.urlopen( url ) as resp: _ = resp.read() threading.Thread( target=_start_server, daemon=True ).start() # run the server - app.run( host=host, port=port, debug=flask_debug, - extra_files = extra_files - ) + if flask_debug: + # NOTE: It's useful to run the webapp using the Flask development server, since it will + # automatically reload itself when the source files change. + app.run( + host=flask_host, port=flask_port, + debug=flask_debug, + extra_files=extra_files + ) + else: + import waitress + # FUDGE! Browsers tend to send a max. of 6-8 concurrent requests per server, so we increase + # the number of worker threads to avoid task queue warnings :-/ + nthreads = app.config.get( "WAITRESS_THREADS", 8 ) + waitress.serve( app, + host=flask_host, port=flask_port, + threads=nthreads + ) + # --------------------------------------------------------------------- diff --git a/vasl_templates/webapp/vo_notes.py b/vasl_templates/webapp/vo_notes.py index 46f5799..d63568f 100644 --- a/vasl_templates/webapp/vo_notes.py +++ b/vasl_templates/webapp/vo_notes.py @@ -319,7 +319,16 @@ def get_vo_note( vo_type, nat, key ): # we have a cached copy - compare the timestamps of the source HTML and the cached image # NOTE: We should also check the HTML for any associated images, and check their timestamps, as well. if os.path.getmtime( cached_fname ) >= os.path.getmtime( vo_note["filename"] ): - resp = send_file( cached_fname ) + # FUDGE! We get errors on Windows when using waitress to serve the webapp, when the tests end + # and ControlTestsServicer tries to clean up its TemporaryDirectory ("not a directory" errors + # for something that is a file :-/). TemporaryDirectory added a ignore_cleanup_errors argument + # in Python 3.10, but for now, we work-around this problem by reading the file ourself and + # serving it from memory. + with open( cached_fname, "rb" ) as fp: + buf = fp.read() + resp = send_file( io.BytesIO( buf ), + download_name = os.path.basename( cached_fname ) + ) resp.headers[ "X-WasCached" ] = 1 return resp with WebDriver.get_instance( "vo_note" ) as webdriver: