commit b68322302eaf4c2c4ed4f3bee7d8ef6cd36e8b43
parent b742a49f714646e0d53cd45cc7e9b32029bd582c
Author: AMIT DUTTA <amitdutta4255@gmail.com>
Date: Sun, 11 Jan 2026 19:12:24 +0530
Migrate to AIOHTTP for cloud compiler server
Refactor server to use AIOHTTP for handling requests and WebSocket connections. Added support for running Python, C, and C++ code with package installation and error handling.
Diffstat:
| M | backend.py | | | 249 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- |
1 file changed, 176 insertions(+), 73 deletions(-)
diff --git a/backend.py b/backend.py
@@ -1,122 +1,225 @@
import asyncio
-import websockets
+from aiohttp import web
import json
import subprocess
import os
import sys
-import platform
import threading
import re
import importlib.util
+import platform # <--- FIXED: Added missing import
# ==================================================================================
-# THREADED COMPILER SERVER (Auto-Install + UTF-8)
+# CLOUD COMPILER SERVER (AIOHTTP) - RENDER COMPATIBLE
# ==================================================================================
-def install_package(package_name):
- """Attempt to install a package via pip."""
+def install_package(package_name, ws=None, loop=None):
+ """Attempt to install a python package via pip."""
try:
- # Check if already installed to save time
if importlib.util.find_spec(package_name) is not None:
return
+
+ msg = f"Installing missing package: {package_name}..."
+ print(msg)
+ if ws and loop:
+ asyncio.run_coroutine_threadsafe(
+ ws.send_json({'type': 'status', 'msg': msg}),
+ loop
+ )
- print(f"Installing missing package: {package_name}...")
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name])
except Exception as e:
- print(f"Failed to install {package_name}: {e}")
+ error_msg = f"Failed to install {package_name}: {e}"
+ print(error_msg)
+ if ws and loop:
+ asyncio.run_coroutine_threadsafe(
+ ws.send_json({'type': 'stdout', 'data': f"\n[Error] {error_msg}\n"}),
+ loop
+ )
-def check_and_install_packages(code):
- """Scan code for imports and install them if missing."""
- # Regex to find 'import x' or 'from x import y'
+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)
-
unique_packages = set(imports + from_imports)
-
- # Filter out standard library modules (approximate list or just let pip handle it)
- # Ideally, we just try to install. Pip is smart enough to skip if satisfied.
for pkg in unique_packages:
- # Skip common standard libs to save time (add more if needed)
if pkg in ['os', 'sys', 'time', 'random', 'math', 'json', 'asyncio', 'threading', 'platform', 'subprocess', 're']:
continue
- install_package(pkg)
+ install_package(pkg, ws, loop)
+
+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")
-async def run_code(websocket):
- print(f"Client connected: {websocket.remote_address}")
+ # --- 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
def read_stream(stream, loop):
try:
while True:
+ # Read 1 byte/char at a time
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
)
+
+ # --- SIGNAL FINISH ---
+ # When stream ends (process exits), tell frontend
+ asyncio.run_coroutine_threadsafe(
+ ws.send_json({'type': 'status', 'msg': 'Program finished'}),
+ loop
+ )
+
except Exception:
pass
try:
- async for message in websocket:
- data = json.loads(message)
-
- if data.get('type') == 'run':
- code = data.get('code')
-
- # 1. AUTO-INSTALL PACKAGES
- await websocket.send(json.dumps({'type': 'status', 'msg': 'Checking dependencies...'}))
- # Run in executor to avoid blocking the event loop
- await asyncio.get_running_loop().run_in_executor(None, check_and_install_packages, code)
-
- # 2. WRITE FILE
- filename = "temp_script.py"
- with open(filename, "w", encoding="utf-8") as f:
- f.write(code)
+ async for msg in ws:
+ if msg.type == web.WSMsgType.TEXT:
+ data = json.loads(msg.data)
- # 3. RUN 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...'}))
-
- 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}")
+ if data.get('type') == 'run':
+ code = data.get('code')
+ language = data.get('language', 'python')
+
+ # --- 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")
+
+ return ws
async def main():
- print("Server started on port 8765...")
- async with websockets.serve(run_code, "0.0.0.0", 8765):
- await asyncio.Future()
+ port = int(os.environ.get("PORT", 8765))
+ 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())