From 2579e5882d70d2545c0265effe0fad59d5d0e176 Mon Sep 17 00:00:00 2001 From: Alejandro Quijada Leyton Date: Mon, 22 Sep 2025 16:31:51 -0300 Subject: [PATCH 1/3] configure server to respect MDV_API_ROOT for path binding Previously the Socket.IO server always bound to the default path (/socket.io), while the client was updated to construct a sub-path (e.g. /server/socket.io) based on mdvApiRoot. This mismatch caused upload handshake requests to fail with 500 errors. The server initialization in websocket.py now computes the socket path dynamically from MDV_API_ROOT: - Root deployments -> /socket.io - Sub-path deployments (e.g. /server/, /carroll/) -> /socket.io This aligns the server with client behavior and restores upload functionality across both root and sub-path deployments. --- python/mdvtools/websocket.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/python/mdvtools/websocket.py b/python/mdvtools/websocket.py index eef06d9e2..ac796b27f 100644 --- a/python/mdvtools/websocket.py +++ b/python/mdvtools/websocket.py @@ -27,7 +27,7 @@ class SocketIOContextRequest(FlaskRequest): def mdv_socketio(app: Flask): """ Experimental and not to be trusted pending design work etc. - + Do we have a SocketIO for the entire app and route messages internally, - yes probably. What `path` should it use? We'll need to make sure it is compatible with @@ -40,8 +40,15 @@ def mdv_socketio(app: Flask): global socketio # allow cors for localhost:5170-5179 # cors = [f"http://localhost:{i}" for i in range(5170,5180)] - socketio = SocketIO(app, cors_allowed_origins="*") - log("socketio initialized with cors_allowed_origins wildcard") + + # Get the API root path for SocketIO path configuration + api_root = app.config.get('mdv_api_root', '/') + if api_root != '/' and not api_root.endswith('/'): + api_root += '/' + socketio_path = f"{api_root}socket.io" + + socketio = SocketIO(app, cors_allowed_origins="*", path=socketio_path) + log(f"socketio initialized with cors_allowed_origins wildcard and path={socketio_path}") # @socketio.on("message") # def message(data): From f4c355bf4d50bc2b07cf8613c7f55ec2f8cd8264 Mon Sep 17 00:00:00 2001 From: Alejandro Quijada Leyton Date: Thu, 25 Sep 2025 16:39:12 -0300 Subject: [PATCH 2/3] fix safe_mdv_app so that the prefix middleware is applied at the module level --- python/mdvtools/dbutils/safe_mdv_app.py | 67 ++++++++++++------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/python/mdvtools/dbutils/safe_mdv_app.py b/python/mdvtools/dbutils/safe_mdv_app.py index 552998f29..a9494b81c 100644 --- a/python/mdvtools/dbutils/safe_mdv_app.py +++ b/python/mdvtools/dbutils/safe_mdv_app.py @@ -7,10 +7,26 @@ logger.info("safe_mdv_app.py starting") +# https://stackoverflow.com/a/36033627/279703 +class PrefixMiddleware(object): + def __init__(self, app, prefix=''): + self.app = app + self.prefix = prefix + + def __call__(self, environ, start_response): + if environ['PATH_INFO'].startswith(self.prefix): + environ['PATH_INFO'] = environ['PATH_INFO'][len(self.prefix):] + environ['SCRIPT_NAME'] = self.prefix + return self.app(environ, start_response) + else: + response_body = b"This url does not belong to the app." + headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] + start_response('404 Not Found', headers) + return [response_body] + # Import the app at the module level (outside any conditionals) try: from mdvtools.dbutils.mdv_server_app import app - # app = socketio if app is None: logger.error("app from mdv_server_app is None - using fallback") app = Flask(__name__) @@ -18,52 +34,35 @@ logger.exception(f'Error importing mdv_app: {e}') app = Flask(__name__) +# Apply prefix middleware at module level - works for both direct run and WSGI deployment +prefix = os.environ.get('MDV_API_ROOT', '') +if prefix: + # Remove trailing slash from prefix if it exists + if prefix.endswith('/'): + prefix = prefix[:-1] + + logger.info(f"Applying PrefixMiddleware with prefix: {prefix}") + app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix=prefix) + # Keep the existing __main__ block for direct execution if __name__ == '__main__': """ Running the app in local debugger with a path prefix - This can be usefull for testing behaviour app running on a sub-path + This can be useful for testing behaviour app running on a sub-path """ from mdvtools.websocket import socketio from mdvtools.socketio_upload import initialize_socketio_upload - # https://stackoverflow.com/a/36033627/279703 - class PrefixMiddleware(object): - - def __init__(self, app, prefix=''): - self.app = app - self.prefix = prefix - - def __call__(self, environ, start_response): - if environ['PATH_INFO'].startswith(self.prefix): - environ['PATH_INFO'] = environ['PATH_INFO'][len(self.prefix):] - environ['SCRIPT_NAME'] = self.prefix - return self.app(environ, start_response) - else: - # It's good practice to ensure the Content-Length header is set. - # The Werkzeug BaseResponse object (which Flask uses) handles this automatically - # if the response is a string or a list of strings, but here we are returning a list of bytes. - response_body = b"This url does not belong to the app." - headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] - start_response('404 Not Found', headers) - return [response_body] logger.info("running as __main__") - os.environ['MDV_API_ROOT'] = '/test/' - - # Get the prefix from the environment variable - prefix = os.environ.get('MDV_API_ROOT', '') - # Remove trailing slash from prefix if it exists, as PATH_INFO usually starts with a slash - if prefix.endswith('/'): - prefix = prefix[:-1] - - if prefix: - logger.info(f"Applying PrefixMiddleware with prefix: {prefix}") - app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix=prefix) + # Set default for local testing if not already set + if 'MDV_API_ROOT' not in os.environ: + os.environ['MDV_API_ROOT'] = '/test/' # Register upload socket events if initialize_socketio_upload is not None: initialize_socketio_upload(app) - # app.run(host='0.0.0.0', debug=False, port=5056) + + # Run with socketio if available, otherwise fallback to Flask if socketio is not None: socketio.run(app, host='0.0.0.0', debug=False, port=5056) else: From 6d0e974f13d237d5df32ba008f9b27e7bbc7d5eb Mon Sep 17 00:00:00 2001 From: Alejandro Quijada Leyton Date: Thu, 25 Sep 2025 16:50:12 -0300 Subject: [PATCH 3/3] Revert "Update socket configuration (#266)" This reverts commit 7aaa0cc5ac20dd27a06d57e4b73ca139b3a89681. --- src/charts/dialogs/SocketIOUploadClient.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/charts/dialogs/SocketIOUploadClient.ts b/src/charts/dialogs/SocketIOUploadClient.ts index 60149ab23..90a14a540 100644 --- a/src/charts/dialogs/SocketIOUploadClient.ts +++ b/src/charts/dialogs/SocketIOUploadClient.ts @@ -217,10 +217,10 @@ export class SocketIOUploadClient { this.socket = io(`${this.config.namespace}`, { path: this.config.socketPath || '/socket.io', // '/test/socket.io' or '/carroll/socket.io' autoConnect: false, - transports: ['polling'], + transports: ['websocket', 'polling'], timeout: 60000, forceNew: true, - reconnection: false, + reconnection: true, reconnectionDelay: 1000, reconnectionAttempts: 5, });