Skip to content

Conversation

@aleql
Copy link
Contributor

@aleql aleql commented Sep 22, 2025

This PR aims to fix the Socket.IO upload failures that were causing 500 errors when connecting. The root of the problem was a mismatch between the client and server socket paths. The client was attempting to connect using a sub-path such as /server/socket.io, while the server was only listening on the default /socket.io. This caused the upload handshake requests to hit an unhandled route, resulting in errors.

The issue was introduced in commit 78c48fa8 (“SocketIO Import and Create Project Fix (#263)”), when the serverUrl parameter was removed and replaced with a dynamically constructed socketPath based on mdvApiRoot. This change allowed the client to adapt to sub-path deployments, but the server-side initialization in websocket.py was not updated at the same time. The result was that the client and server were no longer aligned, and uploads stopped working.

The fix was to update the server configuration so that it also respects MDV_API_ROOT when setting up the Socket.IO instance. Now the server computes the socket path dynamically: it uses /socket.io when deployed at the root, and <MDV_API_ROOT>/socket.io when deployed under a sub-path such as /server/ or /carroll/. With this change, the client and server are once again in sync, and uploads succeed regardless of deployment structure.

Summary by CodeRabbit

  • Bug Fixes

    • Ensure real-time endpoints respect the configured API root so sockets work correctly when the app is served under a subpath.
  • New Features

    • Add optional URL-prefix handling at startup so requests are correctly routed when an API root is configured.
    • Improve socket resilience by enabling WebSocket transport with automatic reconnection and polling fallback.
  • Chores

    • Log the computed Socket.IO path and set a sensible default API root at startup for easier diagnostics.

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/) -> <subpath>/socket.io

This aligns the server with client behavior and restores upload functionality
across both root and sub-path deployments.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 22, 2025

Walkthrough

Adds prefix-stripping WSGI middleware and earlier MDV_API_ROOT handling, changes Socket.IO server initialization to use a path derived from app.config["mdv_api_root"], and updates the client socket to allow websocket + polling transports with automatic reconnection.

Changes

Cohort / File(s) Summary of Changes
WSGI prefix middleware & startup
python/mdvtools/dbutils/safe_mdv_app.py
Add PrefixMiddleware class to strip a configurable URL prefix (MDV_API_ROOT) from requests, apply it at import/startup when the env var is set, move default MDV_API_ROOT initialization earlier, remove duplicated inline middleware in __main__, and fix a docstring typo.
Socket.IO server init
python/mdvtools/websocket.py
Read mdv_api_root from app.config, normalize trailing slash (except root), construct path = mdv_api_root + "socket.io", initialize SocketIO with cors_allowed_origins="*" and the computed path, and log the computed path.
Socket.IO client config
src/charts/dialogs/SocketIOUploadClient.ts
Expand transports from ['polling'] to ['websocket','polling'] and enable reconnection: true, allowing websocket transport with polling fallback and automatic reconnection attempts.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Env as Environment
  participant App as Flask App (wsgi_app)
  participant Prefix as PrefixMiddleware
  participant Config as app.config
  participant WS as websocket.init (python/mdvtools/websocket.py)
  participant SIO as SocketIO server

  Env->>App: set MDV_API_ROOT (at import/startup)
  App->>Config: app.config["mdv_api_root"]
  Config-->>WS: mdv_api_root
  WS->>WS: normalize mdv_api_root (ensure trailing slash unless "/")
  WS->>SIO: SocketIO(app, cors_allowed_origins="*", path=mdv_api_root+"socket.io")
  Note right of WS: logs computed Socket.IO path
  App->>Prefix: wrap wsgi_app with PrefixMiddleware (if MDV_API_ROOT set)
  Prefix->>App: strip prefix from PATH_INFO / adjust SCRIPT_NAME or return 404
Loading
sequenceDiagram
  autonumber
  participant Client as SocketIO client
  participant Server as SocketIO server

  Client->>Server: connect (transports:['websocket','polling'])
  alt websocket available
    Client->>Server: websocket handshake
    Server-->>Client: upgrade to websocket
  else websocket blocked / fails
    Client->>Server: fallback to polling
  end
  Note right of Client: reconnection: true -> automatic retries on disconnect
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

I nibble code like clover bright,
I trim the path to make it right,
Socket hums where slashes meet,
Websocket leaps, polling keeps the beat,
A rabbit hops — reconnect in sight. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “Fix Socket.IO Uploads by Aligning Server/Client Paths” succinctly captures the primary goal of the changeset by indicating a fix to upload functionality through synchronization of socket paths between client and server. It directly references the cause of the upload failures without extraneous detail. It’s concise, specific, and clearly communicates the main change to any teammate reviewing history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
python/mdvtools/websocket.py (2)

51-51: Return the SocketIO instance for easier testing and DI.

Returning the instance enables simple assertions in tests and avoids reliance on the global.

Apply this diff:

-    log(f"socketio initialized path={socketio_path} cors={allowed_origins}")
+    log(f"socketio initialized path={socketio_path} cors={allowed_origins}")
+    return socketio

44-49: Make SocketIO path resolution resilient to config casing and missing slashes

Fallback to app.config['MDV_API_ROOT'] and enforce both leading and trailing slashes; routes.py already normalizes MDV_API_ROOT for /api_root but defensive normalization here is safe.

File: python/mdvtools/websocket.py (lines 44–49)

-    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"
+    api_root = app.config.get('mdv_api_root') or app.config.get('MDV_API_ROOT') or '/'
+    api_root = (api_root or '/').strip()
+    if api_root != '/':
+        if not api_root.startswith('/'):
+            api_root = '/' + api_root
+        if not api_root.endswith('/'):
+            api_root += '/'
+    socketio_path = f"{api_root}socket.io"
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7aaa0cc and 2579e58.

📒 Files selected for processing (1)
  • python/mdvtools/websocket.py (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: nawabfurquan
PR: Taylor-CCB-Group/MDV#263
File: src/charts/dialogs/SocketIOUploadClient.ts:213-219
Timestamp: 2025-09-17T13:35:04.309Z
Learning: The MDV_API_ROOT environment variable is now normalized server-side in routes.py (lines 69-72) to ensure it always starts and ends with "/", eliminating the need for client-side path sanitization in SocketIOUploadClient.
📚 Learning: 2025-09-17T13:35:04.309Z
Learnt from: nawabfurquan
PR: Taylor-CCB-Group/MDV#263
File: src/charts/dialogs/SocketIOUploadClient.ts:213-219
Timestamp: 2025-09-17T13:35:04.309Z
Learning: The MDV_API_ROOT environment variable is now normalized server-side in routes.py (lines 69-72) to ensure it always starts and ends with "/", eliminating the need for client-side path sanitization in SocketIOUploadClient.

Applied to files:

  • python/mdvtools/websocket.py
🧬 Code graph analysis (1)
python/mdvtools/websocket.py (2)
python/mdvtools/auth/tests/test_auth.py (1)
  • app (9-14)
python/mdvtools/server.py (1)
  • log (40-46)
🔇 Additional comments (1)
python/mdvtools/websocket.py (1)

50-50: Confirm Flask‑SocketIO path semantics for pinned versions

Some flask-socketio / python-socketio releases differ on whether the server-side path accepts a leading slash or a URL prefix (e.g., /server/socket.io). Confirm against the exact versions pinned in this repo and cite the official docs.

Location: python/mdvtools/websocket.py — line 50 (socketio = SocketIO(app, cors_allowed_origins="*", path=socketio_path))

Action: provide requirements.txt / pyproject.toml / poetry.lock or the flask-socketio and python-socketio version strings (or run pip show flask-socketio python-socketio) so I can verify and cite the matching docs.

Comment on lines +50 to +51
socketio = SocketIO(app, cors_allowed_origins="*", path=socketio_path)
log(f"socketio initialized with cors_allowed_origins wildcard and path={socketio_path}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid wildcard CORS in production; make origins configurable.

Using cors_allowed_origins="" is a security posture gap. Read allowed origins from config and fall back to "" only in dev/test.

Apply this diff:

-    socketio = SocketIO(app, cors_allowed_origins="*", path=socketio_path)
-    log(f"socketio initialized with cors_allowed_origins wildcard and path={socketio_path}")
+    origins_cfg = app.config.get("MDV_CORS_ALLOWED_ORIGINS") or app.config.get("mdv_cors_allowed_origins")
+    if isinstance(origins_cfg, str):
+        allowed_origins = [o.strip() for o in origins_cfg.split(",") if o.strip()]
+    else:
+        allowed_origins = origins_cfg
+    if not allowed_origins:
+        allowed_origins = "*" if (app.debug or app.testing) else []
+    socketio = SocketIO(app, cors_allowed_origins=allowed_origins, path=socketio_path)
+    log(f"socketio initialized path={socketio_path} cors={allowed_origins}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
socketio = SocketIO(app, cors_allowed_origins="*", path=socketio_path)
log(f"socketio initialized with cors_allowed_origins wildcard and path={socketio_path}")
origins_cfg = app.config.get("MDV_CORS_ALLOWED_ORIGINS") or app.config.get("mdv_cors_allowed_origins")
if isinstance(origins_cfg, str):
allowed_origins = [o.strip() for o in origins_cfg.split(",") if o.strip()]
else:
allowed_origins = origins_cfg
if not allowed_origins:
allowed_origins = "*" if (app.debug or app.testing) else []
socketio = SocketIO(app, cors_allowed_origins=allowed_origins, path=socketio_path)
log(f"socketio initialized path={socketio_path} cors={allowed_origins}")

@aleql
Copy link
Contributor Author

aleql commented Sep 25, 2025

PR update:

  • Reverted PR Update socket configuration #266: The previous PR disabled not only WebSocket-based upload, but also the retry feature of the SocketIO upload client. This forced uploads to use only polling and prevented reconnections, removing a core part of the feature.
  • Subpath support in safe_mdv_app: Added configuration so the Docker container can be run in a subpath by changing the setting MDV_API_ROOT=/ in the docker YAML file.

Forcing polling-only causes issues in dev environments due to CORS restrictions on HTTP requests. WebSockets are not affected by CORS in the same way and already fall back to polling if WebSocket fails. Removing WebSocket impacts dev uploads unnecessarily.
The same issue is observed in the BIA environment, where WebSocket uploads don’t seem to work. I recommend checking the BIA server configuration and verifying the Vite proxy settings for polling. WebSockets provide a more reliable and performant connection. Removing them reduces the robustness of the feature, whose purpose was to guarantee reliable connections.

Testing: confirmed uploads work under these environments with files up to 15 GB:

  • Docker default setup: frontend + backend on localhost:5055
  • Dev frontend (localhost:5170) uploading to backend on localhost:5055
  • Docker subpath setup: frontend + backend on localhost:5055/server/
  • Dev frontend (localhost:5170) uploading to backend running with subpath config on localhost:5055/test/

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2579e58 and 6d0e974.

📒 Files selected for processing (2)
  • python/mdvtools/dbutils/safe_mdv_app.py (1 hunks)
  • src/charts/dialogs/SocketIOUploadClient.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: nawabfurquan
PR: Taylor-CCB-Group/MDV#263
File: src/charts/dialogs/SocketIOUploadClient.ts:213-219
Timestamp: 2025-09-17T13:35:04.309Z
Learning: The MDV_API_ROOT environment variable is now normalized server-side in routes.py (lines 69-72) to ensure it always starts and ends with "/", eliminating the need for client-side path sanitization in SocketIOUploadClient.
📚 Learning: 2025-09-17T13:35:04.309Z
Learnt from: nawabfurquan
PR: Taylor-CCB-Group/MDV#263
File: src/charts/dialogs/SocketIOUploadClient.ts:213-219
Timestamp: 2025-09-17T13:35:04.309Z
Learning: The MDV_API_ROOT environment variable is now normalized server-side in routes.py (lines 69-72) to ensure it always starts and ends with "/", eliminating the need for client-side path sanitization in SocketIOUploadClient.

Applied to files:

  • python/mdvtools/dbutils/safe_mdv_app.py
🧬 Code graph analysis (1)
python/mdvtools/dbutils/safe_mdv_app.py (1)
python/mdvtools/socketio_upload.py (1)
  • initialize_socketio_upload (862-907)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: tsc
🔇 Additional comments (1)
src/charts/dialogs/SocketIOUploadClient.ts (1)

218-225: Websocket transport addition looks good

Adding 'websocket' ahead of 'polling' with reconnection enabled gives us the desired fallback chain and matches the server-side path fix. LGTM.

Comment on lines +37 to +45
# 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reapply PrefixMiddleware after setting default MDV_API_ROOT

When the module loads, we capture MDV_API_ROOT and (optionally) wrap app.wsgi_app. Later, inside the __main__ block, we set the default /test/ value if the variable was absent. Because the middleware was already skipped earlier, we never re-wrap the app after this assignment. As a result, running python safe_mdv_app.py with no pre-set env var leaves the server listening on the root path while the rest of the stack believes it’s under /test/, recreating the socket path mismatch we’re trying to solve.

Please move the prefix application into a helper that can be invoked both at module load and after setting the default, e.g.:

+def apply_prefix_middleware() -> None:
+    prefix = os.environ.get('MDV_API_ROOT', '')
+    if prefix.endswith('/'):
+        prefix = prefix[:-1]
+    if prefix and not isinstance(app.wsgi_app, PrefixMiddleware):
+        logger.info(f"Applying PrefixMiddleware with prefix: {prefix}")
+        app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix=prefix)
+
+apply_prefix_middleware()
...
     # Set default for local testing if not already set
     if 'MDV_API_ROOT' not in os.environ:
         os.environ['MDV_API_ROOT'] = '/test/'
+        apply_prefix_middleware()

This keeps the import-time behavior for deployments while ensuring the local default actually takes effect.

Also applies to: 58-66

🤖 Prompt for AI Agents
In python/mdvtools/dbutils/safe_mdv_app.py around lines 37-45 and 58-66, the
PrefixMiddleware is applied at import time using MDV_API_ROOT but later a
default is set in __main__, so when MDV_API_ROOT is absent the app never gets
wrapped; refactor by extracting the prefix-detection-and-application logic into
a small helper function (e.g., apply_prefix_middleware(app)) that reads
MDV_API_ROOT, normalizes it (remove trailing slash), logs, and wraps
app.wsgi_app only when non-empty, then call that helper both at module load (to
preserve deployment behavior) and again after setting the default in the
__main__ block so the default `/test/` will be applied when running the module
directly.

@xinaesthete
Copy link
Collaborator

So, now we're using the class PrefixMiddleware in all circumstances - which may be harmless, but it was only meant for testing purposes. I suspect there may be a risk of this causing trouble with deployments like bia, though - i.e. now the app may serve itself at something like /MDV_API_ROOT/MDV_API_ROOT, because there was already something causing one layer of that, with the configuration passed in the environment not having any effect on how the app was served, only on how the path was communicated to the front-end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants