3 Commits

Author SHA1 Message Date
4453e69e68 working socket integration 2025-04-21 21:03:59 +05:30
c143efa70e input channel mapping update 2025-04-21 15:05:17 +05:30
86dcfa2a4a changes in sockets and terminal development 2025-04-21 14:59:25 +05:30
34 changed files with 4254 additions and 1175 deletions

View File

@@ -1,24 +1,13 @@
# VS Code Clone Project # VSCode Clone with React and Vite
## Authors This project is a VSCode-like code editor built with React and Vite. It features a customizable UI with an activity bar, sidebar, editor area, panel, and status bar, mimicking the look and feel of Visual Studio Code.
- Arnab Bhowmik
- Ishika Bhoyar
## Description ## Features
This project is a VS Code Clone built with React and Monaco Editor. It features a file tree navigation, tab management, code editing with syntax highlighting, and a terminal panel for running code. It mimics the core functionalities of Visual Studio Code in a browser-based environment.
## Frontend Functionalities - **Activity Bar**: Switch between different views like Explorer, Search, Source Control, etc.
- Built with React and Monaco Editor. - **Sidebar**: Displays file explorer, search results, and source control information.
- File tree navigation for managing files and folders. - **Editor Area**: Code editor with syntax highlighting and multiple tabs.
- Tab management for opening multiple files simultaneously. - **Panel**: Terminal, Problems, and Output views.
- Code editing with syntax highlighting and language support. - **Status Bar**: Displays status information and provides quick actions.
- Terminal panel for running code and viewing output.
- Persistent file structure and content using localStorage.
## Backend Functionalities ## Project Structure
- Built with Go and Docker for secure code execution.
- Supports multiple programming languages (Python, Java, C/C++).
- Executes code in isolated Docker containers with resource limits.
- RESTful API for submitting code, checking status, and retrieving results.
- Job queue system for managing concurrent executions.
- Enforces timeouts and resource limits for security and performance.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,150 @@
import React from "react"
"use client"
import Link from "next/link"
import { ChevronDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"
export function Navbar() {
return (
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container flex h-16 items-center justify-between">
<Link href="/" className="flex items-center space-x-2">
<span className="text-xl font-bold">*Azzle</span>
</Link>
<NavigationMenu className="hidden md:flex">
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>
Demo <ChevronDown className="h-4 w-4" />
</NavigationMenuTrigger>
<NavigationMenuContent>
<div className="grid gap-3 p-6 w-[400px]">
<NavigationMenuLink asChild>
<Link
href="/demo/features"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Features</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
Explore all the features our platform has to offer
</p>
</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link
href="/demo/pricing"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Pricing</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
View our flexible pricing plans
</p>
</Link>
</NavigationMenuLink>
</div>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href="/about" legacyBehavior passHref>
<NavigationMenuLink className="group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50">
About
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>
Services <ChevronDown className="h-4 w-4" />
</NavigationMenuTrigger>
<NavigationMenuContent>
<div className="grid gap-3 p-6 w-[400px]">
<NavigationMenuLink asChild>
<Link
href="/services/consulting"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Consulting</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
Expert guidance for your business needs
</p>
</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link
href="/services/implementation"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Implementation</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
Full-service implementation and support
</p>
</Link>
</NavigationMenuLink>
</div>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>
Pages <ChevronDown className="h-4 w-4" />
</NavigationMenuTrigger>
<NavigationMenuContent>
<div className="grid gap-3 p-6 w-[400px]">
<NavigationMenuLink asChild>
<Link
href="/blog"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Blog</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
Read our latest articles and updates
</p>
</Link>
</NavigationMenuLink>
<NavigationMenuLink asChild>
<Link
href="/resources"
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="text-sm font-medium leading-none">Resources</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
Helpful guides and documentation
</p>
</Link>
</NavigationMenuLink>
</div>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href="/contact" legacyBehavior passHref>
<NavigationMenuLink className="group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50">
Contact
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
<div className="flex items-center space-x-4">
<Button variant="ghost" asChild>
<Link href="/login">Login</Link>
</Button>
<Button asChild>
<Link href="/signup">Sign up free</Link>
</Button>
</div>
</div>
</header>
)
}
export default Navbar

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef } from "react"; import React from "react";
import { X, Maximize2, ChevronDown, Plus } from "lucide-react"; import { useState, useEffect } from "react";
import { X } from "lucide-react";
const Panel = ({ const Panel = ({
height, height,
@@ -11,135 +12,96 @@ const Panel = ({
onClose, onClose,
userInput = "", userInput = "",
onUserInputChange, onUserInputChange,
onInputSubmit, onInputSubmit
}) => { }) => {
const [activeTab, setActiveTab] = useState(initialTab); const [activeTab, setActiveTab] = useState(initialTab);
const terminalRef = useRef(null);
const [inputBuffer, setInputBuffer] = useState("");
// Update active tab when initialTab changes // Set active tab when initialTab changes
useEffect(() => { useEffect(() => {
setActiveTab(initialTab); setActiveTab(initialTab);
}, [initialTab]); }, [initialTab]);
// Auto-scroll terminal to the bottom when content changes const renderTerminal = () => {
useEffect(() => { return (
if (terminalRef.current) { <div className="panel-terminal">
terminalRef.current.scrollTop = terminalRef.current.scrollHeight; {terminalOutput.length > 0 ? (
} // Render output from EditorArea when available
}, [terminalOutput]); <>
{terminalOutput.map((line, index) => (
// Handle keyboard input for the terminal <div key={index} className={`terminal-line ${line.type === 'warning' ? 'terminal-warning' : line.type === 'input' ? 'terminal-input-line' : line.type === 'prompt' ? 'terminal-prompt-line' : 'terminal-output'}`}>
useEffect(() => { {line.type === 'command' ? <span className="terminal-prompt">$</span> : ''}
const handleKeyDown = (e) => { {line.type === 'input' ? <span className="terminal-input-marker">[Input]</span> : ''}
if (!isRunning) return; {line.type === 'prompt' ? <span className="terminal-prompt-marker">&gt;</span> : ''}
if (e.key === "Enter") {
if (inputBuffer.trim() && onInputSubmit) {
e.preventDefault();
// Update parent's userInput state directly and call submit in the same function
// instead of using setTimeout which creates a race condition
onUserInputChange(inputBuffer);
onInputSubmit(inputBuffer); // Pass inputBuffer directly to avoid race condition
setInputBuffer("");
}
} else if (e.key === "Backspace") {
setInputBuffer((prev) => prev.slice(0, -1));
} else if (e.key.length === 1) {
setInputBuffer((prev) => prev + e.key);
}
};
const terminalElement = terminalRef.current;
terminalElement?.addEventListener("keydown", handleKeyDown);
return () => {
terminalElement?.removeEventListener("keydown", handleKeyDown);
};
}, [isRunning, inputBuffer, onInputSubmit, onUserInputChange]);
// Render the terminal tab
const renderTerminal = () => (
<div
className="panel-terminal"
ref={terminalRef}
tabIndex={0} // Make div focusable
onClick={() => terminalRef.current?.focus()} // Focus when clicked
>
{terminalOutput.length > 0 ? (
<>
{terminalOutput.map((line, index) => {
const typeClass =
line.type === "warning"
? "terminal-warning"
: line.type === "error"
? "terminal-error"
: "terminal-output";
return (
<div key={index} className={`terminal-line ${typeClass}`}>
{line.timestamp && (
<span className="terminal-timestamp">{line.timestamp} </span>
)}
{line.type === "command" && <span className="terminal-prompt">$</span>}
{line.content} {line.content}
</div> </div>
); ))}
})} {waitingForInput && (
<div className="terminal-line terminal-input-container">
{isRunning && ( <div className="terminal-input-header">
<div className="terminal-line terminal-input-line"> <span className="terminal-input-marker">Input Required:</span>
<span className="terminal-prompt">$</span> {inputBuffer} </div>
<span className="terminal-cursor"></span> <div className="terminal-input-wrapper">
<div className="terminal-input-prompt">&gt;</div>
<input
type="text"
className="terminal-input"
value={userInput}
onChange={(e) => onUserInputChange && onUserInputChange(e.target.value)}
placeholder="Enter input for your program here..."
onKeyDown={(e) => {
if (e.key === 'Enter' && onInputSubmit) {
onInputSubmit();
}
}}
autoFocus
/>
</div>
<div className="terminal-input-help">
Press Enter to submit input
</div>
</div>
)}
</>
) : (
// Default terminal content when no output
<>
<div className="terminal-line">
<span className="terminal-prompt">$</span> npm start
</div> </div>
)} <div className="terminal-line terminal-output">Starting the development server...</div>
</> <div className="terminal-line terminal-output">Compiled successfully!</div>
) : ( <div className="terminal-line terminal-output">You can now view vscode-clone in the browser.</div>
<div className="terminal-line"> <div className="terminal-line terminal-output">Local: http://localhost:3000</div>
<span className="terminal-prompt">$</span> <div className="terminal-line terminal-output">On Your Network: http://192.168.1.5:3000</div>
<span className="terminal-cursor"></span> <div className="terminal-line">
</div> <span className="terminal-prompt">$</span>
)} </div>
</div> </>
); )}
</div>
);
};
// Render other tabs const renderProblems = () => {
const renderProblems = () => ( return (
<div className="panel-problems"> <div className="panel-problems">
<div className="panel-empty-message">No problems have been detected in the workspace.</div> <div className="panel-empty-message">No problems have been detected in the workspace.</div>
</div> </div>
); );
};
const renderOutput = () => ( const renderOutput = () => {
<div className="panel-output"> return (
<div className="output-line">[Extension Host] Extension host started.</div> <div className="panel-output">
<div className="output-line">[Language Server] Language server started.</div> <div className="output-line">[Extension Host] Extension host started.</div>
{activeRunningFile && ( <div className="output-line">[Language Server] Language server started.</div>
<div className="output-line">[Running] {activeRunningFile}</div> {activeRunningFile && (
)} <div className="output-line">[Running] {activeRunningFile}</div>
</div> )}
); </div>
);
};
const renderDebugConsole = () => (
<div className="panel-debug-console">
<div className="debug-line">Debug session not yet started.</div>
<div className="debug-line">Press F5 to start debugging.</div>
</div>
);
const renderPorts = () => (
<div className="panel-ports">
<div className="ports-line">No forwarded ports detected.</div>
</div>
);
const renderComments = () => (
<div className="panel-comments">
<div className="comments-line">No comments have been added to this workspace.</div>
</div>
);
// Get content for the active tab
const getTabContent = () => { const getTabContent = () => {
switch (activeTab) { switch (activeTab) {
case "terminal": case "terminal":
@@ -148,12 +110,6 @@ const Panel = ({
return renderProblems(); return renderProblems();
case "output": case "output":
return renderOutput(); return renderOutput();
case "debug":
return renderDebugConsole();
case "ports":
return renderPorts();
case "comments":
return renderComments();
default: default:
return <div>Unknown tab</div>; return <div>Unknown tab</div>;
} }
@@ -162,29 +118,76 @@ const Panel = ({
return ( return (
<div className="panel" style={{ height: `${height}px` }}> <div className="panel" style={{ height: `${height}px` }}>
<div className="panel-tabs"> <div className="panel-tabs">
{["problems", "output", "debug", "terminal", "ports", "comments"].map((tab) => ( <div
<div className={`panel-tab ${activeTab === "problems" ? "active" : ""}`}
key={tab} onClick={() => setActiveTab("problems")}
className={`panel-tab ${activeTab === tab ? "active" : ""}`} >
onClick={() => setActiveTab(tab)} <span className="tab-icon">
> <svg
<span className="tab-name">{tab.toUpperCase()}</span> xmlns="http://www.w3.org/2000/svg"
</div> width="16"
))} height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
</span>
<span className="tab-name">Problems</span>
</div>
<div className={`panel-tab ${activeTab === "output" ? "active" : ""}`} onClick={() => setActiveTab("output")}>
<span className="tab-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</span>
<span className="tab-name">Output</span>
</div>
<div
className={`panel-tab ${activeTab === "terminal" ? "active" : ""}`}
onClick={() => setActiveTab("terminal")}
>
<span className="tab-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="4 17 10 11 4 5"></polyline>
<line x1="12" y1="19" x2="20" y2="19"></line>
</svg>
</span>
<span className="tab-name">Terminal</span>
</div>
{/* Add close button */}
<div className="panel-actions"> <div className="panel-actions">
{/* <button className="panel-action-btn">
<span className="current-terminal">node - frontend</span>
<ChevronDown size={16} />
</button>
<button className="panel-action-btn">
<Plus size={16} />
</button>
<button className="panel-action-btn">
<Maximize2 size={16} />
</button> */}
<button className="panel-close-btn" onClick={onClose}> <button className="panel-close-btn" onClick={onClose}>
<X size={16} /> <X size={14} />
</button> </button>
</div> </div>
</div> </div>
@@ -194,4 +197,5 @@ const Panel = ({
); );
}; };
export default Panel; export default Panel;

View File

@@ -31,10 +31,10 @@ const Sidebar = ({
const renderExplorer = () => { const renderExplorer = () => {
const renderFileTree = (structure, path = "") => { const renderFileTree = (structure, path = "") => {
if (!structure) return null; if (!structure) return null;
return Object.entries(structure).map(([name, item]) => { return Object.entries(structure).map(([name, item]) => {
const currentPath = path ? `${path}/${name}` : name; const currentPath = path ? `${path}/${name}` : name;
if (item.type === "folder") { if (item.type === "folder") {
const isExpanded = expandedFolders[currentPath]; const isExpanded = expandedFolders[currentPath];
return ( return (
@@ -75,6 +75,21 @@ const Sidebar = ({
</svg> </svg>
)} )}
</span> </span>
<span className="folder-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="#75beff"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
</span>
<span className="folder-name">{name}</span> <span className="folder-name">{name}</span>
</div> </div>
{isExpanded && ( {isExpanded && (
@@ -122,7 +137,7 @@ const Sidebar = ({
} }
}); });
}; };
return ( return (
<div className="sidebar-section"> <div className="sidebar-section">
<div className="sidebar-title"> <div className="sidebar-title">
@@ -169,38 +184,10 @@ const Sidebar = ({
</div> </div>
); );
}; };
const getFileIcon = (fileName) => { const getFileIcon = (fileName) => {
const extension = fileName.split('.').pop().toLowerCase(); const extension = fileName.split('.').pop().toLowerCase();
if (fileName.toLowerCase() === 'readme.md') {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="#007acc" /* Blue color for the circle */
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10" fill="none" stroke="#007acc" />
<text
x="12"
y="15"
textAnchor="middle"
fontSize="10"
fill="#007acc"
fontFamily="Arial, sans-serif"
fontWeight="bold"
>
i
</text>
</svg>
);
}
if (['jsx', 'js', 'ts', 'tsx'].includes(extension)) { if (['jsx', 'js', 'ts', 'tsx'].includes(extension)) {
return ( return (
<svg <svg
@@ -253,7 +240,7 @@ const Sidebar = ({
</svg> </svg>
); );
} }
return ( return (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@@ -1,11 +1,10 @@
import React from "react"; import React from "react";
"use client"
const StatusBar = ({ togglePanel, panelVisible }) => { const StatusBar = ({ togglePanel, panelVisible }) => {
return ( return (
<div className="status-bar"> <div className="status-bar">
{/* Left Section of the Status Bar */}
<div className="status-bar-left"> <div className="status-bar-left">
{/* Branch Indicator */}
<div className="status-item"> <div className="status-item">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -17,7 +16,6 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
aria-label="Branch Icon"
> >
<line x1="6" y1="3" x2="6" y2="15"></line> <line x1="6" y1="3" x2="6" y2="15"></line>
<circle cx="18" cy="6" r="3"></circle> <circle cx="18" cy="6" r="3"></circle>
@@ -27,7 +25,6 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
<span>main</span> <span>main</span>
</div> </div>
{/* Error Indicator */}
<div className="status-item"> <div className="status-item">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -39,72 +36,30 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
aria-label="Error Icon"
> >
<polyline points="20 6 9 17 4 12"></polyline> <polyline points="20 6 9 17 4 12"></polyline>
</svg> </svg>
<span>0 errors</span> <span>0 errors</span>
</div> </div>
{/* Warning Indicator */} <button className="status-item status-button" onClick={togglePanel}>
<div className="status-item">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-label="Warning Icon"
>
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
<line x1="12" y1="9" x2="12" y2="13"></line>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</svg>
<span>0 warnings</span>
</div>
{/* Toggle Terminal Button */}
<button
className="status-item status-button"
onClick={togglePanel}
aria-label="Toggle Terminal"
>
<span>{panelVisible ? "Hide Terminal" : "Show Terminal"}</span> <span>{panelVisible ? "Hide Terminal" : "Show Terminal"}</span>
</button> </button>
</div> </div>
{/* Right Section of the Status Bar */}
<div className="status-bar-right"> <div className="status-bar-right">
{/* Line and Column Indicator */}
<div className="status-item"> <div className="status-item">
<span>Ln 1, Col 1</span> <span>Ln 1, Col 1</span>
</div> </div>
{/* Spaces Indicator */}
<div className="status-item"> <div className="status-item">
<span>Spaces: 2</span> <span>Spaces: 2</span>
</div> </div>
{/* Encoding Indicator */}
<div className="status-item"> <div className="status-item">
<span>UTF-8</span> <span>UTF-8</span>
</div> </div>
{/* Language Mode */}
<div className="status-item">
<span>JavaScript</span>
</div>
{/* EOL (End of Line) Indicator */}
<div className="status-item">
<span>LF</span>
</div>
{/* Connection Status */}
<div className="status-item"> <div className="status-item">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -116,7 +71,6 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
aria-label="Connection Icon"
> >
<path d="M5 12.55a11 11 0 0 1 14.08 0"></path> <path d="M5 12.55a11 11 0 0 1 14.08 0"></path>
<path d="M1.42 9a16 16 0 0 1 21.16 0"></path> <path d="M1.42 9a16 16 0 0 1 21.16 0"></path>
@@ -126,7 +80,6 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
<span>Connected</span> <span>Connected</span>
</div> </div>
{/* Bell Icon */}
<div className="status-item"> <div className="status-item">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -138,7 +91,6 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
aria-label="Bell Icon"
> >
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path> <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path> <path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
@@ -146,7 +98,8 @@ const StatusBar = ({ togglePanel, panelVisible }) => {
</div> </div>
</div> </div>
</div> </div>
); )
}; }
export default StatusBar
export default StatusBar;

View File

@@ -0,0 +1,239 @@
import React, { useState, useEffect, useRef } from 'react';
const WebSocketTerminal = ({ code, language, onClose }) => {
const [connected, setConnected] = useState(false);
const [output, setOutput] = useState([]);
const [input, setInput] = useState('');
const [submissionId, setSubmissionId] = useState(null);
const wsRef = useRef(null);
const outputRef = useRef(null);
// Auto-scroll to bottom of output
useEffect(() => {
if (outputRef.current) {
outputRef.current.scrollTop = outputRef.current.scrollHeight;
}
}, [output]);
// Connect to WebSocket on component mount
useEffect(() => {
// Use API URL from environment variable
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8080';
const wsUrl = apiUrl.replace('http://', 'ws://').replace('https://', 'wss://');
// Create WebSocket connection
wsRef.current = new WebSocket(`${wsUrl}/ws`);
// Connection opened
wsRef.current.addEventListener('open', () => {
setConnected(true);
setOutput(prev => [...prev, { type: 'system', content: 'Connected to server' }]);
// Send the code submission
const submission = {
language,
code
};
wsRef.current.send(JSON.stringify(submission));
});
// Listen for messages
wsRef.current.addEventListener('message', (event) => {
const message = event.data;
// Check if this is a submission ID message
if (message.startsWith('Submission ID: ')) {
const id = message.substring('Submission ID: '.length);
setSubmissionId(id);
setOutput(prev => [...prev, { type: 'system', content: `Execution started with ID: ${id}` }]);
return;
}
// Regular output
setOutput(prev => [...prev, { type: 'output', content: message }]);
});
// Connection closed
wsRef.current.addEventListener('close', () => {
setConnected(false);
setOutput(prev => [...prev, { type: 'system', content: 'Disconnected from server' }]);
});
// Connection error
wsRef.current.addEventListener('error', (error) => {
console.error('WebSocket error:', error);
setOutput(prev => [...prev, { type: 'error', content: 'Connection error' }]);
});
// Clean up on unmount
return () => {
if (wsRef.current) {
wsRef.current.close();
}
};
}, [code, language]);
// Handle input submission
const handleInputSubmit = (e) => {
e.preventDefault();
if (!input.trim() || !connected) return;
// Send input to server
wsRef.current.send(input);
// Add input to output display
setOutput(prev => [...prev, { type: 'input', content: input }]);
// Clear input field
setInput('');
};
return (
<div className="websocket-terminal">
<div className="terminal-header">
<div className="terminal-title">
{connected ? 'Connected' : 'Disconnected'}
{submissionId && ` - Execution ID: ${submissionId}`}
</div>
<button className="terminal-close" onClick={onClose}>×</button>
</div>
<div className="terminal-output" ref={outputRef}>
{output.map((line, index) => (
<div key={index} className={`terminal-line ${line.type}`}>
{line.type === 'input' && <span className="input-prefix">&gt; </span>}
{line.content}
</div>
))}
</div>
<form className="terminal-input-form" onSubmit={handleInputSubmit}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Enter input..."
disabled={!connected}
className="terminal-input-field"
/>
<button
type="submit"
disabled={!connected}
className="terminal-input-submit"
>
Send
</button>
</form>
<style jsx>{`
.websocket-terminal {
display: flex;
flex-direction: column;
height: 100%;
background-color: #1e1e1e;
color: #d4d4d4;
font-family: 'Consolas', monospace;
border-radius: 4px;
overflow: hidden;
}
.terminal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background-color: #252526;
border-bottom: 1px solid #333;
}
.terminal-title {
font-size: 14px;
}
.terminal-close {
background: none;
border: none;
color: #d4d4d4;
font-size: 18px;
cursor: pointer;
}
.terminal-output {
flex: 1;
padding: 12px;
overflow-y: auto;
font-size: 14px;
line-height: 1.5;
}
.terminal-line {
margin-bottom: 4px;
white-space: pre-wrap;
word-break: break-word;
}
.terminal-line.system {
color: #569cd6;
}
.terminal-line.error {
color: #f44747;
}
.terminal-line.input {
color: #ce9178;
}
.input-prefix {
color: #569cd6;
font-weight: bold;
}
.terminal-input-form {
display: flex;
padding: 8px;
background-color: #252526;
border-top: 1px solid #333;
}
.terminal-input-field {
flex: 1;
background-color: #1e1e1e;
color: #d4d4d4;
border: 1px solid #3c3c3c;
border-radius: 4px;
padding: 8px 12px;
font-family: 'Consolas', monospace;
font-size: 14px;
}
.terminal-input-field:focus {
outline: none;
border-color: #007acc;
}
.terminal-input-submit {
margin-left: 8px;
background-color: #0e639c;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
}
.terminal-input-submit:hover {
background-color: #1177bb;
}
.terminal-input-submit:disabled {
background-color: #3c3c3c;
cursor: not-allowed;
}
`}</style>
</div>
);
};
export default WebSocketTerminal;

View File

@@ -57,12 +57,12 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: var(--vscode-activityBar-background); background-color: var(--vscode-activityBar-background);
z-index: 10; /* Lower z-index than the StatusBar */ z-index: 10;
position: fixed; width: 50px;
height: 100%;
position: fixed; /* Change to fixed to avoid layout issues */
top: 0; top: 0;
left: 0; left: 0;
height: calc(100% - 22px); /* Subtract the height of the StatusBar */
width: 50px;
} }
.activity-bar button { .activity-bar button {
@@ -404,7 +404,7 @@ body {
flex: 1; flex: 1;
overflow: auto; overflow: auto;
font-family: "Consolas", "Courier New", monospace; font-family: "Consolas", "Courier New", monospace;
font-size: 10px; font-size: 13px;
padding: 8px; padding: 8px;
padding: 10px; padding: 10px;
font-family: 'Consolas', 'Courier New', monospace; font-family: 'Consolas', 'Courier New', monospace;
@@ -418,16 +418,6 @@ body {
height: 100%; height: 100%;
} }
.panel-terminal {
padding: 8px;
font-family: monospace;
overflow-y: auto;
height: calc(100% - 36px); /* Adjust based on your header height */
background-color: #1e1e1e;
color: #ddd;
outline: none; /* Remove focus outline */
}
.panel-terminal .terminal-line { .panel-terminal .terminal-line {
white-space: pre-wrap; white-space: pre-wrap;
margin-bottom: 3px; margin-bottom: 3px;
@@ -436,20 +426,46 @@ body {
.terminal-line { .terminal-line {
white-space: pre-wrap; white-space: pre-wrap;
line-height: 1.5; line-height: 1.5;
margin-bottom: 2px;
} }
.terminal-prompt { .terminal-prompt {
color: #0a84ff; color: #0f0;
margin-right: 8px; margin-right: 8px;
color: #569cd6;
margin-right: 6px;
}
.terminal-input-marker {
color: #4ec9b0;
margin-right: 8px;
font-weight: bold;
}
.terminal-prompt-marker {
color: #569cd6;
margin-right: 8px;
font-weight: bold;
}
.terminal-prompt-line {
color: #569cd6;
font-weight: bold;
}
.terminal-input-line {
color: #4ec9b0;
background-color: rgba(78, 201, 176, 0.1);
padding: 2px 8px;
border-radius: 3px;
} }
.terminal-output { .terminal-output {
color: #ddd; color: #888888;
color: #cccccc;
} }
.terminal-warning { .terminal-warning {
color: #ffa500; color: #ddb100;
} }
.output-line { .output-line {
@@ -471,8 +487,9 @@ body {
} }
@keyframes blink { @keyframes blink {
0%, 100% { opacity: 1; } 50% {
50% { opacity: 0; } opacity: 0;
}
} }
.panel-empty-message { .panel-empty-message {
@@ -869,7 +886,7 @@ body {
margin-right: 4px; margin-right: 4px;
} }
.terminal-toggle-button { .terminal-toggle-button, .websocket-toggle-button {
background-color: transparent; background-color: transparent;
border: none; border: none;
color: #cccccc; color: #cccccc;
@@ -880,7 +897,12 @@ body {
align-items: center; align-items: center;
} }
.terminal-toggle-button:hover { .terminal-toggle-button:hover, .websocket-toggle-button:hover {
opacity: 1;
}
.websocket-toggle-button.active {
color: #4ec9b0;
opacity: 1; opacity: 1;
} }
@@ -932,18 +954,159 @@ body {
} }
.terminal-input { .terminal-input {
background-color: transparent; background-color: rgba(78, 201, 176, 0.1);
border: none; border: 1px solid rgba(78, 201, 176, 0.3);
color: inherit; border-radius: 3px;
color: #4ec9b0;
font-family: monospace; font-family: monospace;
font-size: inherit; font-size: inherit;
margin-left: 8px; margin-left: 8px;
outline: none; outline: none;
width: calc(100% - 60px); width: calc(100% - 60px);
padding: 4px 8px;
} }
.terminal-input:focus { .terminal-input:focus {
outline: none; outline: none;
border-color: rgba(78, 201, 176, 0.6);
}
.terminal-input-container {
margin: 10px 0;
padding: 10px;
background-color: rgba(78, 201, 176, 0.05);
border-radius: 5px;
border-left: 3px solid #4ec9b0;
}
.terminal-input-header {
margin-bottom: 8px;
}
.terminal-input-wrapper {
margin-bottom: 8px;
display: flex;
align-items: center;
}
.terminal-input-prompt {
color: #4ec9b0;
font-weight: bold;
margin-right: 8px;
font-size: 18px;
}
.terminal-input-help {
font-size: 12px;
color: #888888;
font-style: italic;
}
/* WebSocket Terminal */
.websocket-terminal {
display: flex;
flex-direction: column;
height: 100%;
background-color: #1e1e1e;
color: #d4d4d4;
font-family: 'Consolas', monospace;
border-radius: 4px;
overflow: hidden;
}
.terminal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background-color: #252526;
border-bottom: 1px solid #333;
}
.terminal-title {
font-size: 14px;
}
.terminal-close {
background: none;
border: none;
color: #d4d4d4;
font-size: 18px;
cursor: pointer;
}
.terminal-output {
flex: 1;
padding: 12px;
overflow-y: auto;
font-size: 14px;
line-height: 1.5;
}
.terminal-line {
margin-bottom: 4px;
white-space: pre-wrap;
word-break: break-word;
}
.terminal-line.system {
color: #569cd6;
}
.terminal-line.error {
color: #f44747;
}
.terminal-line.input {
color: #ce9178;
}
.input-prefix {
color: #569cd6;
font-weight: bold;
}
.terminal-input-form {
display: flex;
padding: 8px;
background-color: #252526;
border-top: 1px solid #333;
}
.terminal-input-field {
flex: 1;
background-color: #1e1e1e;
color: #d4d4d4;
border: 1px solid #3c3c3c;
border-radius: 4px;
padding: 8px 12px;
font-family: 'Consolas', monospace;
font-size: 14px;
}
.terminal-input-field:focus {
outline: none;
border-color: #007acc;
}
.terminal-input-submit {
margin-left: 8px;
background-color: #0e639c;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
}
.terminal-input-submit:hover {
background-color: #1177bb;
}
.terminal-input-submit:disabled {
background-color: #3c3c3c;
cursor: not-allowed;
} }
.terminal-line.info { .terminal-line.info {
@@ -971,8 +1134,9 @@ body {
} }
@keyframes blink { @keyframes blink {
0%, 100% { opacity: 1; } 50% {
50% { opacity: 0; } opacity: 0;
}
} }
/* Make sure the monaco container adjusts when terminal is shown */ /* Make sure the monaco container adjusts when terminal is shown */

297
Readme.md
View File

@@ -1,240 +1,125 @@
# Monaco Code Execution Engine # Monaco Online Code Compiler
Monaco is a secure, containerized code execution engine that allows you to run code in multiple programming languages through a simple REST API and WebSocket connections for real-time terminal interaction. A full-featured online code compiler with a VS Code-like interface. This project allows users to write, edit, and execute code in multiple programming languages directly in the browser.
## Features ## Features
- **Multi-language support**: Run code in Python, Java, C, and C++ - **VS Code-like Interface**: Familiar editor experience with syntax highlighting, tabs, and file explorer
- **Secure execution**: All code runs in isolated Docker containers - **Multi-language Support**: Run code in Python, JavaScript, Go, Java, C, and C++
- **Resource limits**: Memory, CPU, and file descriptor limits to prevent abuse - **Input/Output Handling**: Enter input for your programs and see the output in real-time
- **Concurrent processing**: Efficient job queue for handling multiple requests - **Secure Execution**: Code runs in isolated Docker containers on the backend
- **Simple REST API**: Easy to integrate with any frontend - **File Management**: Create, edit, and organize files and folders
- **Interactive terminal**: Real-time code execution with input/output via WebSockets
- **VS Code-like interface**: Modern editor with syntax highlighting and file management
## Architecture ## Project Structure
Monaco consists of several components: - **Frontend**: React-based UI with Monaco Editor
- **Backend**: Go-based code execution service with Docker integration
- HTTP Handlers (internal/api/handlers): Processes API requests
- Execution Service (internal/executor): Manages code execution in containers
- Job Queue (internal/queue): Handles concurrent execution of code submissions
- Submission Model (internal/models): Defines the data structure for code submissions
### Backend Components ## Getting Started
- **HTTP Handlers** (`handler/handler.go`): Processes API requests and WebSocket connections ### Prerequisites
- **Execution Service** (`service/execution.go`): Manages code execution in containers
- **Job Queue** (`queue/queue.go`): Handles concurrent execution of code submissions
- **Submission Model** (`model/submission.go`): Defines the data structure for code submissions
### Frontend Components - Node.js 18+ for the frontend
- Go 1.22+ for the backend
- Docker for code execution
- **Editor Area** (`EditorArea.jsx`): Main code editor with Monaco editor integration ### Running the Frontend
- **Terminal Panel** (`Panel.jsx`): Interactive terminal for code execution and input
- **Sidebar** (`Sidebar.jsx`): File explorer and project structure navigation
- **Status Bar** (`StatusBar.jsx`): Information display and quick actions
### Communication Flow
1. Frontend submits code to backend via REST API
2. Backend assigns a unique ID and queues the execution
3. Frontend connects to WebSocket endpoint with the execution ID
4. Backend sends real-time execution output through WebSocket
5. Frontend can send user input back through WebSocket
6. Results are stored and retrievable via REST endpoints
## Requirements
- **Backend**:
- Go 1.22.3 or higher
- Docker
- Network connectivity for container image pulling
- **Frontend**:
- Node.js and npm/yarn
- Modern web browser
## Installation
### Backend Setup
1. Clone the repository:
```bash
git clone https://github.com/arnab-afk/monaco.git
cd monaco/backend
2.Install Go dependencies:
```bash
go mod download
```
3.Build the application:
```bash
go build -o monaco
```
4.Run the service
```bash
./monaco
```
The backend service will start on port 8080 by default.
### Frontend Setup
1. Navigate to the Frontend directory:
```bash ```bash
cd Frontend cd Frontend
```
2. Install dependencies:
```bash
npm install npm install
```
3. Set up environment variables: Create a ```.env``` or ```.env.local.``` file with:
```bash
VITE_API_URL=http://localhost:8080
```
4. Start the development server:
```bash
npm run dev npm run dev
``` ```
The frontend will be available at http://localhost:5173 by default. The frontend will be available at http://localhost:5173
### API Reference ### Running the Backend
### REST Endpoints ```bash
```POST /submit``` cd backend
go build -o monaco ./cmd/server
Submits code for execution ./monaco
```json
{
"language": "python",
"code": "print('Hello, World!')",
"input": ""
}
``` ```
Response: The backend API will be available at http://localhost:8080
```json
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1"
}
```
```GET /status?id={submissionId}``` ## Using the Online Compiler
Checks the status of submission: 1. **Create a File**: Click the "+" button in the editor tabs or use the file explorer
```json 2. **Write Code**: Use the Monaco editor to write your code
{ 3. **Run Code**: Click the "Play" button in the top right corner
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1", 4. **Enter Input**: If your program requires input, enter it in the terminal panel
"status": "completed", 5. **View Output**: See the execution results in the terminal panel
"queuedAt": "2025-03-25T14:30:00Z",
"startedAt": "2025-03-25T14:30:01Z",
"completedAt": "2025-03-25T14:30:02Z",
"executionTime": 1000
}
```
```GET /result?id={submissionId}``` ## Supported Languages
Gets the execution result of a submission. - **Python** (.py)
- **JavaScript** (.js)
- **Go** (.go)
- **Java** (.java)
- **C** (.c)
- **C++** (.cpp)
Response: ## Examples
```json
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
"status": "completed",
"language": "python",
"output": "Hello, World!",
"queuedAt": "2025-03-25T14:30:00Z",
"startedAt": "2025-03-25T14:30:01Z",
"completedAt": "2025-03-25T14:30:02Z",
"executionTime": 1000,
"executionTimeFormatted": "1.0s",
"totalTime": 2000,
"totalTimeFormatted": "2.0s"
}
```
```GET /queue-stats```
Gets the statistics about the job queue.
Response:
```json
{
"queue_stats": {
"queue_length": 5,
"max_workers": 3,
"running_jobs": 3
},
"submissions": 42
}
```
### WebSocket Endpoints
```ws://localhost:8080/ws/terminal?id={submissionId}```
Establishes a real-time connection for terminal interaction.
- The server sends execution output as plain text messages.
- The client can send input as plain text messages (with newline).
- Connection automatically closes when execution completes or fails.
### Terminal Input Handling
The system supports interactive programs requiring user input:
1. The frontend detects possible input prompts by looking for patterns
2. When detected, it focuses the terminal and allows user input
3. User input is captured in the terminal component's inputBuffer
4. When the user presses Enter, the input is:
- Sent to the backend via WebSocket.
- Displayed in the terminal.
- Buffer is cleared for next input.
5. The input is processed by the running program in real-time.
Troubleshooting tips:
- Ensure WebSocket connection is established before sending input
- Check for WebSocket errors in console
- Verify input reaches the backend by checking server logs
- Ensure newline characters are properly appended to input.
### Language Support
### Python ### Python
- **Version**: Python 3.9
- **Input Handling**: Direct stdin piping
- **Limitations**: No file I/O, no package imports outside standard library
- **Resource Limits**: 100MB memory, 10% CPU
### Java
- **Version**: Java 11 (Eclipse Temurin)
- **Class Detection**: Extracts class name from code using regex.
- **Memory Settings**: 64MB min heap, 256MB max heap
- **Resource Limits**: 400MB memory, 50% CPU
C
- **Version**: Latest GCC
- **Compilation Flags**: Default GCC settings
- **Resource Limits**: 100MB memory, 10% CPU
### C++ ```python
- **Version**: Latest G++ name = input("Enter your name: ")
- **Standard**: C++17 print(f"Hello, {name}!")
- **Resource Limits**: 100MB memory, 10% CPU for i in range(5):
print(f"Count: {i}")
```
### Security Considerations ### JavaScript
All code execution happens within isolated Docker containers with:
- No network access (```--network=none```) ```javascript
- Limited CPU and memory resources const readline = require('readline');
- Limited file system access const rl = readline.createInterface({
- No persistent storage input: process.stdin,
- Execution time limits (10-15 seconds) output: process.stdout
});
### Debugging rl.question('Enter your name: ', (name) => {
Check backend logs for execution details console.log(`Hello, ${name}!`);
Use browser developer tools to debug WebSocket connections for (let i = 0; i < 5; i++) {
Terminal panel shows WebSocket connection status and errors console.log(`Count: ${i}`);
Check Docker logs for container-related issues. }
rl.close();
});
```
### Contributing ### Go
Contributions are welcome! Please feel free to submit a Pull Request.
```go
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
fmt.Print("Enter your name: ")
reader := bufio.NewReader(os.Stdin)
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
fmt.Printf("Hello, %s!\n", name)
for i := 0; i < 5; i++ {
fmt.Printf("Count: %d\n", i)
}
}
```
## Security Considerations
- All code is executed in isolated Docker containers
- Network access is disabled
- Memory and CPU limits are enforced
- Execution timeouts prevent infinite loops

View File

@@ -20,7 +20,7 @@
Monaco is a secure, containerized code execution backend service designed to run user-submitted code in multiple programming languages. It features a job queue system to manage execution resources, containerized execution environments for security, and a RESTful API for submission and monitoring. Monaco is a secure, containerized code execution backend service designed to run user-submitted code in multiple programming languages. It features a job queue system to manage execution resources, containerized execution environments for security, and a RESTful API for submission and monitoring.
**Key Features:** **Key Features:**
- Multi-language support (Python, Java, C, C++) - Multi-language support (Python, JavaScript, Go, Java, C, C++)
- Secure containerized execution using Docker - Secure containerized execution using Docker
- Resource limiting to prevent abuse - Resource limiting to prevent abuse
- Job queuing for managing concurrent executions - Job queuing for managing concurrent executions
@@ -34,10 +34,10 @@ Monaco is a secure, containerized code execution backend service designed to run
Monaco follows a layered architecture with the following key components: Monaco follows a layered architecture with the following key components:
1. **HTTP Handlers** (handler package) - Processes incoming HTTP requests 1. **HTTP Handlers** (internal/api/handlers) - Processes incoming HTTP requests
2. **Execution Service** (service package) - Manages code execution in containers 2. **Execution Service** (internal/executor) - Manages code execution in containers
3. **Job Queue** (queue package) - Controls concurrent execution 3. **Job Queue** (internal/queue) - Controls concurrent execution
4. **Data Models** (model package) - Defines data structures 4. **Data Models** (internal/models) - Defines data structures
### Request Flow ### Request Flow
@@ -60,10 +60,12 @@ Client Request → HTTP Handlers → Execution Service → Job Queue → Docker
### Prerequisites ### Prerequisites
- Go 1.22+ - Go 1.22+
- Docker Engine - Docker Engine
- Docker images for supported languages: - Docker images for supported languages:
- `python:3.9` - `python:3.9`
- `node:18-alpine`
- `golang:1.22-alpine`
- `eclipse-temurin:11-jdk-alpine` - `eclipse-temurin:11-jdk-alpine`
- `gcc:latest` - `gcc:latest`
@@ -82,7 +84,7 @@ Client Request → HTTP Handlers → Execution Service → Job Queue → Docker
3. Build the application: 3. Build the application:
```bash ```bash
go build -o monaco main.go go build -o monaco ./cmd/server
``` ```
4. Run the service: 4. Run the service:
@@ -103,7 +105,7 @@ Submits code for execution.
**Request Body:** **Request Body:**
```json ```json
{ {
"language": "python", // Required: "python", "java", "c", or "cpp" "language": "python", // Required: "python", "javascript", "go", "java", "c", or "cpp"
"code": "print('Hello, World!')", // Required: source code to execute "code": "print('Hello, World!')", // Required: source code to execute
"input": "optional input string" // Optional: input to stdin "input": "optional input string" // Optional: input to stdin
} }
@@ -127,7 +129,7 @@ Checks the status of a submission.
**Response:** **Response:**
```json ```json
{ {
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1", "id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
"status": "completed", // "pending", "queued", "running", "completed", "failed" "status": "completed", // "pending", "queued", "running", "completed", "failed"
"queuedAt": "2025-03-25T14:30:00Z", "queuedAt": "2025-03-25T14:30:00Z",
"startedAt": "2025-03-25T14:30:01Z", // Only present if status is "running", "completed", or "failed" "startedAt": "2025-03-25T14:30:01Z", // Only present if status is "running", "completed", or "failed"
@@ -256,6 +258,17 @@ The queue tracks and reports:
- **Input Handling**: Direct stdin piping - **Input Handling**: Direct stdin piping
- **Limitations**: No file I/O, no package imports outside standard library - **Limitations**: No file I/O, no package imports outside standard library
### JavaScript
- **Version**: Node.js 18 (Alpine)
- **Input Handling**: File-based input redirection
- **Limitations**: No file I/O, no package imports outside standard library
### Go
- **Version**: Go 1.22 (Alpine)
- **Compilation**: Standard Go build process
- **Input Handling**: Direct stdin piping
- **Limitations**: No file I/O, no external dependencies
### Java ### Java
- **Version**: Java 11 (Eclipse Temurin) - **Version**: Java 11 (Eclipse Temurin)
- **Class Detection**: Extracts class name from code using regex - **Class Detection**: Extracts class name from code using regex

View File

@@ -0,0 +1,38 @@
package main
import (
"log"
"net/http"
"os"
"time"
"github.com/arnab-afk/monaco/internal/api"
)
func main() {
// Configure logging
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.SetOutput(os.Stdout)
log.Println("Starting Monaco code execution backend...")
// Initialize router with all routes
router := api.SetupRoutes()
// Start the server
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
server := &http.Server{
Addr: ":" + port,
Handler: router,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
log.Printf("Server started at :%s", port)
log.Fatal(server.ListenAndServe())
}

View File

@@ -0,0 +1,155 @@
# Monaco Code Execution Examples
This document provides examples of code submissions for each supported language.
## Python
```json
{
"language": "python",
"code": "name = input('Enter your name: ')\nprint(f'Hello, {name}!')\nfor i in range(5):\n print(f'Count: {i}')",
"input": "World"
}
```
Expected output:
```
Enter your name: Hello, World!
Count: 0
Count: 1
Count: 2
Count: 3
Count: 4
```
## JavaScript
```json
{
"language": "javascript",
"code": "const readline = require('readline');\nconst rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout\n});\n\nrl.question('Enter your name: ', (name) => {\n console.log(`Hello, ${name}!`);\n for (let i = 0; i < 5; i++) {\n console.log(`Count: ${i}`);\n }\n rl.close();\n});",
"input": "World"
}
```
Expected output:
```
Enter your name: Hello, World!
Count: 0
Count: 1
Count: 2
Count: 3
Count: 4
```
## Go
```json
{
"language": "go",
"code": "package main\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc main() {\n\tfmt.Print(\"Enter your name: \")\n\treader := bufio.NewReader(os.Stdin)\n\tname, _ := reader.ReadString('\\n')\n\tname = strings.TrimSpace(name)\n\tfmt.Printf(\"Hello, %s!\\n\", name)\n\tfor i := 0; i < 5; i++ {\n\t\tfmt.Printf(\"Count: %d\\n\", i)\n\t}\n}",
"input": "World"
}
```
Expected output:
```
Enter your name: Hello, World!
Count: 0
Count: 1
Count: 2
Count: 3
Count: 4
```
## Java
```json
{
"language": "java",
"code": "import java.util.Scanner;\n\npublic class Main {\n public static void main(String[] args) {\n Scanner scanner = new Scanner(System.in);\n System.out.print(\"Enter your name: \");\n String name = scanner.nextLine();\n System.out.println(\"Hello, \" + name + \"!\");\n for (int i = 0; i < 5; i++) {\n System.out.println(\"Count: \" + i);\n }\n scanner.close();\n }\n}",
"input": "World"
}
```
Expected output:
```
Enter your name: Hello, World!
Count: 0
Count: 1
Count: 2
Count: 3
Count: 4
```
## C
```json
{
"language": "c",
"code": "#include <stdio.h>\n\nint main() {\n char name[100];\n printf(\"Enter your name: \");\n scanf(\"%s\", name);\n printf(\"Hello, %s!\\n\", name);\n for (int i = 0; i < 5; i++) {\n printf(\"Count: %d\\n\", i);\n }\n return 0;\n}",
"input": "World"
}
```
Expected output:
```
Enter your name: Hello, World!
Count: 0
Count: 1
Count: 2
Count: 3
Count: 4
```
## C++
```json
{
"language": "cpp",
"code": "#include <iostream>\n#include <string>\n\nint main() {\n std::string name;\n std::cout << \"Enter your name: \";\n std::cin >> name;\n std::cout << \"Hello, \" << name << \"!\" << std::endl;\n for (int i = 0; i < 5; i++) {\n std::cout << \"Count: \" << i << std::endl;\n }\n return 0;\n}",
"input": "World"
}
```
Expected output:
```
Enter your name: Hello, World!
Count: 0
Count: 1
Count: 2
Count: 3
Count: 4
```
## Testing with cURL
You can test these examples using cURL:
```bash
curl -X POST http://localhost:8080/submit \
-H "Content-Type: application/json" \
-d '{
"language": "python",
"code": "name = input(\"Enter your name: \")\nprint(f\"Hello, {name}!\")\nfor i in range(5):\n print(f\"Count: {i}\")",
"input": "World"
}'
```
This will return a submission ID:
```json
{
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1"
}
```
You can then check the status and result:
```bash
curl http://localhost:8080/status?id=6423259c-ee14-c5aa-1c90-d5e989f92aa1
```
```bash
curl http://localhost:8080/result?id=6423259c-ee14-c5aa-1c90-d5e989f92aa1
```

View File

@@ -2,15 +2,12 @@ package handler
import ( import (
"encoding/json" "encoding/json"
"fmt"
"log"
"net/http" "net/http"
"sync" "sync"
"time" "time"
"github.com/arnab-afk/monaco/model" "github.com/arnab-afk/monaco/model"
"github.com/arnab-afk/monaco/service" "github.com/arnab-afk/monaco/service"
"github.com/gorilla/websocket"
) )
// Handler manages HTTP requests for code submissions // Handler manages HTTP requests for code submissions
@@ -182,63 +179,6 @@ func (h *Handler) QueueStatsHandler(w http.ResponseWriter, r *http.Request) {
}) })
} }
// ConnectTerminal connects a WebSocket to a running execution
func (h *Handler) ConnectTerminal(conn *websocket.Conn, executionID string) {
// Get submission from storage
h.mu.Lock()
submission, found := h.submissions[executionID]
status := "not found"
if found {
status = submission.Status
}
h.mu.Unlock()
log.Printf("[WS-%s] Terminal connection request, submission status: %s", executionID, status)
if !found {
log.Printf("[WS-%s] Execution not found", executionID)
conn.WriteMessage(websocket.TextMessage, []byte("Execution not found"))
conn.Close()
return
}
// If execution is already completed, send stored output and close
if submission.Status == "completed" || submission.Status == "failed" {
log.Printf("[WS-%s] Execution already %s, sending stored output (length: %d)",
executionID, submission.Status, len(submission.Output))
conn.WriteMessage(websocket.TextMessage, []byte(submission.Output))
conn.Close()
return
}
log.Printf("[WS-%s] Registering connection for real-time updates, current status: %s",
executionID, submission.Status)
// Register this connection with the execution service for real-time updates
h.executionService.RegisterTerminalConnection(executionID, conn)
// Send initial connection confirmation
initialMsg := fmt.Sprintf("[System] Connected to process (ID: %s, Status: %s)\n",
executionID, submission.Status)
conn.WriteMessage(websocket.TextMessage, []byte(initialMsg))
// Handle incoming messages from the terminal (for stdin)
go func() {
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Printf("[WS-%s] Read error: %v", executionID, err)
h.executionService.UnregisterTerminalConnection(executionID, conn)
break
}
log.Printf("[WS-%s] Received input from client: %s", executionID, string(message))
// Send input to the execution if it's waiting for input
h.executionService.SendInput(executionID, string(message))
}
}()
}
// generateID creates a unique ID for submissions // generateID creates a unique ID for submissions
func (h *Handler) generateID() string { func (h *Handler) generateID() string {
return service.GenerateUUID() return service.GenerateUUID()

View File

@@ -0,0 +1,85 @@
package handler
import (
"encoding/json"
"log"
"net/http"
"github.com/arnab-afk/monaco/model"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// Allow all origins for development
CheckOrigin: func(r *http.Request) bool { return true },
}
// WebSocketHandler handles WebSocket connections for code execution
func (h *Handler) WebSocketHandler(w http.ResponseWriter, r *http.Request) {
// Upgrade the HTTP connection to a WebSocket connection
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Failed to upgrade connection: %v", err)
return
}
// Read the initial message containing the code submission
_, message, err := conn.ReadMessage()
if err != nil {
log.Printf("Failed to read message: %v", err)
conn.Close()
return
}
// Parse the message as a code submission
var submission model.CodeSubmission
if err := json.Unmarshal(message, &submission); err != nil {
log.Printf("Failed to parse message: %v", err)
conn.WriteMessage(websocket.TextMessage, []byte("Error: Invalid submission format"))
conn.Close()
return
}
// Validate the submission
if submission.Code == "" {
conn.WriteMessage(websocket.TextMessage, []byte("Error: Code is required"))
conn.Close()
return
}
// Set default language if not provided
if submission.Language == "" {
submission.Language = "python" // Default to Python
}
// Validate language
supportedLanguages := map[string]bool{
"python": true,
"java": true,
"c": true,
"cpp": true,
}
if !supportedLanguages[submission.Language] {
conn.WriteMessage(websocket.TextMessage, []byte("Error: Unsupported language: "+submission.Language))
conn.Close()
return
}
// Generate a unique ID for the submission
submission.ID = h.generateID()
submission.Status = "pending"
// Store the submission
h.mu.Lock()
h.submissions[submission.ID] = &submission
h.mu.Unlock()
// Send the submission ID to the client
conn.WriteMessage(websocket.TextMessage, []byte("Submission ID: "+submission.ID))
// Execute the code with WebSocket communication
h.executionService.HandleWebSocket(conn, &submission)
}

View File

@@ -0,0 +1,260 @@
package handlers
import (
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
"github.com/arnab-afk/monaco/internal/executor"
"github.com/arnab-afk/monaco/internal/models"
)
// Handler manages HTTP requests for code submissions
type Handler struct {
executionService *executor.ExecutionService
mu sync.Mutex
submissions map[string]*models.CodeSubmission
}
// NewHandler creates a new handler instance
func NewHandler() *Handler {
return &Handler{
executionService: executor.NewExecutionService(),
submissions: make(map[string]*models.CodeSubmission),
}
}
// SubmitHandler handles code submission requests
func (h *Handler) SubmitHandler(w http.ResponseWriter, r *http.Request) {
// Only allow POST method
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Parse the request body
var submission models.CodeSubmission
if err := json.NewDecoder(r.Body).Decode(&submission); err != nil {
http.Error(w, "Invalid request body: "+err.Error(), http.StatusBadRequest)
return
}
// Validate the submission
if submission.Code == "" {
http.Error(w, "Code is required", http.StatusBadRequest)
return
}
if submission.Language == "" {
http.Error(w, "Language is required", http.StatusBadRequest)
return
}
// Generate a unique ID for the submission
h.mu.Lock()
submission.ID = executor.GenerateUUID()
submission.Status = "pending"
h.submissions[submission.ID] = &submission
h.mu.Unlock()
// Execute the code in a goroutine
go h.executionService.ExecuteCode(&submission)
// Return the submission ID
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
json.NewEncoder(w).Encode(map[string]string{"id": submission.ID})
}
// StatusHandler handles status check requests
func (h *Handler) StatusHandler(w http.ResponseWriter, r *http.Request) {
// Only allow GET method
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Get the submission ID from the query parameters
id := r.URL.Query().Get("id")
if id == "" {
http.Error(w, "ID is required", http.StatusBadRequest)
return
}
// Get the submission from the map
h.mu.Lock()
submission, exists := h.submissions[id]
h.mu.Unlock()
if !exists {
http.Error(w, "Submission not found", http.StatusNotFound)
return
}
// Return the submission status
response := map[string]interface{}{
"id": submission.ID,
"status": submission.Status,
}
// Add time information based on status
if !submission.QueuedAt.IsZero() {
response["queuedAt"] = submission.QueuedAt.Format(time.RFC3339)
}
if !submission.StartedAt.IsZero() {
response["startedAt"] = submission.StartedAt.Format(time.RFC3339)
}
if !submission.CompletedAt.IsZero() {
response["completedAt"] = submission.CompletedAt.Format(time.RFC3339)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
// ResultHandler handles result requests
func (h *Handler) ResultHandler(w http.ResponseWriter, r *http.Request) {
// Only allow GET method
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Get the submission ID from the query parameters
id := r.URL.Query().Get("id")
if id == "" {
http.Error(w, "ID is required", http.StatusBadRequest)
return
}
// Get the submission from the map
h.mu.Lock()
submission, exists := h.submissions[id]
h.mu.Unlock()
if !exists {
http.Error(w, "Submission not found", http.StatusNotFound)
return
}
// Return the submission result
response := map[string]interface{}{
"id": submission.ID,
"status": submission.Status,
"language": submission.Language,
"output": submission.Output,
"input": submission.Input,
}
// Add error information if available
if submission.Error != "" {
response["error"] = submission.Error
}
// Add time information
if !submission.QueuedAt.IsZero() {
response["queuedAt"] = submission.QueuedAt.Format(time.RFC3339)
}
if !submission.StartedAt.IsZero() {
response["startedAt"] = submission.StartedAt.Format(time.RFC3339)
}
if !submission.CompletedAt.IsZero() {
response["completedAt"] = submission.CompletedAt.Format(time.RFC3339)
if !submission.StartedAt.IsZero() {
response["executionTime"] = submission.CompletedAt.Sub(submission.StartedAt).Milliseconds()
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
// QueueStatsHandler provides information about the job queue
func (h *Handler) QueueStatsHandler(w http.ResponseWriter, r *http.Request) {
// Only allow GET method
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Get the queue statistics
stats := h.executionService.GetQueueStats()
// Return the queue statistics
response := map[string]interface{}{
"queue_stats": stats,
"submissions": len(h.submissions),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
// SubmitInputHandler handles interactive input submission
func (h *Handler) SubmitInputHandler(w http.ResponseWriter, r *http.Request) {
// Only allow POST method
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Parse the request body
var inputRequest struct {
ID string `json:"id"`
Input string `json:"input"`
}
if err := json.NewDecoder(r.Body).Decode(&inputRequest); err != nil {
http.Error(w, "Invalid request body: "+err.Error(), http.StatusBadRequest)
return
}
// Validate the request
if inputRequest.ID == "" {
http.Error(w, "ID is required", http.StatusBadRequest)
return
}
// Get the submission from the map
h.mu.Lock()
submission, exists := h.submissions[inputRequest.ID]
h.mu.Unlock()
if !exists {
http.Error(w, "Submission not found", http.StatusNotFound)
return
}
// Check if the submission is waiting for input or running
// We're more lenient here to handle race conditions
if submission.Status != "waiting_for_input" && submission.Status != "running" {
http.Error(w, fmt.Sprintf("Submission is not waiting for input (status: %s)", submission.Status), http.StatusBadRequest)
return
}
// Send the input to the execution service
h.executionService.SubmitInput(submission, inputRequest.Input)
// Return success response
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "input_submitted"})
}
// HealthCheckHandler handles health check requests
func (h *Handler) HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
// Only allow GET method
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Return a simple health check response
response := map[string]interface{}{
"status": "ok",
"timestamp": time.Now().Format(time.RFC3339),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}

View File

@@ -0,0 +1,70 @@
package handlers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSubmitHandler(t *testing.T) {
h := NewHandler()
// Create a test request
reqBody := map[string]string{
"language": "python",
"code": "print('Hello, World!')",
"input": "",
}
reqJSON, _ := json.Marshal(reqBody)
req, err := http.NewRequest("POST", "/submit", bytes.NewBuffer(reqJSON))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
// Create a response recorder
rr := httptest.NewRecorder()
// Call the handler
h.SubmitHandler(rr, req)
// Check the status code
assert.Equal(t, http.StatusAccepted, rr.Code)
// Check the response body
var response map[string]string
err = json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "id")
assert.NotEmpty(t, response["id"])
}
func TestHealthCheckHandler(t *testing.T) {
h := NewHandler()
// Create a test request
req, err := http.NewRequest("GET", "/health", nil)
if err != nil {
t.Fatal(err)
}
// Create a response recorder
rr := httptest.NewRecorder()
// Call the handler
h.HealthCheckHandler(rr, req)
// Check the status code
assert.Equal(t, http.StatusOK, rr.Code)
// Check the response body
var response map[string]interface{}
err = json.Unmarshal(rr.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "ok", response["status"])
assert.Contains(t, response, "timestamp")
}

View File

@@ -0,0 +1,49 @@
package handlers
import (
"log"
"net/http"
"time"
)
// LoggingMiddleware logs HTTP requests
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
startTime := time.Now()
log.Printf("[HTTP] %s %s %s", r.Method, r.URL.Path, r.RemoteAddr)
next.ServeHTTP(w, r)
log.Printf("[HTTP] %s %s completed in %v", r.Method, r.URL.Path, time.Since(startTime))
})
}
// CORSMiddleware adds CORS headers to responses
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Set CORS headers
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// Handle preflight requests
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
// Call the next handler
next.ServeHTTP(w, r)
})
}
// RecoveryMiddleware recovers from panics
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("[PANIC] %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}

View File

@@ -0,0 +1,261 @@
package handlers
import (
"fmt"
"log"
"net/http"
"sync"
"time"
"github.com/arnab-afk/monaco/internal/executor"
"github.com/arnab-afk/monaco/internal/models"
"github.com/gorilla/websocket"
)
// WebSocketTerminal represents a terminal session over WebSocket
type WebSocketTerminal struct {
ID string
Conn *websocket.Conn
InputChan chan string
OutputChan chan string
Done chan struct{}
mu sync.Mutex
}
var (
// Configure the upgrader
upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// Allow all origins for development
CheckOrigin: func(r *http.Request) bool { return true },
}
// Active terminal sessions
terminals = make(map[string]*WebSocketTerminal)
terminalsMu sync.Mutex
)
// TerminalHandler handles WebSocket connections for terminal sessions
func (h *Handler) TerminalHandler(w http.ResponseWriter, r *http.Request) {
// Upgrade the HTTP connection to a WebSocket connection
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Failed to upgrade connection: %v", err)
return
}
// Generate a unique ID for this terminal session
terminalID := executor.GenerateUUID()
// Create channels for communication
inputChan := make(chan string)
outputChan := make(chan string)
done := make(chan struct{})
// Create a new terminal session
terminal := &WebSocketTerminal{
ID: terminalID,
Conn: conn,
InputChan: inputChan,
OutputChan: outputChan,
Done: done,
}
// Store the terminal session
terminalsMu.Lock()
terminals[terminalID] = terminal
terminalsMu.Unlock()
// Send the terminal ID to the client
if err := conn.WriteJSON(map[string]string{"type": "terminal_id", "id": terminalID}); err != nil {
log.Printf("Failed to send terminal ID: %v", err)
conn.Close()
return
}
// Handle incoming messages (input from the client)
go func() {
defer func() {
close(done)
conn.Close()
// Remove the terminal from the map
terminalsMu.Lock()
delete(terminals, terminalID)
terminalsMu.Unlock()
log.Printf("Terminal session %s closed", terminalID)
}()
for {
// Read message from the WebSocket
messageType, message, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("WebSocket error: %v", err)
}
return
}
// Handle different message types
if messageType == websocket.TextMessage {
// Parse the message
input := string(message)
// Send the input to the execution service
select {
case inputChan <- input:
// Input sent successfully
case <-done:
return
}
}
}
}()
// Handle outgoing messages (output to the client)
go func() {
for {
select {
case output := <-outputChan:
// Send the output to the client
err := conn.WriteMessage(websocket.TextMessage, []byte(output))
if err != nil {
log.Printf("Failed to write message: %v", err)
return
}
case <-done:
return
}
}
}()
// Keep the connection alive with ping/pong
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
case <-done:
return
}
}
}()
}
// ExecuteCodeWebSocket executes code and streams the output over WebSocket
func (h *Handler) ExecuteCodeWebSocket(w http.ResponseWriter, r *http.Request) {
// Upgrade the HTTP connection to a WebSocket connection
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Failed to upgrade connection: %v", err)
return
}
defer conn.Close()
// Read the initial message containing the code to execute
_, message, err := conn.ReadMessage()
if err != nil {
log.Printf("Failed to read message: %v", err)
return
}
// Parse the message into a code submission
var submission models.CodeSubmission
if err := submission.UnmarshalJSON(message); err != nil {
log.Printf("Failed to parse submission: %v", err)
conn.WriteJSON(map[string]string{"error": "Invalid submission format"})
return
}
// Generate a unique ID for the submission
submission.ID = executor.GenerateUUID()
submission.Status = "pending"
// Store the submission
h.mu.Lock()
h.submissions[submission.ID] = &submission
h.mu.Unlock()
// Create channels for communication
inputChan := make(chan string)
outputChan := make(chan string)
done := make(chan struct{})
// Set up the execution service to use these channels
h.executionService.SetupWebSocketChannels(&submission, inputChan, outputChan)
// Send the submission ID to the client
if err := conn.WriteJSON(map[string]string{"type": "submission_id", "id": submission.ID}); err != nil {
log.Printf("Failed to send submission ID: %v", err)
return
}
// Execute the code in a goroutine
go func() {
h.executionService.ExecuteCodeWebSocket(&submission)
close(done)
}()
// Handle incoming messages (input from the client)
go func() {
for {
select {
case <-done:
return
default:
// Read message from the WebSocket
_, message, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("WebSocket error: %v", err)
}
return
}
// Send the input to the execution service
select {
case inputChan <- string(message):
// Input sent successfully
case <-done:
return
}
}
}
}()
// Handle outgoing messages (output to the client)
for {
select {
case output := <-outputChan:
// Send the output to the client
err := conn.WriteMessage(websocket.TextMessage, []byte(output))
if err != nil {
log.Printf("Failed to write message: %v", err)
return
}
case <-done:
// Execution completed
return
}
}
}
// GetTerminal returns a terminal session by ID
func GetTerminal(id string) (*WebSocketTerminal, error) {
terminalsMu.Lock()
defer terminalsMu.Unlock()
terminal, exists := terminals[id]
if !exists {
return nil, fmt.Errorf("terminal not found: %s", id)
}
return terminal, nil
}

View File

@@ -0,0 +1,32 @@
package api
import (
"net/http"
"github.com/arnab-afk/monaco/internal/api/handlers"
)
// SetupRoutes configures all API routes
func SetupRoutes() http.Handler {
// Create a new handler
h := handlers.NewHandler()
// Create a new router
mux := http.NewServeMux()
// Apply middleware to all routes
var handler http.Handler = mux
handler = handlers.RecoveryMiddleware(handler)
handler = handlers.LoggingMiddleware(handler)
handler = handlers.CORSMiddleware(handler)
// Register routes
mux.HandleFunc("/submit", h.SubmitHandler)
mux.HandleFunc("/status", h.StatusHandler)
mux.HandleFunc("/result", h.ResultHandler)
mux.HandleFunc("/submit-input", h.SubmitInputHandler)
mux.HandleFunc("/queue-stats", h.QueueStatsHandler)
mux.HandleFunc("/health", h.HealthCheckHandler)
return handler
}

View File

@@ -0,0 +1,683 @@
package executor
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"time"
"github.com/arnab-afk/monaco/internal/models"
"github.com/arnab-afk/monaco/internal/queue"
)
// ExecutionService manages code execution
type ExecutionService struct {
queue *queue.JobQueue
mu sync.Mutex
// Map of submission ID to input channel for interactive programs
inputChannels map[string]chan string
// WebSocket channels for real-time communication
wsInputChannels map[string]chan string
wsOutputChannels map[string]chan string
}
// CodeExecutionJob represents a code execution job
type CodeExecutionJob struct {
service *ExecutionService
submission *models.CodeSubmission
}
// NewExecutionService creates a new execution service
func NewExecutionService() *ExecutionService {
return &ExecutionService{
queue: queue.NewJobQueue(5), // 5 concurrent workers
inputChannels: make(map[string]chan string),
wsInputChannels: make(map[string]chan string),
wsOutputChannels: make(map[string]chan string),
}
}
// NewCodeExecutionJob creates a new code execution job
func NewCodeExecutionJob(service *ExecutionService, submission *models.CodeSubmission) *CodeExecutionJob {
return &CodeExecutionJob{
service: service,
submission: submission,
}
}
// Execute runs the code execution job
func (j *CodeExecutionJob) Execute() {
submission := j.submission
submission.Status = "running"
submission.StartedAt = time.Now()
log.Printf("[JOB-%s] Starting execution for language: %s", submission.ID, submission.Language)
j.service.executeLanguageSpecific(submission)
submission.CompletedAt = time.Now()
log.Printf("[JOB-%s] Execution completed in %v", submission.ID, submission.CompletedAt.Sub(submission.StartedAt))
}
// ExecuteCode adds the submission to the execution queue
func (s *ExecutionService) ExecuteCode(submission *models.CodeSubmission) {
submission.Status = "queued"
submission.QueuedAt = time.Now()
log.Printf("[SUBMISSION-%s] Code submission queued for language: %s", submission.ID, submission.Language)
// Create and add the job to the queue
job := NewCodeExecutionJob(s, submission)
s.queue.AddJob(job)
}
// executeLanguageSpecific executes code based on the language
func (s *ExecutionService) executeLanguageSpecific(submission *models.CodeSubmission) {
switch strings.ToLower(submission.Language) {
case "python":
s.executePython(submission)
case "javascript", "js":
s.executeJavaScript(submission)
case "go", "golang":
s.executeGo(submission)
case "java":
s.executeJava(submission)
case "c":
s.executeC(submission)
case "cpp", "c++":
s.executeCpp(submission)
default:
submission.Status = "failed"
submission.Error = fmt.Sprintf("Unsupported language: %s", submission.Language)
log.Printf("[EXEC-%s] ERROR: Unsupported language: %s", submission.ID, submission.Language)
}
}
// executePython runs Python code in a container
func (s *ExecutionService) executePython(submission *models.CodeSubmission) {
log.Printf("[PYTHON-%s] Preparing Python execution environment", submission.ID)
startTime := time.Now()
// Create a temporary file for the code
tempDir, err := os.MkdirTemp("", "monaco-python-*")
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to create temp directory: %v", err)
return
}
defer os.RemoveAll(tempDir)
// Write the code to a file
codePath := filepath.Join(tempDir, "code.py")
if err := os.WriteFile(codePath, []byte(submission.Code), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write code file: %v", err)
return
}
// Check if we should use interactive mode
if strings.Contains(submission.Code, "input(") {
// This code likely requires interactive input
submission.IsInteractive = true
s.executePythonInteractive(submission, tempDir)
return
}
// Non-interactive mode
// Create a file for input if provided
inputPath := ""
if submission.Input != "" {
inputPath = filepath.Join(tempDir, "input.txt")
if err := os.WriteFile(inputPath, []byte(submission.Input), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write input file: %v", err)
return
}
}
// Run the code in a Docker container
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var cmd *exec.Cmd
if inputPath != "" {
cmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"--ulimit", "nofile=64:64", // File descriptor limits
"-v", tempDir+":/code", // Mount code directory
"python:3.9",
"sh", "-c", "cat /code/input.txt | python /code/code.py")
} else {
cmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"--ulimit", "nofile=64:64", // File descriptor limits
"-v", tempDir+":/code", // Mount code directory
"python:3.9",
"python", "/code/code.py")
}
output, err := cmd.CombinedOutput()
elapsed := time.Since(startTime)
log.Printf("[PYTHON-%s] Python execution completed in %v", submission.ID, elapsed)
s.updateSubmissionResult(submission, output, err, ctx.Err() != nil)
}
// executeJavaScript runs JavaScript code in a container
func (s *ExecutionService) executeJavaScript(submission *models.CodeSubmission) {
log.Printf("[JS-%s] Preparing JavaScript execution environment", submission.ID)
startTime := time.Now()
// Create a temporary file for the code
tempDir, err := os.MkdirTemp("", "monaco-js-*")
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to create temp directory: %v", err)
return
}
defer os.RemoveAll(tempDir)
// Write the code to a file
codePath := filepath.Join(tempDir, "code.js")
if err := os.WriteFile(codePath, []byte(submission.Code), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write code file: %v", err)
return
}
// Check if we should use interactive mode
if strings.Contains(submission.Code, "readline") && strings.Contains(submission.Code, "question") {
// This code likely requires interactive input
submission.IsInteractive = true
s.executeJavaScriptInteractive(submission, tempDir)
return
}
// Create a file for input if provided
inputPath := ""
if submission.Input != "" {
inputPath = filepath.Join(tempDir, "input.txt")
if err := os.WriteFile(inputPath, []byte(submission.Input), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write input file: %v", err)
return
}
}
// Run the code in a Docker container
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var cmd *exec.Cmd
if inputPath != "" {
// Create a wrapper script to handle input
wrapperPath := filepath.Join(tempDir, "wrapper.js")
wrapperCode := `
const fs = require('fs');
const input = fs.readFileSync('/code/input.txt', 'utf8');
// Redirect input to stdin
process.stdin.push(input);
process.stdin.push(null);
// Load and run the user code
require('./code.js');
`
if err := os.WriteFile(wrapperPath, []byte(wrapperCode), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write wrapper file: %v", err)
return
}
cmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"-v", tempDir+":/code", // Mount code directory
"node:18-alpine",
"node", "/code/wrapper.js")
} else {
cmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"-v", tempDir+":/code", // Mount code directory
"node:18-alpine",
"node", "/code/code.js")
}
output, err := cmd.CombinedOutput()
elapsed := time.Since(startTime)
log.Printf("[JS-%s] JavaScript execution completed in %v", submission.ID, elapsed)
s.updateSubmissionResult(submission, output, err, ctx.Err() != nil)
}
// executeGo runs Go code in a container
func (s *ExecutionService) executeGo(submission *models.CodeSubmission) {
log.Printf("[GO-%s] Preparing Go execution environment", submission.ID)
startTime := time.Now()
// Create a temporary file for the code
tempDir, err := os.MkdirTemp("", "monaco-go-*")
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to create temp directory: %v", err)
return
}
defer os.RemoveAll(tempDir)
// Write the code to a file
codePath := filepath.Join(tempDir, "main.go")
if err := os.WriteFile(codePath, []byte(submission.Code), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write code file: %v", err)
return
}
// Create a file for input if provided
inputPath := ""
if submission.Input != "" {
inputPath = filepath.Join(tempDir, "input.txt")
if err := os.WriteFile(inputPath, []byte(submission.Input), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write input file: %v", err)
return
}
}
// Run the code in a Docker container
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// First compile the Go code
compileCmd := exec.CommandContext(ctx, "docker", "run", "--rm",
"-v", tempDir+":/code", // Mount code directory
"golang:1.22-alpine",
"go", "build", "-o", "/code/app", "/code/main.go")
compileOutput, compileErr := compileCmd.CombinedOutput()
if compileErr != nil {
log.Printf("[GO-%s] Compilation failed: %v", submission.ID, compileErr)
submission.Status = "failed"
submission.Error = fmt.Sprintf("Compilation error: %s", compileOutput)
return
}
// Then run the compiled binary
var runCmd *exec.Cmd
if inputPath != "" {
runCmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"-v", tempDir+":/code", // Mount code directory
"golang:1.22-alpine",
"sh", "-c", "cat /code/input.txt | /code/app")
} else {
runCmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"-v", tempDir+":/code", // Mount code directory
"golang:1.22-alpine",
"/code/app")
}
output, err := runCmd.CombinedOutput()
elapsed := time.Since(startTime)
log.Printf("[GO-%s] Go execution completed in %v", submission.ID, elapsed)
s.updateSubmissionResult(submission, output, err, ctx.Err() != nil)
}
// executeJava runs Java code in a container
func (s *ExecutionService) executeJava(submission *models.CodeSubmission) {
log.Printf("[JAVA-%s] Preparing Java execution environment", submission.ID)
startTime := time.Now()
// Create a temporary file for the code
tempDir, err := os.MkdirTemp("", "monaco-java-*")
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to create temp directory: %v", err)
return
}
defer os.RemoveAll(tempDir)
// Extract class name from the code
className := extractJavaClassName(submission.Code)
if className == "" {
className = "Main" // Default class name
}
// Write the code to a file
codePath := filepath.Join(tempDir, className+".java")
if err := os.WriteFile(codePath, []byte(submission.Code), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write code file: %v", err)
return
}
// Create a file for input if provided
inputPath := ""
if submission.Input != "" {
inputPath = filepath.Join(tempDir, "input.txt")
if err := os.WriteFile(inputPath, []byte(submission.Input), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write input file: %v", err)
return
}
}
// Run the code in a Docker container
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
// First compile the Java code
compileCmd := exec.CommandContext(ctx, "docker", "run", "--rm",
"-v", tempDir+":/code", // Mount code directory
"eclipse-temurin:11-jdk-alpine",
"javac", "/code/"+className+".java")
compileOutput, compileErr := compileCmd.CombinedOutput()
if compileErr != nil {
log.Printf("[JAVA-%s] Compilation failed: %v", submission.ID, compileErr)
submission.Status = "failed"
submission.Error = fmt.Sprintf("Compilation error: %s", compileOutput)
return
}
// Then run the compiled class
var runCmd *exec.Cmd
if inputPath != "" {
runCmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=400m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=50000", // 50% CPU
"-v", tempDir+":/code", // Mount code directory
"eclipse-temurin:11-jdk-alpine",
"sh", "-c", "cd /code && cat input.txt | java -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Xverify:none -Xms64m -Xmx256m "+className)
} else {
runCmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=400m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=50000", // 50% CPU
"-v", tempDir+":/code", // Mount code directory
"eclipse-temurin:11-jdk-alpine",
"java", "-XX:+TieredCompilation", "-XX:TieredStopAtLevel=1", "-Xverify:none", "-Xms64m", "-Xmx256m", "-cp", "/code", className)
}
output, err := runCmd.CombinedOutput()
elapsed := time.Since(startTime)
log.Printf("[JAVA-%s] Java execution completed in %v", submission.ID, elapsed)
s.updateSubmissionResult(submission, output, err, ctx.Err() != nil)
}
// executeC runs C code in a container
func (s *ExecutionService) executeC(submission *models.CodeSubmission) {
log.Printf("[C-%s] Preparing C execution environment", submission.ID)
startTime := time.Now()
// Create a temporary file for the code
tempDir, err := os.MkdirTemp("", "monaco-c-*")
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to create temp directory: %v", err)
return
}
defer os.RemoveAll(tempDir)
// Write the code to a file
codePath := filepath.Join(tempDir, "code.c")
if err := os.WriteFile(codePath, []byte(submission.Code), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write code file: %v", err)
return
}
// Create a file for input if provided
inputPath := ""
if submission.Input != "" {
inputPath = filepath.Join(tempDir, "input.txt")
if err := os.WriteFile(inputPath, []byte(submission.Input), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write input file: %v", err)
return
}
}
// Run the code in a Docker container
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// First compile the C code
compileCmd := exec.CommandContext(ctx, "docker", "run", "--rm",
"-v", tempDir+":/code", // Mount code directory
"gcc:latest",
"gcc", "-o", "/code/app", "/code/code.c")
compileOutput, compileErr := compileCmd.CombinedOutput()
if compileErr != nil {
log.Printf("[C-%s] Compilation failed: %v", submission.ID, compileErr)
submission.Status = "failed"
submission.Error = fmt.Sprintf("Compilation error: %s", compileOutput)
return
}
// Then run the compiled binary
var runCmd *exec.Cmd
if inputPath != "" {
runCmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"-v", tempDir+":/code", // Mount code directory
"gcc:latest",
"sh", "-c", "cat /code/input.txt | /code/app")
} else {
runCmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"-v", tempDir+":/code", // Mount code directory
"gcc:latest",
"/code/app")
}
output, err := runCmd.CombinedOutput()
elapsed := time.Since(startTime)
log.Printf("[C-%s] C execution completed in %v", submission.ID, elapsed)
s.updateSubmissionResult(submission, output, err, ctx.Err() != nil)
}
// executeCpp runs C++ code in a container
func (s *ExecutionService) executeCpp(submission *models.CodeSubmission) {
log.Printf("[CPP-%s] Preparing C++ execution environment", submission.ID)
startTime := time.Now()
// Create a temporary file for the code
tempDir, err := os.MkdirTemp("", "monaco-cpp-*")
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to create temp directory: %v", err)
return
}
defer os.RemoveAll(tempDir)
// Write the code to a file
codePath := filepath.Join(tempDir, "code.cpp")
if err := os.WriteFile(codePath, []byte(submission.Code), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write code file: %v", err)
return
}
// Create a file for input if provided
inputPath := ""
if submission.Input != "" {
inputPath = filepath.Join(tempDir, "input.txt")
if err := os.WriteFile(inputPath, []byte(submission.Input), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write input file: %v", err)
return
}
}
// Run the code in a Docker container
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// First compile the C++ code
compileCmd := exec.CommandContext(ctx, "docker", "run", "--rm",
"-v", tempDir+":/code", // Mount code directory
"gcc:latest",
"g++", "-o", "/code/app", "/code/code.cpp")
compileOutput, compileErr := compileCmd.CombinedOutput()
if compileErr != nil {
log.Printf("[CPP-%s] Compilation failed: %v", submission.ID, compileErr)
submission.Status = "failed"
submission.Error = fmt.Sprintf("Compilation error: %s", compileOutput)
return
}
// Then run the compiled binary
var runCmd *exec.Cmd
if inputPath != "" {
runCmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"-v", tempDir+":/code", // Mount code directory
"gcc:latest",
"sh", "-c", "cat /code/input.txt | /code/app")
} else {
runCmd = exec.CommandContext(ctx, "docker", "run", "--rm",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"-v", tempDir+":/code", // Mount code directory
"gcc:latest",
"/code/app")
}
output, err := runCmd.CombinedOutput()
elapsed := time.Since(startTime)
log.Printf("[CPP-%s] C++ execution completed in %v", submission.ID, elapsed)
s.updateSubmissionResult(submission, output, err, ctx.Err() != nil)
}
// updateSubmissionResult updates the submission with the execution result
func (s *ExecutionService) updateSubmissionResult(submission *models.CodeSubmission, output []byte, err error, timedOut bool) {
// Format the output to include the input if provided
formattedOutput := ""
if submission.Input != "" {
// Only add input lines that were actually used
inputLines := strings.Split(submission.Input, "\n")
for _, line := range inputLines {
if line != "" {
// Don't add the input marker for empty lines
formattedOutput += "[Input] " + line + "\n"
}
}
}
// Add the actual output
rawOutput := string(output)
if timedOut {
submission.Status = "failed"
submission.Error = "Execution timed out"
submission.Output = formattedOutput + rawOutput
return
}
if err != nil {
submission.Status = "failed"
submission.Error = err.Error()
submission.Output = formattedOutput + rawOutput
return
}
submission.Status = "completed"
submission.Output = formattedOutput + rawOutput
}
// SubmitInput submits input to a running interactive program
func (s *ExecutionService) SubmitInput(submission *models.CodeSubmission, input string) {
s.mu.Lock()
inputChan, exists := s.inputChannels[submission.ID]
s.mu.Unlock()
if !exists {
log.Printf("[ERROR] No input channel found for submission %s", submission.ID)
return
}
// Send the input to the channel
inputChan <- input
// Update the submission status
submission.Status = "running"
submission.Output += "[Input] " + input + "\n"
}
// GetQueueStats returns statistics about the job queue
func (s *ExecutionService) GetQueueStats() models.QueueStats {
return s.queue.GetStats()
}
// GenerateUUID generates a unique ID for submissions
func GenerateUUID() string {
b := make([]byte, 16)
_, err := rand.Read(b)
if err != nil {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
return hex.EncodeToString(b)
}
// extractJavaClassName extracts the class name from Java code
func extractJavaClassName(code string) string {
// Simple regex-like extraction
lines := strings.Split(code, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "public class ") {
parts := strings.Split(line, " ")
if len(parts) > 2 {
className := parts[2]
// Remove any { or implements/extends
className = strings.Split(className, "{")[0]
className = strings.Split(className, " ")[0]
return strings.TrimSpace(className)
}
}
}
return ""
}

View File

@@ -0,0 +1,366 @@
package executor
import (
"bufio"
"context"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/arnab-afk/monaco/internal/models"
)
// executePythonInteractive runs Python code in interactive mode
func (s *ExecutionService) executePythonInteractive(submission *models.CodeSubmission, tempDir string) {
log.Printf("[PYTHON-%s] Running Python in interactive mode", submission.ID)
// Create an input channel for this submission
inputChan := make(chan string)
s.mu.Lock()
s.inputChannels[submission.ID] = inputChan
s.mu.Unlock()
// Clean up when done
defer func() {
s.mu.Lock()
delete(s.inputChannels, submission.ID)
close(inputChan)
s.mu.Unlock()
}()
// Create a wrapper script that handles interactive input
wrapperPath := filepath.Join(tempDir, "wrapper.py")
wrapperCode := `
import sys
import os
import time
import traceback
# Load the user's code
with open('/code/code.py', 'r') as f:
user_code = f.read()
# Replace the built-in input function
original_input = input
def custom_input(prompt=''):
# Print the prompt without newline
sys.stdout.write(prompt)
sys.stdout.flush()
# Signal that we're waiting for input
sys.stdout.write('\n[WAITING_FOR_INPUT]\n')
sys.stdout.flush()
# Wait for input from the parent process
# Use a blocking read that won't raise EOFError
line = ''
while True:
try:
char = sys.stdin.read(1)
if char == '\n':
break
if char:
line += char
except:
# If any error occurs, wait a bit and try again
time.sleep(0.1)
continue
# Echo the input as if the user typed it
sys.stdout.write(line + '\n')
sys.stdout.flush()
return line
# Replace the built-in input function
input = custom_input
# Execute the user's code
try:
# Use globals and locals to ensure proper variable scope
exec(user_code, globals(), globals())
except Exception as e:
# Print detailed error information
sys.stdout.write(f'\nError: {str(e)}\n')
traceback.print_exc(file=sys.stdout)
sys.stdout.flush()
`
if err := os.WriteFile(wrapperPath, []byte(wrapperCode), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write wrapper file: %v", err)
return
}
// Run the code in a Docker container
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) // Longer timeout for interactive
defer cancel()
// Start the container
cmd := exec.CommandContext(ctx, "docker", "run", "--rm", "-i",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"--ulimit", "nofile=64:64", // File descriptor limits
"-v", tempDir+":/code", // Mount code directory
"python:3.9",
"python", "/code/wrapper.py")
// Get pipes for stdin and stdout
stdin, err := cmd.StdinPipe()
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to get stdin pipe: %v", err)
return
}
stdout, err := cmd.StdoutPipe()
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to get stdout pipe: %v", err)
return
}
// Start the command
if err := cmd.Start(); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to start command: %v", err)
return
}
// Set status to running
submission.Status = "running"
// Read output in a goroutine
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
line := scanner.Text()
// Check if the program is waiting for input
if line == "[WAITING_FOR_INPUT]" {
// Update status to waiting for input
submission.Status = "waiting_for_input"
continue
}
// Add the output to the submission
submission.Output += line + "\n"
}
}()
// Handle input in a goroutine
go func() {
for input := range inputChan {
// Write the input to stdin
_, err := stdin.Write([]byte(input + "\n"))
if err != nil {
log.Printf("[ERROR] Failed to write to stdin: %v", err)
break
}
}
}()
// Wait for the command to complete
err = cmd.Wait()
// Update the submission status
if err != nil {
if ctx.Err() != nil {
submission.Status = "failed"
submission.Error = "Execution timed out"
} else {
submission.Status = "failed"
submission.Error = err.Error()
}
} else {
submission.Status = "completed"
}
submission.CompletedAt = time.Now()
log.Printf("[PYTHON-%s] Interactive execution completed", submission.ID)
}
// executeJavaScriptInteractive runs JavaScript code in interactive mode
func (s *ExecutionService) executeJavaScriptInteractive(submission *models.CodeSubmission, tempDir string) {
log.Printf("[JS-%s] Running JavaScript in interactive mode", submission.ID)
// Create an input channel for this submission
inputChan := make(chan string)
s.mu.Lock()
s.inputChannels[submission.ID] = inputChan
s.mu.Unlock()
// Clean up when done
defer func() {
s.mu.Lock()
delete(s.inputChannels, submission.ID)
close(inputChan)
s.mu.Unlock()
}()
// Create a wrapper script that handles interactive input
wrapperPath := filepath.Join(tempDir, "wrapper.js")
wrapperCode := `
const fs = require('fs');
const readline = require('readline');
// Load the user's code
const userCode = fs.readFileSync('/code/code.js', 'utf8');
// Create a custom readline interface
const originalReadline = readline.createInterface;
readline.createInterface = function(options) {
// Create a custom interface that intercepts input
const rl = originalReadline({
input: process.stdin,
output: process.stdout,
terminal: false
});
// Override the question method
const originalQuestion = rl.question;
rl.question = function(query, callback) {
// Print the prompt
process.stdout.write(query);
// Signal that we're waiting for input
process.stdout.write('\n[WAITING_FOR_INPUT]\n');
process.stdout.flush();
// Set up a more robust input handler
const onLine = (answer) => {
// Echo the input as if the user typed it
process.stdout.write(answer + '\n');
process.stdout.flush();
callback(answer);
};
// Handle input with error recovery
rl.once('line', onLine);
// Add error handler
rl.once('error', (err) => {
console.error('Input error:', err.message);
// Provide a default answer in case of error
callback('');
});
};
return rl;
};
// Capture uncaught exceptions
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err.message);
console.error(err.stack);
});
// Execute the user's code
try {
eval(userCode);
} catch (e) {
console.error('Error:', e.message);
console.error(e.stack);
}
`
if err := os.WriteFile(wrapperPath, []byte(wrapperCode), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write wrapper file: %v", err)
return
}
// Run the code in a Docker container
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) // Longer timeout for interactive
defer cancel()
// Start the container
cmd := exec.CommandContext(ctx, "docker", "run", "--rm", "-i",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"-v", tempDir+":/code", // Mount code directory
"node:18-alpine",
"node", "/code/wrapper.js")
// Get pipes for stdin and stdout
stdin, err := cmd.StdinPipe()
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to get stdin pipe: %v", err)
return
}
stdout, err := cmd.StdoutPipe()
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to get stdout pipe: %v", err)
return
}
// Start the command
if err := cmd.Start(); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to start command: %v", err)
return
}
// Set status to running
submission.Status = "running"
// Read output in a goroutine
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
line := scanner.Text()
// Check if the program is waiting for input
if line == "[WAITING_FOR_INPUT]" {
// Update status to waiting for input
submission.Status = "waiting_for_input"
continue
}
// Add the output to the submission
submission.Output += line + "\n"
}
}()
// Handle input in a goroutine
go func() {
for input := range inputChan {
// Write the input to stdin
_, err := stdin.Write([]byte(input + "\n"))
if err != nil {
log.Printf("[ERROR] Failed to write to stdin: %v", err)
break
}
}
}()
// Wait for the command to complete
err = cmd.Wait()
// Update the submission status
if err != nil {
if ctx.Err() != nil {
submission.Status = "failed"
submission.Error = "Execution timed out"
} else {
submission.Status = "failed"
submission.Error = err.Error()
}
} else {
submission.Status = "completed"
}
submission.CompletedAt = time.Now()
log.Printf("[JS-%s] Interactive execution completed", submission.ID)
}

View File

@@ -0,0 +1,376 @@
package executor
import (
"bufio"
"context"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/arnab-afk/monaco/internal/models"
)
// WebSocketSession represents a WebSocket execution session
type WebSocketSession struct {
Submission *models.CodeSubmission
InputChan chan string
OutputChan chan string
Done chan struct{}
}
// SetupWebSocketChannels sets up the channels for WebSocket communication
func (s *ExecutionService) SetupWebSocketChannels(submission *models.CodeSubmission, inputChan chan string, outputChan chan string) {
s.mu.Lock()
defer s.mu.Unlock()
// Store the channels in the service
s.wsInputChannels[submission.ID] = inputChan
s.wsOutputChannels[submission.ID] = outputChan
}
// ExecuteCodeWebSocket executes code and streams the output over WebSocket
func (s *ExecutionService) ExecuteCodeWebSocket(submission *models.CodeSubmission) {
log.Printf("[WS-%s] Starting WebSocket execution for %s code", submission.ID, submission.Language)
// Update submission status
submission.Status = "running"
submission.StartedAt = time.Now()
// Execute the code based on the language
switch strings.ToLower(submission.Language) {
case "python":
s.executePythonWebSocket(submission)
case "javascript":
s.executeJavaScriptWebSocket(submission)
case "go":
s.executeGoWebSocket(submission)
case "java":
s.executeJavaWebSocket(submission)
case "c":
s.executeCWebSocket(submission)
case "cpp":
s.executeCppWebSocket(submission)
default:
submission.Status = "failed"
submission.Error = fmt.Sprintf("Unsupported language: %s", submission.Language)
submission.CompletedAt = time.Now()
}
log.Printf("[WS-%s] Execution completed with status: %s", submission.ID, submission.Status)
}
// executePythonWebSocket executes Python code with WebSocket communication
func (s *ExecutionService) executePythonWebSocket(submission *models.CodeSubmission) {
log.Printf("[WS-PYTHON-%s] Preparing Python WebSocket execution", submission.ID)
// Create a temporary directory for the code
tempDir, err := os.MkdirTemp("", "monaco-ws-python-*")
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to create temp directory: %v", err)
return
}
defer os.RemoveAll(tempDir)
// Write the code to a file
codePath := filepath.Join(tempDir, "code.py")
if err := os.WriteFile(codePath, []byte(submission.Code), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write code file: %v", err)
return
}
// Get the input and output channels
s.mu.Lock()
inputChan := s.wsInputChannels[submission.ID]
outputChan := s.wsOutputChannels[submission.ID]
s.mu.Unlock()
// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Run the code in a Docker container
cmd := exec.CommandContext(ctx, "docker", "run", "--rm", "-i",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"--ulimit", "nofile=64:64", // File descriptor limits
"-v", tempDir+":/code", // Mount code directory
"python:3.9",
"python", "/code/code.py")
// Get pipes for stdin and stdout
stdin, err := cmd.StdinPipe()
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to get stdin pipe: %v", err)
return
}
stdout, err := cmd.StdoutPipe()
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to get stdout pipe: %v", err)
return
}
stderr, err := cmd.StderrPipe()
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to get stderr pipe: %v", err)
return
}
// Start the command
if err := cmd.Start(); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to start command: %v", err)
return
}
// Create a done channel to signal when the command is complete
done := make(chan struct{})
// Read from stdout and send to the output channel
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
line := scanner.Text()
select {
case outputChan <- line + "\n":
// Output sent successfully
case <-done:
return
}
}
}()
// Read from stderr and send to the output channel
go func() {
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
line := scanner.Text()
select {
case outputChan <- "ERROR: " + line + "\n":
// Error sent successfully
case <-done:
return
}
}
}()
// Read from the input channel and write to stdin
go func() {
for {
select {
case input := <-inputChan:
// Write the input to stdin
_, err := io.WriteString(stdin, input+"\n")
if err != nil {
log.Printf("[WS-PYTHON-%s] Failed to write to stdin: %v", submission.ID, err)
return
}
case <-done:
return
}
}
}()
// Wait for the command to complete
err = cmd.Wait()
close(done)
// Update the submission status
if err != nil {
if ctx.Err() != nil {
submission.Status = "failed"
submission.Error = "Execution timed out"
} else {
submission.Status = "failed"
submission.Error = err.Error()
}
} else {
submission.Status = "completed"
}
submission.CompletedAt = time.Now()
log.Printf("[WS-PYTHON-%s] WebSocket execution completed", submission.ID)
}
// executeJavaScriptWebSocket executes JavaScript code with WebSocket communication
func (s *ExecutionService) executeJavaScriptWebSocket(submission *models.CodeSubmission) {
log.Printf("[WS-JS-%s] Preparing JavaScript WebSocket execution", submission.ID)
// Create a temporary directory for the code
tempDir, err := os.MkdirTemp("", "monaco-ws-js-*")
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to create temp directory: %v", err)
return
}
defer os.RemoveAll(tempDir)
// Write the code to a file
codePath := filepath.Join(tempDir, "code.js")
if err := os.WriteFile(codePath, []byte(submission.Code), 0644); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to write code file: %v", err)
return
}
// Get the input and output channels
s.mu.Lock()
inputChan := s.wsInputChannels[submission.ID]
outputChan := s.wsOutputChannels[submission.ID]
s.mu.Unlock()
// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Run the code in a Docker container
cmd := exec.CommandContext(ctx, "docker", "run", "--rm", "-i",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"-v", tempDir+":/code", // Mount code directory
"node:18-alpine",
"node", "/code/code.js")
// Get pipes for stdin and stdout
stdin, err := cmd.StdinPipe()
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to get stdin pipe: %v", err)
return
}
stdout, err := cmd.StdoutPipe()
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to get stdout pipe: %v", err)
return
}
stderr, err := cmd.StderrPipe()
if err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to get stderr pipe: %v", err)
return
}
// Start the command
if err := cmd.Start(); err != nil {
submission.Status = "failed"
submission.Error = fmt.Sprintf("Failed to start command: %v", err)
return
}
// Create a done channel to signal when the command is complete
done := make(chan struct{})
// Read from stdout and send to the output channel
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
line := scanner.Text()
select {
case outputChan <- line + "\n":
// Output sent successfully
case <-done:
return
}
}
}()
// Read from stderr and send to the output channel
go func() {
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
line := scanner.Text()
select {
case outputChan <- "ERROR: " + line + "\n":
// Error sent successfully
case <-done:
return
}
}
}()
// Read from the input channel and write to stdin
go func() {
for {
select {
case input := <-inputChan:
// Write the input to stdin
_, err := io.WriteString(stdin, input+"\n")
if err != nil {
log.Printf("[WS-JS-%s] Failed to write to stdin: %v", submission.ID, err)
return
}
case <-done:
return
}
}
}()
// Wait for the command to complete
err = cmd.Wait()
close(done)
// Update the submission status
if err != nil {
if ctx.Err() != nil {
submission.Status = "failed"
submission.Error = "Execution timed out"
} else {
submission.Status = "failed"
submission.Error = err.Error()
}
} else {
submission.Status = "completed"
}
submission.CompletedAt = time.Now()
log.Printf("[WS-JS-%s] WebSocket execution completed", submission.ID)
}
// executeGoWebSocket executes Go code with WebSocket communication
func (s *ExecutionService) executeGoWebSocket(submission *models.CodeSubmission) {
// Implementation similar to executePythonWebSocket but for Go
// For brevity, this is left as a placeholder
submission.Status = "failed"
submission.Error = "WebSocket execution for Go not implemented yet"
}
// executeJavaWebSocket executes Java code with WebSocket communication
func (s *ExecutionService) executeJavaWebSocket(submission *models.CodeSubmission) {
// Implementation similar to executePythonWebSocket but for Java
// For brevity, this is left as a placeholder
submission.Status = "failed"
submission.Error = "WebSocket execution for Java not implemented yet"
}
// executeCWebSocket executes C code with WebSocket communication
func (s *ExecutionService) executeCWebSocket(submission *models.CodeSubmission) {
// Implementation similar to executePythonWebSocket but for C
// For brevity, this is left as a placeholder
submission.Status = "failed"
submission.Error = "WebSocket execution for C not implemented yet"
}
// executeCppWebSocket executes C++ code with WebSocket communication
func (s *ExecutionService) executeCppWebSocket(submission *models.CodeSubmission) {
// Implementation similar to executePythonWebSocket but for C++
// For brevity, this is left as a placeholder
submission.Status = "failed"
submission.Error = "WebSocket execution for C++ not implemented yet"
}

View File

@@ -0,0 +1,36 @@
package models
import "time"
// CodeSubmission represents a code submission for execution
type CodeSubmission struct {
ID string `json:"id"`
Code string `json:"code"`
Language string `json:"language"`
Input string `json:"input"`
Status string `json:"status"` // "pending", "queued", "running", "waiting_for_input", "completed", "failed"
QueuedAt time.Time `json:"queuedAt,omitempty"`
StartedAt time.Time `json:"startedAt,omitempty"`
CompletedAt time.Time `json:"completedAt,omitempty"`
Output string `json:"output,omitempty"`
Error string `json:"error,omitempty"`
IsInteractive bool `json:"isInteractive,omitempty"` // Whether the program requires interactive input
CurrentPrompt string `json:"currentPrompt,omitempty"` // Current input prompt if waiting for input
}
// ExecutionResult represents the result of code execution
type ExecutionResult struct {
Output string `json:"output"`
Error string `json:"error"`
ExitCode int `json:"exitCode"`
ExecutionMS int64 `json:"executionMs"`
}
// QueueStats represents statistics about the job queue
type QueueStats struct {
QueueLength int `json:"queueLength"`
RunningJobs int `json:"runningJobs"`
CompletedJobs int `json:"completedJobs"`
FailedJobs int `json:"failedJobs"`
TotalProcessed int `json:"totalProcessed"`
}

View File

@@ -0,0 +1,112 @@
package queue
import (
"log"
"sync"
"time"
"github.com/arnab-afk/monaco/internal/models"
)
// Job represents a job to be executed
type Job interface {
Execute()
}
// JobQueue manages the execution of jobs
type JobQueue struct {
queue chan Job
wg sync.WaitGroup
mu sync.Mutex
runningJobs int
completedJobs int
failedJobs int
totalProcessed int
workerCount int
}
// NewJobQueue creates a new job queue with the specified number of workers
func NewJobQueue(workerCount int) *JobQueue {
q := &JobQueue{
queue: make(chan Job, 100), // Buffer size of 100 jobs
workerCount: workerCount,
}
// Start workers
for i := 0; i < workerCount; i++ {
q.wg.Add(1)
go q.worker(i)
}
return q
}
// worker processes jobs from the queue
func (q *JobQueue) worker(id int) {
defer q.wg.Done()
log.Printf("[WORKER-%d] Started", id)
for job := range q.queue {
// Update stats
q.mu.Lock()
q.runningJobs++
q.mu.Unlock()
// Execute the job
startTime := time.Now()
log.Printf("[WORKER-%d] Processing job", id)
// Execute the job and handle panics
func() {
defer func() {
if r := recover(); r != nil {
log.Printf("[WORKER-%d] Panic in job execution: %v", id, r)
q.mu.Lock()
q.failedJobs++
q.runningJobs--
q.totalProcessed++
q.mu.Unlock()
}
}()
job.Execute()
}()
// Update stats if no panic occurred
q.mu.Lock()
q.completedJobs++
q.runningJobs--
q.totalProcessed++
q.mu.Unlock()
log.Printf("[WORKER-%d] Job completed in %v", id, time.Since(startTime))
}
log.Printf("[WORKER-%d] Stopped", id)
}
// AddJob adds a job to the queue
func (q *JobQueue) AddJob(job Job) {
q.queue <- job
}
// GetStats returns statistics about the job queue
func (q *JobQueue) GetStats() models.QueueStats {
q.mu.Lock()
defer q.mu.Unlock()
return models.QueueStats{
QueueLength: len(q.queue),
RunningJobs: q.runningJobs,
CompletedJobs: q.completedJobs,
FailedJobs: q.failedJobs,
TotalProcessed: q.totalProcessed,
}
}
// Shutdown stops the job queue
func (q *JobQueue) Shutdown() {
close(q.queue)
q.wg.Wait()
}

View File

@@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/arnab-afk/monaco/handler" "github.com/arnab-afk/monaco/handler"
"github.com/gorilla/websocket"
) )
func main() { func main() {
@@ -47,44 +46,12 @@ func main() {
} }
} }
// Configure WebSocket upgrader // Register handlers with logging and CORS middleware
upgrader := websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// Allow connections from any origin
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// WebSocket handler for terminal connection
http.HandleFunc("/ws/terminal", func(w http.ResponseWriter, r *http.Request) {
// Get execution ID from query parameters
executionID := r.URL.Query().Get("id")
if executionID == "" {
log.Println("[WS] Missing execution ID")
http.Error(w, "Missing execution ID", http.StatusBadRequest)
return
}
// Upgrade HTTP connection to WebSocket
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("[WS] Failed to upgrade connection: %v", err)
return
}
log.Printf("[WS] Terminal connection established for execution ID: %s", executionID)
// Connect this WebSocket to the execution service for real-time streaming
h.ConnectTerminal(conn, executionID)
})
// Register REST API handlers with logging and CORS middleware
http.HandleFunc("/submit", corsMiddleware(loggingMiddleware(h.SubmitHandler))) http.HandleFunc("/submit", corsMiddleware(loggingMiddleware(h.SubmitHandler)))
http.HandleFunc("/status", corsMiddleware(loggingMiddleware(h.StatusHandler))) http.HandleFunc("/status", corsMiddleware(loggingMiddleware(h.StatusHandler)))
http.HandleFunc("/result", corsMiddleware(loggingMiddleware(h.ResultHandler))) http.HandleFunc("/result", corsMiddleware(loggingMiddleware(h.ResultHandler)))
http.HandleFunc("/queue-stats", corsMiddleware(loggingMiddleware(h.QueueStatsHandler))) http.HandleFunc("/queue-stats", corsMiddleware(loggingMiddleware(h.QueueStatsHandler)))
http.HandleFunc("/ws", corsMiddleware(h.WebSocketHandler)) // WebSocket doesn't need logging middleware
port := ":8080" port := ":8080"
log.Printf("Server started at %s", port) log.Printf("Server started at %s", port)

View File

@@ -1,7 +1,7 @@
package service package service
import ( import (
"bytes" "bufio"
"context" "context"
"fmt" "fmt"
"io" "io"
@@ -10,6 +10,7 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings"
"sync" "sync"
"time" "time"
@@ -20,91 +21,19 @@ import (
// ExecutionService handles code execution for multiple languages // ExecutionService handles code execution for multiple languages
type ExecutionService struct { type ExecutionService struct {
mu sync.Mutex mu sync.Mutex
queue *queue.JobQueue queue *queue.JobQueue
terminalConnections map[string][]*websocket.Conn // Map of executionID to WebSocket connections wsConnections map[string]*websocket.Conn // Map of submission ID to WebSocket connection
execInputChannels map[string]chan string // Map of executionID to input channels wsInputChannels map[string]chan string // Map of submission ID to input channel
} }
// NewExecutionService creates a new execution service // NewExecutionService creates a new execution service
func NewExecutionService() *ExecutionService { func NewExecutionService() *ExecutionService {
log.Println("Initializing execution service with 3 concurrent workers") log.Println("Initializing execution service with 3 concurrent workers")
return &ExecutionService{ return &ExecutionService{
queue: queue.NewJobQueue(3), // 3 concurrent executions max queue: queue.NewJobQueue(3), // 3 concurrent executions max
terminalConnections: make(map[string][]*websocket.Conn), wsConnections: make(map[string]*websocket.Conn),
execInputChannels: make(map[string]chan string), wsInputChannels: make(map[string]chan string),
}
}
// RegisterTerminalConnection registers a WebSocket connection for an execution
func (s *ExecutionService) RegisterTerminalConnection(executionID string, conn *websocket.Conn) {
s.mu.Lock()
defer s.mu.Unlock()
if _, exists := s.terminalConnections[executionID]; !exists {
s.terminalConnections[executionID] = make([]*websocket.Conn, 0)
}
s.terminalConnections[executionID] = append(s.terminalConnections[executionID], conn)
log.Printf("[WS-%s] Terminal connection registered, total connections: %d",
executionID, len(s.terminalConnections[executionID]))
}
// UnregisterTerminalConnection removes a WebSocket connection
func (s *ExecutionService) UnregisterTerminalConnection(executionID string, conn *websocket.Conn) {
s.mu.Lock()
defer s.mu.Unlock()
connections, exists := s.terminalConnections[executionID]
if !exists {
return
}
// Remove the specific connection
for i, c := range connections {
if c == conn {
s.terminalConnections[executionID] = append(connections[:i], connections[i+1:]...)
break
}
}
// If no more connections, clean up
if len(s.terminalConnections[executionID]) == 0 {
delete(s.terminalConnections, executionID)
}
log.Printf("[WS-%s] Terminal connection unregistered", executionID)
}
// SendOutputToTerminals sends output to all connected terminals for an execution
func (s *ExecutionService) SendOutputToTerminals(executionID string, output string) {
s.mu.Lock()
connections := s.terminalConnections[executionID]
s.mu.Unlock()
for _, conn := range connections {
if err := conn.WriteMessage(websocket.TextMessage, []byte(output)); err != nil {
log.Printf("[WS-%s] Error sending to terminal: %v", executionID, err)
// Unregister this connection on error
s.UnregisterTerminalConnection(executionID, conn)
}
}
}
// SendInput sends user input to a running process
func (s *ExecutionService) SendInput(executionID string, input string) {
s.mu.Lock()
inputChan, exists := s.execInputChannels[executionID]
s.mu.Unlock()
if exists {
select {
case inputChan <- input:
log.Printf("[WS-%s] Sent input to execution: %s", executionID, input)
default:
log.Printf("[WS-%s] Execution not ready for input", executionID)
}
} else {
log.Printf("[WS-%s] No input channel for execution", executionID)
} }
} }
@@ -188,123 +117,48 @@ func (s *ExecutionService) executeLanguageSpecific(submission *model.CodeSubmiss
func (s *ExecutionService) executeWithInput(cmd *exec.Cmd, input string, timeout time.Duration, submissionID string) ([]byte, error) { func (s *ExecutionService) executeWithInput(cmd *exec.Cmd, input string, timeout time.Duration, submissionID string) ([]byte, error) {
log.Printf("[TIMEOUT-%s] Setting execution timeout: %v", submissionID, timeout) log.Printf("[TIMEOUT-%s] Setting execution timeout: %v", submissionID, timeout)
// Create pipes for stdin, stdout, and stderr // Set up input pipe if input is provided
stdin, stdinErr := cmd.StdinPipe()
if stdinErr != nil {
return nil, fmt.Errorf("failed to create stdin pipe: %v", stdinErr)
}
stdout, stdoutErr := cmd.StdoutPipe()
if stdoutErr != nil {
return nil, fmt.Errorf("failed to create stdout pipe: %v", stdoutErr)
}
stderr, stderrErr := cmd.StderrPipe()
if stderrErr != nil {
return nil, fmt.Errorf("failed to create stderr pipe: %v", stderrErr)
}
// Create an input channel and register it
inputChan := make(chan string, 10)
s.mu.Lock()
s.execInputChannels[submissionID] = inputChan
s.mu.Unlock()
// Clean up the input channel when done
defer func() {
s.mu.Lock()
delete(s.execInputChannels, submissionID)
s.mu.Unlock()
close(inputChan)
}()
// Start the command
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start process: %v", err)
}
// Create a buffer to collect all output
var outputBuffer bytes.Buffer
// Handle stdout in a goroutine
go func() {
buffer := make([]byte, 1024)
for {
n, err := stdout.Read(buffer)
if n > 0 {
data := buffer[:n]
outputBuffer.Write(data)
// Send real-time output to connected terminals
s.SendOutputToTerminals(submissionID, string(data))
}
if err != nil {
break
}
}
}()
// Handle stderr in a goroutine
go func() {
buffer := make([]byte, 1024)
for {
n, err := stderr.Read(buffer)
if n > 0 {
data := buffer[:n]
outputBuffer.Write(data)
// Send real-time output to connected terminals
s.SendOutputToTerminals(submissionID, string(data))
}
if err != nil {
break
}
}
}()
// Write initial input if provided
if input != "" { if input != "" {
io.WriteString(stdin, input+"\n") stdin, err := cmd.StdinPipe()
if err != nil {
log.Printf("[ERROR-%s] Failed to create stdin pipe: %v", submissionID, err)
return nil, err
}
// Write input in a goroutine to avoid blocking
go func() {
defer stdin.Close()
io.WriteString(stdin, input)
}()
log.Printf("[INPUT-%s] Providing input to process", submissionID)
} }
// Process is in a separate context, but it needs to be killed if timeout occurs done := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background()) var output []byte
defer cancel() var err error
// Handle additional input from WebSocket in a goroutine
go func() { go func() {
for { log.Printf("[EXEC-%s] Starting command execution: %v", submissionID, cmd.Args)
select { output, err = cmd.CombinedOutput()
case additionalInput, ok := <-inputChan: close(done)
if !ok {
return
}
log.Printf("[INPUT-%s] Received input from WebSocket: %s", submissionID, additionalInput)
io.WriteString(stdin, additionalInput+"\n")
case <-ctx.Done():
return
}
}
}() }()
// Wait for the command to complete with timeout
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
// Wait for completion or timeout
select { select {
case <-time.After(timeout): case <-time.After(timeout):
cancel() // Stop the input handler
log.Printf("[TIMEOUT-%s] Execution timed out after %v seconds", submissionID, timeout.Seconds()) log.Printf("[TIMEOUT-%s] Execution timed out after %v seconds", submissionID, timeout.Seconds())
if err := cmd.Process.Kill(); err != nil { if err := cmd.Process.Kill(); err != nil {
log.Printf("[TIMEOUT-%s] Failed to kill process: %v", submissionID, err) log.Printf("[TIMEOUT-%s] Failed to kill process: %v", submissionID, err)
return nil, fmt.Errorf("timeout reached but failed to kill process: %v", err)
} }
s.SendOutputToTerminals(submissionID, fmt.Sprintf("\n[System] Process killed after timeout of %v seconds", timeout.Seconds())) return nil, fmt.Errorf("execution timed out after %v seconds", timeout.Seconds())
return outputBuffer.Bytes(), fmt.Errorf("execution timed out after %v seconds", timeout.Seconds()) case <-done:
case err := <-done: if err != nil {
cancel() // Stop the input handler log.Printf("[EXEC-%s] Command execution failed: %v", submissionID, err)
s.SendOutputToTerminals(submissionID, "\n[System] Process completed") } else {
return outputBuffer.Bytes(), err log.Printf("[EXEC-%s] Command execution completed successfully", submissionID)
}
return output, err
} }
} }
@@ -322,9 +176,15 @@ func (s *ExecutionService) executePython(submission *model.CodeSubmission) {
"python:3.9", "python", "-c", submission.Code) "python:3.9", "python", "-c", submission.Code)
log.Printf("[PYTHON-%s] Executing Python code with timeout: 10s", submission.ID) log.Printf("[PYTHON-%s] Executing Python code with timeout: 10s", submission.ID)
var output []byte
var err error
// Use the enhanced executeWithInput method for all executions if submission.Input != "" {
output, err := s.executeWithInput(cmd, submission.Input, 100*time.Second, submission.ID) cmd.Stdin = strings.NewReader(submission.Input)
output, err = cmd.CombinedOutput()
} else {
output, err = s.executeWithTimeout(cmd, 10*time.Second, submission.ID)
}
elapsed := time.Since(startTime) elapsed := time.Since(startTime)
log.Printf("[PYTHON-%s] Python execution completed in %v", submission.ID, elapsed) log.Printf("[PYTHON-%s] Python execution completed in %v", submission.ID, elapsed)
@@ -402,7 +262,7 @@ func (s *ExecutionService) executeJava(submission *model.CodeSubmission) {
log.Printf("[JAVA-%s] Compilation successful", submission.ID) log.Printf("[JAVA-%s] Compilation successful", submission.ID)
// Now run the compiled class with the enhanced executeWithInput method // Now run the compiled class
runCmd := exec.Command("docker", "run", "--rm", "-i", runCmd := exec.Command("docker", "run", "--rm", "-i",
"--network=none", // No network access "--network=none", // No network access
"--memory=400m", // Memory limit "--memory=400m", // Memory limit
@@ -414,8 +274,17 @@ func (s *ExecutionService) executeJava(submission *model.CodeSubmission) {
"-Xverify:none", "-Xms64m", "-Xmx256m", "-Xverify:none", "-Xms64m", "-Xmx256m",
"-cp", "/code", className) "-cp", "/code", className)
log.Printf("[JAVA-%s] Executing Java code", submission.ID) // Add input if provided
output, err := s.executeWithInput(runCmd, submission.Input, 15*time.Second, submission.ID) var output []byte
if submission.Input != "" {
log.Printf("[JAVA-%s] Executing Java code with input", submission.ID)
runCmd.Stdin = strings.NewReader(submission.Input)
output, err = runCmd.CombinedOutput()
} else {
log.Printf("[JAVA-%s] Executing Java code without input", submission.ID)
output, err = s.executeWithTimeout(runCmd, 15*time.Second, submission.ID)
}
elapsed := time.Since(startTime) elapsed := time.Since(startTime)
log.Printf("[JAVA-%s] Java execution completed in %v", submission.ID, elapsed) log.Printf("[JAVA-%s] Java execution completed in %v", submission.ID, elapsed)
@@ -465,7 +334,7 @@ func (s *ExecutionService) executeC(submission *model.CodeSubmission) {
log.Printf("[C-%s] Compilation successful", submission.ID) log.Printf("[C-%s] Compilation successful", submission.ID)
// Run C executable using executeWithInput to support WebSockets // Run C executable
runCmd := exec.Command("docker", "run", "--rm", "-i", runCmd := exec.Command("docker", "run", "--rm", "-i",
"--network=none", // No network access "--network=none", // No network access
"--memory=100m", // Memory limit "--memory=100m", // Memory limit
@@ -474,8 +343,17 @@ func (s *ExecutionService) executeC(submission *model.CodeSubmission) {
"-v", tempDir+":/code", // Mount code directory "-v", tempDir+":/code", // Mount code directory
"gcc:latest", "/code/solution") "gcc:latest", "/code/solution")
log.Printf("[C-%s] Executing C code", submission.ID) // Add input if provided
output, err := s.executeWithInput(runCmd, submission.Input, 30*time.Second, submission.ID) var output []byte
// Don't redeclare err here - use the existing variable
if submission.Input != "" {
log.Printf("[C-%s] Executing C code with input", submission.ID)
runCmd.Stdin = strings.NewReader(submission.Input)
output, err = runCmd.CombinedOutput() // Use the existing err variable
} else {
log.Printf("[C-%s] Executing C code without input", submission.ID)
output, err = s.executeWithTimeout(runCmd, 10*time.Second, submission.ID) // Use the existing err variable
}
elapsed := time.Since(startTime) elapsed := time.Since(startTime)
log.Printf("[C-%s] C execution completed in %v", submission.ID, elapsed) log.Printf("[C-%s] C execution completed in %v", submission.ID, elapsed)
@@ -525,7 +403,7 @@ func (s *ExecutionService) executeCpp(submission *model.CodeSubmission) {
log.Printf("[CPP-%s] Compilation successful", submission.ID) log.Printf("[CPP-%s] Compilation successful", submission.ID)
// Run C++ executable using executeWithInput to support WebSockets // Run C++ executable
runCmd := exec.Command("docker", "run", "--rm", "-i", runCmd := exec.Command("docker", "run", "--rm", "-i",
"--network=none", // No network access "--network=none", // No network access
"--memory=100m", // Memory limit "--memory=100m", // Memory limit
@@ -534,8 +412,16 @@ func (s *ExecutionService) executeCpp(submission *model.CodeSubmission) {
"-v", tempDir+":/code", // Mount code directory "-v", tempDir+":/code", // Mount code directory
"gcc:latest", "/code/solution") "gcc:latest", "/code/solution")
log.Printf("[CPP-%s] Executing C++ code", submission.ID) // Add input if provided
output, err := s.executeWithInput(runCmd, submission.Input, 100*time.Second, submission.ID) var output []byte
if submission.Input != "" {
log.Printf("[CPP-%s] Executing C++ code with input", submission.ID)
runCmd.Stdin = strings.NewReader(submission.Input)
output, err = runCmd.CombinedOutput()
} else {
log.Printf("[CPP-%s] Executing C++ code without input", submission.ID)
output, err = s.executeWithTimeout(runCmd, 10*time.Second, submission.ID)
}
elapsed := time.Since(startTime) elapsed := time.Since(startTime)
log.Printf("[CPP-%s] C++ execution completed in %v", submission.ID, elapsed) log.Printf("[CPP-%s] C++ execution completed in %v", submission.ID, elapsed)
@@ -604,3 +490,223 @@ func (s *ExecutionService) GetQueueStats() map[string]int {
stats["queue_length"], stats["running_jobs"], stats["max_workers"]) stats["queue_length"], stats["running_jobs"], stats["max_workers"])
return stats return stats
} }
// HandleWebSocket handles a WebSocket connection for a code submission
func (s *ExecutionService) HandleWebSocket(conn *websocket.Conn, submission *model.CodeSubmission) {
// Store the WebSocket connection
s.mu.Lock()
s.wsConnections[submission.ID] = conn
// Create an input channel for this submission
inputChan := make(chan string, 10) // Buffer size of 10
s.wsInputChannels[submission.ID] = inputChan
s.mu.Unlock()
// Clean up when done
defer func() {
s.mu.Lock()
delete(s.wsConnections, submission.ID)
delete(s.wsInputChannels, submission.ID)
s.mu.Unlock()
conn.Close()
}()
// Start a goroutine to read input from the WebSocket
go func() {
for {
// Read message from WebSocket
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Printf("[WS-%s] Error reading message: %v", submission.ID, err)
break
}
// Only process text messages
if messageType == websocket.TextMessage {
// Send the input to the input channel
inputChan <- string(message)
}
}
}()
// Execute the code
submission.Status = "running"
submission.StartedAt = time.Now()
log.Printf("[WS-JOB-%s] Starting WebSocket execution for language: %s",
submission.ID, submission.Language)
// Execute the code based on the language
s.executeLanguageSpecificWithWebSocket(submission, inputChan, conn)
}
// executeLanguageSpecificWithWebSocket runs code in the appropriate language with WebSocket I/O
func (s *ExecutionService) executeLanguageSpecificWithWebSocket(submission *model.CodeSubmission, inputChan chan string, conn *websocket.Conn) {
log.Printf("[WS-EXEC-%s] Selecting execution environment for language: %s",
submission.ID, submission.Language)
switch submission.Language {
case "python":
log.Printf("[WS-EXEC-%s] Executing Python code", submission.ID)
s.executePythonWithWebSocket(submission, inputChan, conn)
case "java":
log.Printf("[WS-EXEC-%s] Executing Java code", submission.ID)
s.executeJavaWithWebSocket(submission, inputChan, conn)
case "c":
log.Printf("[WS-EXEC-%s] Executing C code", submission.ID)
s.executeCWithWebSocket(submission, inputChan, conn)
case "cpp":
log.Printf("[WS-EXEC-%s] Executing C++ code", submission.ID)
s.executeCppWithWebSocket(submission, inputChan, conn)
default:
log.Printf("[WS-EXEC-%s] ERROR: Unsupported language: %s", submission.ID, submission.Language)
submission.Status = "failed"
output := "Unsupported language: " + submission.Language
submission.Output = output
// Send error message to WebSocket
conn.WriteMessage(websocket.TextMessage, []byte(output))
}
// Update submission status
submission.CompletedAt = time.Now()
submission.Status = "completed"
}
// executePythonWithWebSocket runs Python code with WebSocket for I/O
func (s *ExecutionService) executePythonWithWebSocket(submission *model.CodeSubmission, inputChan chan string, conn *websocket.Conn) {
log.Printf("[WS-PYTHON-%s] Preparing Python WebSocket execution", submission.ID)
startTime := time.Now()
// Send initial message to client
conn.WriteMessage(websocket.TextMessage, []byte("Starting Python execution...\n"))
// Create a command to run Python in a Docker container
cmd := exec.Command("docker", "run", "--rm", "-i",
"--network=none", // No network access
"--memory=100m", // Memory limit
"--cpu-period=100000", // CPU quota period
"--cpu-quota=10000", // 10% CPU
"--ulimit", "nofile=64:64", // File descriptor limits
"python:3.9", "python", "-c", submission.Code)
// Get stdin pipe
stdin, err := cmd.StdinPipe()
if err != nil {
log.Printf("[WS-PYTHON-%s] Failed to create stdin pipe: %v", submission.ID, err)
conn.WriteMessage(websocket.TextMessage, []byte("Error: Failed to create stdin pipe\n"))
return
}
// Get stdout and stderr pipes
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Printf("[WS-PYTHON-%s] Failed to create stdout pipe: %v", submission.ID, err)
conn.WriteMessage(websocket.TextMessage, []byte("Error: Failed to create stdout pipe\n"))
return
}
stderr, err := cmd.StderrPipe()
if err != nil {
log.Printf("[WS-PYTHON-%s] Failed to create stderr pipe: %v", submission.ID, err)
conn.WriteMessage(websocket.TextMessage, []byte("Error: Failed to create stderr pipe\n"))
return
}
// Start the command
if err := cmd.Start(); err != nil {
log.Printf("[WS-PYTHON-%s] Failed to start command: %v", submission.ID, err)
conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("Error: Failed to start command: %v\n", err)))
return
}
// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Create a channel to signal when the command is done
done := make(chan struct{})
// Start a goroutine to handle command completion
go func() {
err := cmd.Wait()
if err != nil {
log.Printf("[WS-PYTHON-%s] Command failed: %v", submission.ID, err)
conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("\nExecution failed: %v\n", err)))
} else {
log.Printf("[WS-PYTHON-%s] Command completed successfully", submission.ID)
conn.WriteMessage(websocket.TextMessage, []byte("\nExecution completed successfully\n"))
}
close(done)
}()
// Start a goroutine to read from stdout and stderr
go func() {
scanner := bufio.NewScanner(io.MultiReader(stdout, stderr))
for scanner.Scan() {
line := scanner.Text()
log.Printf("[WS-PYTHON-%s] Output: %s", submission.ID, line)
conn.WriteMessage(websocket.TextMessage, []byte(line+"\n"))
}
}()
// Handle input from the WebSocket
go func() {
for {
select {
case input := <-inputChan:
log.Printf("[WS-PYTHON-%s] Received input: %s", submission.ID, input)
// Write the input to stdin
_, err := io.WriteString(stdin, input+"\n")
if err != nil {
log.Printf("[WS-PYTHON-%s] Failed to write to stdin: %v", submission.ID, err)
}
case <-ctx.Done():
return
case <-done:
return
}
}
}()
// Wait for the command to complete or timeout
select {
case <-ctx.Done():
log.Printf("[WS-PYTHON-%s] Execution timed out after 30 seconds", submission.ID)
conn.WriteMessage(websocket.TextMessage, []byte("\nExecution timed out after 30 seconds\n"))
cmd.Process.Kill()
case <-done:
// Command completed
}
elapsed := time.Since(startTime)
log.Printf("[WS-PYTHON-%s] Python execution completed in %v", submission.ID, elapsed)
// Update submission result
submission.CompletedAt = time.Now()
submission.Status = "completed"
}
// executeJavaWithWebSocket runs Java code with WebSocket for I/O
func (s *ExecutionService) executeJavaWithWebSocket(submission *model.CodeSubmission, inputChan chan string, conn *websocket.Conn) {
// For now, just send a message that this is not implemented
conn.WriteMessage(websocket.TextMessage, []byte("Java WebSocket execution not yet implemented\n"))
submission.Status = "failed"
submission.Output = "Java WebSocket execution not yet implemented"
}
// executeCWithWebSocket runs C code with WebSocket for I/O
func (s *ExecutionService) executeCWithWebSocket(submission *model.CodeSubmission, inputChan chan string, conn *websocket.Conn) {
// For now, just send a message that this is not implemented
conn.WriteMessage(websocket.TextMessage, []byte("C WebSocket execution not yet implemented\n"))
submission.Status = "failed"
submission.Output = "C WebSocket execution not yet implemented"
}
// executeCppWithWebSocket runs C++ code with WebSocket for I/O
func (s *ExecutionService) executeCppWithWebSocket(submission *model.CodeSubmission, inputChan chan string, conn *websocket.Conn) {
// For now, just send a message that this is not implemented
conn.WriteMessage(websocket.TextMessage, []byte("C++ WebSocket execution not yet implemented\n"))
submission.Status = "failed"
submission.Output = "C++ WebSocket execution not yet implemented"
}

View File

@@ -1 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1

Binary file not shown.

3
examples/basic_input.py Normal file
View File

@@ -0,0 +1,3 @@
# Very basic input example
name = input("What is your name? ")
print(f"Hello, {name}!")

View File

@@ -0,0 +1,41 @@
// Interactive Calculator Example
// This demonstrates how the interactive input/output works
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function calculator() {
console.log("Welcome to the Interactive Calculator!");
console.log("Enter 'q' to quit at any time.");
function promptUser() {
rl.question("Enter an expression (e.g., 2 + 3): ", (expression) => {
if (expression.toLowerCase() === 'q') {
console.log("Thank you for using the Interactive Calculator!");
rl.close();
return;
}
try {
// Safely evaluate the expression
const result = eval(expression);
console.log(`Result: ${result}`);
} catch (e) {
console.log(`Error: ${e.message}`);
console.log("Please try again with a valid expression.");
}
// Continue prompting
promptUser();
});
}
// Start the prompt loop
promptUser();
}
// Run the calculator
calculator();

View File

@@ -0,0 +1,24 @@
# Interactive Calculator Example
# This demonstrates how the interactive input/output works
def calculator():
print("Welcome to the Interactive Calculator!")
print("Enter 'q' to quit at any time.")
while True:
expression = input("Enter an expression (e.g., 2 + 3): ")
if expression.lower() == 'q':
print("Thank you for using the Interactive Calculator!")
break
try:
# Safely evaluate the expression
result = eval(expression)
print(f"Result: {result}")
except Exception as e:
print(f"Error: {str(e)}")
print("Please try again with a valid expression.")
# Run the calculator
calculator()

View File

@@ -0,0 +1,22 @@
// Interactive JavaScript Example
// This example demonstrates interactive input/output
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter your name: ', (name) => {
console.log(`Hello, ${name}!`);
rl.question('Enter your age: ', (age) => {
console.log(`You are ${age} years old.`);
rl.question('What is your favorite color? ', (color) => {
console.log(`Your favorite color is ${color}.`);
console.log('Thank you for using the interactive example!');
rl.close();
});
});
});

View File

@@ -0,0 +1,13 @@
# Interactive Python Example
# This example demonstrates interactive input/output
name = input("Enter your name: ")
print(f"Hello, {name}!")
age = input("Enter your age: ")
print(f"You are {age} years old.")
favorite_color = input("What is your favorite color? ")
print(f"Your favorite color is {favorite_color}.")
print("Thank you for using the interactive example!")

5
examples/simple_input.py Normal file
View File

@@ -0,0 +1,5 @@
# Simple input example
name = input("Enter your name: ")
print(f"Hello, {name}!")
for i in range(5):
print(f"Count: {i}")