cloud-compiler

A real-time, low-latency code ...
Log | Files | Refs | README | LICENSE

backend.py (17887B)


      1 import asyncio
      2 import aiohttp
      3 from aiohttp import web
      4 import json
      5 import subprocess
      6 import os
      7 import sys
      8 import threading
      9 import re
     10 import importlib.util
     11 import platform
     12 import uuid
     13 import shutil
     14 
     15 # ==================================================================================
     16 # CLOUD COMPILER SERVER (AIOHTTP) - RENDER COMPATIBLE - SECURE VERSION
     17 # ==================================================================================
     18 
     19 # Get API Key from Environment
     20 GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
     21 
     22 def install_package(package_name, ws=None, loop=None):
     23     """Attempt to install a python package via pip."""
     24     try:
     25         if importlib.util.find_spec(package_name) is not None:
     26             return
     27         
     28         msg = f"Installing missing package: {package_name}..."
     29         print(msg)
     30         if ws and loop:
     31              asyncio.run_coroutine_threadsafe(
     32                 ws.send_json({'type': 'status', 'msg': msg}), 
     33                 loop
     34             )
     35 
     36         subprocess.check_call([sys.executable, "-m", "pip", "install", package_name])
     37     except Exception as e:
     38         error_msg = f"Failed to install {package_name}: {e}"
     39         print(error_msg)
     40         if ws and loop:  
     41              asyncio.run_coroutine_threadsafe(
     42                 ws.send_json({'type': 'stdout', 'data': f"\n[Error] {error_msg}\n"}), 
     43                 loop
     44             )
     45 
     46 def check_and_install_packages(code, ws=None, loop=None):
     47     """Scan Python code for imports and install them if missing."""
     48     imports = re.findall(r'^\s*import\s+(\w+)', code, re.MULTILINE)
     49     from_imports = re.findall(r'^\s*from\s+(\w+)', code, re.MULTILINE)
     50     unique_packages = set(imports + from_imports)
     51     for pkg in unique_packages:
     52         if pkg in ['os', 'sys', 'time', 'random', 'math', 'json', 'asyncio', 'threading', 'platform', 'subprocess', 're', 'aiohttp']:
     53             continue
     54         install_package(pkg, ws, loop)
     55 
     56 async def handle_client(request):
     57     # --- HEALTH CHECK HANDLING ---
     58     if request.headers.get("Upgrade", "").lower() != "websocket":
     59         return web.Response(text="OK")
     60 
     61     # --- WEBSOCKET HANDLING ---
     62     ws = web.WebSocketResponse()
     63     await ws.prepare(request)
     64     
     65     print(f"Client connected: {request.remote}")
     66     
     67     # Create unique session directory for this client
     68     session_id = str(uuid.uuid4())
     69     session_dir = os.path.join("temp_sessions", session_id)
     70     os.makedirs(session_dir, exist_ok=True)
     71     print(f"Created session directory: {session_dir}")
     72     
     73     process = None
     74     
     75     # Helper to read output stream in a separate thread
     76     def read_stream(stream, loop):
     77         try:
     78             while True:
     79                 char = stream.read(1)
     80                 if not char:
     81                     break
     82                 asyncio.run_coroutine_threadsafe(
     83                     ws.send_json({'type': 'stdout', 'data': char}), 
     84                     loop
     85                 )
     86             
     87             asyncio.run_coroutine_threadsafe(
     88                 ws.send_json({'type': 'status', 'msg': 'Program finished'}), 
     89                 loop
     90             )
     91         except Exception:  
     92             pass
     93 
     94     try:
     95         async for msg in ws:
     96             if msg.type == web.WSMsgType.TEXT:
     97                 data = json.loads(msg.data)
     98                 
     99                 if data.get('type') == 'run':
    100                     code = data.get('code')
    101                     language = data.get('language', 'python')
    102                     
    103                     # --- PYTHON HANDLING ---
    104                     if language == 'python':
    105                         await ws.send_json({'type': 'status', 'msg': 'Checking dependencies...'})
    106                         loop = asyncio.get_running_loop()
    107                         await loop.run_in_executor(None, check_and_install_packages, code, ws, loop)
    108                         
    109                         filename = os.path.join(session_dir, "temp_script.py")
    110                         with open(filename, "w", encoding="utf-8") as f:
    111                             f.write(code)
    112                         
    113                         # 🔒 SECURITY FIX: Sanitized environment (no API keys)
    114                         safe_env = {
    115                             "PYTHONIOENCODING": "utf-8",
    116                             "PATH": os.environ.get("PATH", ""),
    117                             "PYTHONPATH": os.environ.get("PYTHONPATH", ""),
    118                             "HOME": os.environ.get("HOME", ""),
    119                             "USER": os.environ.get("USER", ""),
    120                             "LANG": os.environ.get("LANG", "en_US.UTF-8"),
    121                         }
    122 
    123                         process = subprocess.Popen(
    124                             [sys.executable, "-u", filename], 
    125                             stdin=subprocess.PIPE,
    126                             stdout=subprocess.PIPE,
    127                             stderr=subprocess.STDOUT, 
    128                             text=True, 
    129                             bufsize=0, 
    130                             encoding='utf-8', 
    131                             env=safe_env  # ✅ Use sanitized environment
    132                         )
    133                         await ws.send_json({'type': 'status', 'msg': 'Running Python...'})
    134 
    135                     # --- C HANDLING ---
    136                     elif language == 'c':
    137                         filename = os.path.join(session_dir, "temp_code.c")
    138                         executable = os.path.join(session_dir, "a.out") if platform.system() != "Windows" else os.path.join(session_dir, "a.exe")
    139                         
    140                         with open(filename, "w", encoding="utf-8") as f:
    141                             f.write(code)
    142                         
    143                         await ws.send_json({'type':  'status', 'msg':  'Compiling C...'})
    144                         
    145                         # 🔒 SECURITY:  Sanitized environment
    146                         safe_env = {
    147                             "PATH": os.environ.get("PATH", ""),
    148                             "HOME": os.environ.get("HOME", ""),
    149                             "USER": os.environ.get("USER", ""),
    150                             "LANG": os.environ.get("LANG", "en_US.UTF-8"),
    151                             "TMPDIR": os.environ.get("TMPDIR", "/tmp"),
    152                         }
    153                         
    154                         compile_process = subprocess.run(
    155                             ["gcc", filename, "-o", executable, "-lm", "-pthread"],
    156                             capture_output=True,
    157                             text=True,
    158                             env=safe_env
    159                         )
    160                         
    161                         if compile_process.returncode != 0:
    162                             await ws.send_json({'type': 'stdout', 'data': f"Compilation Error:\n{compile_process.stderr}"})
    163                             await ws.send_json({'type':  'status', 'msg':  'Compilation Failed'})
    164                             continue 
    165                         
    166                         await ws.send_json({'type': 'status', 'msg': 'Running C Binary...'})
    167                         
    168                         process = subprocess.Popen(
    169                             [executable],
    170                             stdin=subprocess.PIPE,
    171                             stdout=subprocess.PIPE,
    172                             stderr=subprocess.STDOUT,
    173                             text=True,
    174                             bufsize=0,
    175                             encoding='utf-8',
    176                             env=safe_env  # ✅ Sanitized environment
    177                         )
    178 
    179                     # --- C++ HANDLING ---
    180                     elif language == 'cpp':
    181                         # FIXED: Removed the space in "temp_code.cpp"
    182                         filename = os.path.join(session_dir, "temp_code.cpp")
    183                         executable = os.path.join(session_dir, "a.out") if platform.system() != "Windows" else os.path.join(session_dir, "a.exe")
    184                         
    185                         with open(filename, "w", encoding="utf-8") as f:
    186                             f.write(code)
    187                         
    188                         await ws.send_json({'type': 'status', 'msg': 'Compiling C++...'})
    189                         
    190                         # 🔒 SECURITY: Sanitized environment
    191                         safe_env = {
    192                             "PATH": os.environ.get("PATH", ""),
    193                             "HOME": os.environ.get("HOME", ""),
    194                             "USER": os.environ.get("USER", ""),
    195                             "LANG": os.environ.get("LANG", "en_US.UTF-8"),
    196                             "TMPDIR":  os.environ.get("TMPDIR", "/tmp"),
    197                         }
    198                         
    199                         compile_process = subprocess.run(
    200                             ["g++", filename, "-o", executable, "-lm", "-pthread"],
    201                             capture_output=True,
    202                             text=True,
    203                             env=safe_env
    204                         )
    205                         
    206                         if compile_process.returncode != 0:
    207                             await ws.send_json({'type': 'stdout', 'data': f"Compilation Error:\n{compile_process.stderr}"})
    208                             await ws.send_json({'type': 'status', 'msg': 'Compilation Failed'})
    209                             continue
    210                         
    211                         await ws.send_json({'type': 'status', 'msg': 'Running C++ Binary...'})
    212                         
    213                         process = subprocess.Popen(
    214                             [executable],
    215                             stdin=subprocess.PIPE,
    216                             stdout=subprocess.PIPE,
    217                             stderr=subprocess.STDOUT,
    218                             text=True,
    219                             bufsize=0,
    220                             encoding='utf-8',
    221                             env=safe_env  # ✅ Sanitized environment
    222                         )
    223 
    224                     if process:
    225                         loop = asyncio.get_running_loop()
    226                         thread = threading.Thread(target=read_stream, args=(process.stdout, loop))
    227                         thread.daemon = True 
    228                         thread.start()
    229 
    230                 elif data.get('type') == 'input':
    231                     if process and process.poll() is None:
    232                         user_input = data.get('data')
    233                         try:
    234                             process.stdin.write(user_input)
    235                             process.stdin.flush()
    236                         except Exception:
    237                             pass
    238 
    239                 # --- AI FIX HANDLER ---
    240                 elif data.get('type') == 'ai_fix':
    241                     code = data.get('code')
    242                     error_log = data.get('error')
    243                     language = data.get('language', 'c')
    244                     
    245                     if not GEMINI_API_KEY:  
    246                         await ws.send_json({'type': 'ai_error', 'msg':  'Server Error:  GEMINI_API_KEY not configured.'})
    247                         continue
    248 
    249                     # Construct Prompt - Ask for JSON in plain text
    250                     prompt = f"""You are an expert {language.upper()} debugger. Analyze this code and error, then respond with ONLY a valid JSON object (no markdown, no backticks).
    251 
    252 CODE:  
    253 {code}
    254 
    255 ERROR: 
    256 {error_log}
    257 
    258 Respond in this exact JSON format:
    259 {{
    260     "explanation": "Brief 1-2 sentence explanation of the bug",
    261     "fixed_code":  "Complete corrected code here"
    262 }}"""
    263 
    264                     try:
    265                         # ✅ FIXED: Use correct stable model endpoint
    266                         api_url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key={GEMINI_API_KEY}"
    267                         
    268                         print(f"[AI] Calling Gemini API...")
    269                         
    270                         async with aiohttp.ClientSession() as session:
    271                             async with session.post(
    272                                 api_url,
    273                                 headers={"Content-Type":  "application/json"},
    274                                 json={
    275                                     "contents": [{"parts": [{"text": prompt}]}],
    276                                     "generationConfig": {
    277                                         "temperature": 0.1,
    278                                         "maxOutputTokens": 2048
    279                                     }
    280                                 }
    281                             ) as resp:
    282                                 response_text = await resp.text()
    283                                 
    284                                 print(f"[AI] Status: {resp.status}")
    285                                 print(f"[AI] Response preview: {response_text[: 200]}")
    286                                 
    287                                 if resp.status != 200:
    288                                     await ws.send_json({
    289                                         'type': 'ai_error', 
    290                                         'msg': f"AI API Error (Status {resp.status})"
    291                                     })
    292                                 else:
    293                                     try:
    294                                         result = json.loads(response_text)
    295                                         
    296                                         # Extract AI response
    297                                         ai_text = result['candidates'][0]['content']['parts'][0]['text']
    298                                         
    299                                         # Clean markdown if present
    300                                         ai_text = ai_text.strip()
    301                                         if ai_text.startswith('```json'):
    302                                             ai_text = ai_text.split('```json')[1].split('```')[0].strip()
    303                                         elif ai_text.startswith('```'):
    304                                             ai_text = ai_text.split('```')[1].split('```')[0].strip()
    305                                         
    306                                         # Parse JSON
    307                                         parsed_response = json.loads(ai_text)
    308                                         
    309                                         # Validate structure
    310                                         if 'explanation' in parsed_response and 'fixed_code' in parsed_response: 
    311                                             print(f"[AI] Success! Sending response to client")
    312                                             await ws.send_json({
    313                                                 'type': 'ai_response', 
    314                                                 'data': parsed_response
    315                                             })
    316                                         else:
    317                                             print(f"[AI] Invalid structure:  {parsed_response}")
    318                                             await ws.send_json({
    319                                                 'type': 'ai_error', 
    320                                                 'msg': 'Invalid AI response format'
    321                                             })
    322                                             
    323                                     except (KeyError, IndexError) as e:
    324                                         print(f"[AI] API structure error: {e}")
    325                                         await ws.send_json({
    326                                             'type': 'ai_error', 
    327                                             'msg': 'Unexpected API response'
    328                                         })
    329                                     except json.JSONDecodeError as e:
    330                                         print(f"[AI] JSON parse error: {e}")
    331                                         print(f"[AI] Attempted to parse:  {ai_text[: 300]}")
    332                                         await ws.send_json({
    333                                             'type': 'ai_error', 
    334                                             'msg': 'AI returned invalid JSON'
    335                                         })
    336                                     
    337                     except aiohttp.ClientError as e:
    338                         print(f"[AI] Network error: {e}")
    339                         await ws.send_json({
    340                             'type': 'ai_error', 
    341                             'msg': 'Network error'
    342                         })
    343                     except Exception as e:  
    344                         print(f"[AI] Unexpected error: {e}")
    345                         import traceback
    346                         traceback.print_exc()
    347                         await ws.send_json({
    348                             'type': 'ai_error', 
    349                             'msg': f'Server error: {type(e).__name__}'
    350                         })
    351 
    352             elif msg.type == web.WSMsgType.ERROR:
    353                 print(f'ws connection closed with exception {ws.exception()}')
    354 
    355     finally:
    356         if process:  
    357             try:
    358                 process.kill()
    359             except:
    360                 pass
    361         # Clean up session directory
    362         try:
    363             shutil.rmtree(session_dir, ignore_errors=True)
    364             print(f"Cleaned up session directory: {session_dir}")
    365         except:
    366             pass
    367         print("Client disconnected")
    368 
    369     return ws
    370 
    371 async def main():
    372     # Create temp_sessions directory if it doesn't exist
    373     os.makedirs("temp_sessions", exist_ok=True)
    374     print("temp_sessions directory ready")
    375     
    376     port = int(os.environ.get("PORT", 8765))
    377     app = web.Application()
    378     app.add_routes([web.get('/', handle_client)])
    379     
    380     runner = web.AppRunner(app)
    381     await runner.setup()
    382     site = web.TCPSite(runner, '0.0.0.0', port)
    383     print(f"AIOHTTP Server started on port {port}...")
    384     await site.start()
    385     
    386     # Keep the server running
    387     await asyncio.Event().wait()
    388 
    389 if __name__ == "__main__":
    390     asyncio.run(main())
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror