commit a91290f045a72de724941526ce60db634a05b85e
parent 470f3ab26c412d197a5fde41c8e5e1b99cd4c781
Author: AMIT DUTTA <amitdutta4255@gmail.com>
Date: Sat, 10 Jan 2026 21:07:14 +0530
Refactor server to use AIOHTTP for WebSocket handling
Diffstat:
| M | backend.py | | | 295 | ++++++++++++++++++++++++++++++++++++++++--------------------------------------- |
1 file changed, 149 insertions(+), 146 deletions(-)
diff --git a/backend.py b/backend.py
@@ -1,5 +1,5 @@
import asyncio
-import websockets
+from aiohttp import web
import json
import subprocess
import os
@@ -8,13 +8,12 @@ import platform
import threading
import re
import importlib.util
-import http
# ==================================================================================
-# CLOUD COMPILER SERVER (Python, C, C++) - RENDER COMPATIBLE
+# CLOUD COMPILER SERVER (AIOHTTP) - RENDER COMPATIBLE
# ==================================================================================
-def install_package(package_name, websocket=None, loop=None):
+def install_package(package_name, ws=None, loop=None):
"""Attempt to install a python package via pip."""
try:
if importlib.util.find_spec(package_name) is not None:
@@ -22,9 +21,9 @@ def install_package(package_name, websocket=None, loop=None):
msg = f"Installing missing package: {package_name}..."
print(msg)
- if websocket and loop:
+ if ws and loop:
asyncio.run_coroutine_threadsafe(
- websocket.send(json.dumps({'type': 'status', 'msg': msg})),
+ ws.send_json({'type': 'status', 'msg': msg}),
loop
)
@@ -32,13 +31,13 @@ def install_package(package_name, websocket=None, loop=None):
except Exception as e:
error_msg = f"Failed to install {package_name}: {e}"
print(error_msg)
- if websocket and loop:
+ if ws and loop:
asyncio.run_coroutine_threadsafe(
- websocket.send(json.dumps({'type': 'stdout', 'data': f"\n[Error] {error_msg}\n"})),
+ ws.send_json({'type': 'stdout', 'data': f"\n[Error] {error_msg}\n"}),
loop
)
-def check_and_install_packages(code, websocket=None, loop=None):
+def check_and_install_packages(code, ws=None, loop=None):
"""Scan Python code for imports and install them if missing."""
imports = re.findall(r'^\s*import\s+(\w+)', code, re.MULTILINE)
from_imports = re.findall(r'^\s*from\s+(\w+)', code, re.MULTILINE)
@@ -46,10 +45,19 @@ def check_and_install_packages(code, websocket=None, loop=None):
for pkg in unique_packages:
if pkg in ['os', 'sys', 'time', 'random', 'math', 'json', 'asyncio', 'threading', 'platform', 'subprocess', 're']:
continue
- install_package(pkg, websocket, loop)
+ install_package(pkg, ws, loop)
-async def run_code(websocket):
- print(f"Client connected: {websocket.remote_address}")
+async def handle_client(request):
+ # --- HEALTH CHECK HANDLING ---
+ # Render sends HEAD/GET to root. If not a websocket upgrade, return OK.
+ if request.headers.get("Upgrade", "").lower() != "websocket":
+ return web.Response(text="OK")
+
+ # --- WEBSOCKET HANDLING ---
+ ws = web.WebSocketResponse()
+ await ws.prepare(request)
+
+ print(f"Client connected: {request.remote}")
process = None
# Helper to read output stream in a separate thread
@@ -60,155 +68,150 @@ async def run_code(websocket):
char = stream.read(1)
if not char:
break
+ # aiohttp's send_json is a coroutine
asyncio.run_coroutine_threadsafe(
- websocket.send(json.dumps({'type': 'stdout', 'data': char})),
+ ws.send_json({'type': 'stdout', 'data': char}),
loop
)
except Exception:
pass
try:
- async for message in websocket:
- data = json.loads(message)
-
- if data.get('type') == 'run':
- code = data.get('code')
- language = data.get('language', 'python')
+ async for msg in ws:
+ if msg.type == web.WSMsgType.TEXT:
+ data = json.loads(msg.data)
- # --- PYTHON HANDLING ---
- if language == 'python':
- await websocket.send(json.dumps({'type': 'status', 'msg': 'Checking dependencies...'}))
- # Run package check in executor to avoid blocking main loop
- loop = asyncio.get_running_loop()
- await loop.run_in_executor(None, check_and_install_packages, code, websocket, loop)
-
- filename = "temp_script.py"
- with open(filename, "w", encoding="utf-8") as f:
- f.write(code)
-
- env = os.environ.copy()
- env["PYTHONIOENCODING"] = "utf-8"
-
- process = subprocess.Popen(
- [sys.executable, "-u", filename],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- text=True,
- bufsize=0,
- encoding='utf-8',
- env=env
- )
- await websocket.send(json.dumps({'type': 'status', 'msg': 'Running Python...'}))
-
- # --- C HANDLING ---
- elif language == 'c':
- filename = "temp_code.c"
- executable = "./a.out" if platform.system() != "Windows" else "a.exe"
-
- with open(filename, "w", encoding="utf-8") as f:
- f.write(code)
-
- await websocket.send(json.dumps({'type': 'status', 'msg': 'Compiling C...'}))
-
- compile_process = subprocess.run(
- ["gcc", filename, "-o", executable],
- capture_output=True,
- text=True
- )
-
- if compile_process.returncode != 0:
- await websocket.send(json.dumps({'type': 'stdout', 'data': f"Compilation Error:\n{compile_process.stderr}"}))
- continue
-
- await websocket.send(json.dumps({'type': 'status', 'msg': 'Running C Binary...'}))
-
- process = subprocess.Popen(
- [executable],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- text=True,
- bufsize=0,
- encoding='utf-8'
- )
-
- # --- C++ HANDLING ---
- elif language == 'cpp':
- filename = "temp_code.cpp"
- executable = "./a.out" if platform.system() != "Windows" else "a.exe"
-
- with open(filename, "w", encoding="utf-8") as f:
- f.write(code)
-
- await websocket.send(json.dumps({'type': 'status', 'msg': 'Compiling C++...'}))
+ if data.get('type') == 'run':
+ code = data.get('code')
+ language = data.get('language', 'python')
- compile_process = subprocess.run(
- ["g++", filename, "-o", executable],
- capture_output=True,
- text=True
- )
-
- if compile_process.returncode != 0:
- await websocket.send(json.dumps({'type': 'stdout', 'data': f"Compilation Error:\n{compile_process.stderr}"}))
- continue
-
- await websocket.send(json.dumps({'type': 'status', 'msg': 'Running C++ Binary...'}))
-
- process = subprocess.Popen(
- [executable],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- text=True,
- bufsize=0,
- encoding='utf-8'
- )
-
- if process:
- loop = asyncio.get_running_loop()
- thread = threading.Thread(target=read_stream, args=(process.stdout, loop))
- thread.daemon = True
- thread.start()
-
- elif data.get('type') == 'input':
- if process and process.poll() is None:
- user_input = data.get('data')
- try:
- process.stdin.write(user_input)
- process.stdin.flush()
- except Exception:
- pass
-
- except websockets.exceptions.ConnectionClosed:
- print("Client disconnected")
- except Exception as e:
- print(f"Server Error: {e}")
+ # --- PYTHON HANDLING ---
+ if language == 'python':
+ await ws.send_json({'type': 'status', 'msg': 'Checking dependencies...'})
+ loop = asyncio.get_running_loop()
+ await loop.run_in_executor(None, check_and_install_packages, code, ws, loop)
+
+ filename = "temp_script.py"
+ with open(filename, "w", encoding="utf-8") as f:
+ f.write(code)
+
+ env = os.environ.copy()
+ env["PYTHONIOENCODING"] = "utf-8"
+
+ process = subprocess.Popen(
+ [sys.executable, "-u", filename],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ bufsize=0,
+ encoding='utf-8',
+ env=env
+ )
+ await ws.send_json({'type': 'status', 'msg': 'Running Python...'})
+
+ # --- C HANDLING ---
+ elif language == 'c':
+ filename = "temp_code.c"
+ executable = "./a.out" if platform.system() != "Windows" else "a.exe"
+
+ with open(filename, "w", encoding="utf-8") as f:
+ f.write(code)
+
+ await ws.send_json({'type': 'status', 'msg': 'Compiling C...'})
+
+ compile_process = subprocess.run(
+ ["gcc", filename, "-o", executable],
+ capture_output=True,
+ text=True
+ )
+
+ if compile_process.returncode != 0:
+ await ws.send_json({'type': 'stdout', 'data': f"Compilation Error:\n{compile_process.stderr}"})
+ continue
+
+ await ws.send_json({'type': 'status', 'msg': 'Running C Binary...'})
+
+ process = subprocess.Popen(
+ [executable],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ bufsize=0,
+ encoding='utf-8'
+ )
+
+ # --- C++ HANDLING ---
+ elif language == 'cpp':
+ filename = "temp_code.cpp"
+ executable = "./a.out" if platform.system() != "Windows" else "a.exe"
+
+ with open(filename, "w", encoding="utf-8") as f:
+ f.write(code)
+
+ await ws.send_json({'type': 'status', 'msg': 'Compiling C++...'})
+
+ compile_process = subprocess.run(
+ ["g++", filename, "-o", executable],
+ capture_output=True,
+ text=True
+ )
+
+ if compile_process.returncode != 0:
+ await ws.send_json({'type': 'stdout', 'data': f"Compilation Error:\n{compile_process.stderr}"})
+ continue
+
+ await ws.send_json({'type': 'status', 'msg': 'Running C++ Binary...'})
+
+ process = subprocess.Popen(
+ [executable],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ bufsize=0,
+ encoding='utf-8'
+ )
+
+ if process:
+ loop = asyncio.get_running_loop()
+ thread = threading.Thread(target=read_stream, args=(process.stdout, loop))
+ thread.daemon = True
+ thread.start()
+
+ elif data.get('type') == 'input':
+ if process and process.poll() is None:
+ user_input = data.get('data')
+ try:
+ process.stdin.write(user_input)
+ process.stdin.flush()
+ except Exception:
+ pass
+
+ elif msg.type == web.WSMsgType.ERROR:
+ print(f'ws connection closed with exception {ws.exception()}')
+
finally:
if process: process.kill()
+ print("Client disconnected")
-async def health_check(path, request_headers):
- """
- Handler for HTTP requests (Health Checks) before WebSocket handshake.
- Render uses HEAD/GET on / to check health.
- """
- # 1. Check for standard health check paths
- if path == "/" or path == "/healthz":
- return http.HTTPStatus.OK, [], b"OK"
-
- # 2. Check headers to see if it's a websocket upgrade attempt
- # If it lacks 'Upgrade' or 'Connection' headers, it's likely a health check
- if "Upgrade" not in request_headers or "websocket" not in request_headers.get("Upgrade", "").lower():
- return http.HTTPStatus.OK, [], b"OK"
-
- # 3. Proceed with WebSocket Handshake
- return None
+ return ws
async def main():
port = int(os.environ.get("PORT", 8765))
- print(f"Server started on port {port}...")
- async with websockets.serve(run_code, "0.0.0.0", port, process_request=health_check):
- await asyncio.Future()
+ app = web.Application()
+ # Route root path to the unified handler (handles both HTTP checks and WS upgrades)
+ app.add_routes([web.get('/', handle_client)])
+
+ runner = web.AppRunner(app)
+ await runner.setup()
+ site = web.TCPSite(runner, '0.0.0.0', port)
+ print(f"AIOHTTP Server started on port {port}...")
+ await site.start()
+
+ # Keep the server running
+ await asyncio.Event().wait()
if __name__ == "__main__":
asyncio.run(main())