helix_core/compiler/workflow/
serve.rs

1use std::path::PathBuf;
2use std::net::{TcpListener, TcpStream};
3use std::io::{Read, Write};
4use std::thread;
5use anyhow::{Result, Context};
6pub fn serve_project(
7    port: Option<u16>,
8    host: Option<String>,
9    directory: Option<PathBuf>,
10    verbose: bool,
11) -> Result<()> {
12    let project_dir = directory
13        .unwrap_or_else(|| {
14            find_project_root().unwrap_or_else(|_| std::env::current_dir().unwrap())
15        });
16    let port = port.unwrap_or(8080);
17    let host = host.unwrap_or_else(|| "127.0.0.1".to_string());
18    let target_dir = project_dir.join("target");
19    if !target_dir.exists() {
20        return Err(
21            anyhow::anyhow!(
22                "Target directory not found. Run 'helix build' first to compile your project."
23            ),
24        );
25    }
26    let address = format!("{}:{}", host, port);
27    if verbose {
28        println!("🌐 Starting MSO server:");
29        println!("  Address: {}", address);
30        println!("  Directory: {}", target_dir.display());
31    }
32    let listener = TcpListener::bind(&address).context("Failed to bind to address")?;
33    println!("✅ MSO server started successfully!");
34    println!("  🌐 Server running at: http://{}", address);
35    println!("  📁 Serving files from: {}", target_dir.display());
36    println!("  📋 Available endpoints:");
37    println!("    GET / - List available binaries");
38    println!("    GET /<filename> - Download binary file");
39    println!("    GET /health - Health check");
40    println!("  Press Ctrl+C to stop");
41    for stream in listener.incoming() {
42        match stream {
43            Ok(stream) => {
44                let target_dir = target_dir.clone();
45                thread::spawn(move || {
46                    handle_connection(stream, target_dir);
47                });
48            }
49            Err(e) => {
50                eprintln!("❌ Failed to accept connection: {}", e);
51            }
52        }
53    }
54    Ok(())
55}
56fn handle_connection(mut stream: TcpStream, target_dir: PathBuf) {
57    let mut buffer = [0; 1024];
58    match stream.read(&mut buffer) {
59        Ok(size) => {
60            let request = String::from_utf8_lossy(&buffer[..size]);
61            let response = handle_request(&request, &target_dir);
62            if let Err(e) = stream.write_all(response.as_bytes()) {
63                eprintln!("❌ Failed to write response: {}", e);
64            }
65        }
66        Err(e) => {
67            eprintln!("❌ Failed to read request: {}", e);
68        }
69    }
70}
71fn handle_request(request: &str, target_dir: &PathBuf) -> String {
72    let lines: Vec<&str> = request.lines().collect();
73    if lines.is_empty() {
74        return create_error_response("400 Bad Request", "Empty request");
75    }
76    let request_line = lines[0];
77    let parts: Vec<&str> = request_line.split_whitespace().collect();
78    if parts.len() < 2 {
79        return create_error_response("400 Bad Request", "Invalid request line");
80    }
81    let method = parts[0];
82    let path = parts[1];
83    match method {
84        "GET" => handle_get_request(path, target_dir),
85        _ => {
86            create_error_response(
87                "405 Method Not Allowed",
88                "Only GET requests are supported",
89            )
90        }
91    }
92}
93fn handle_get_request(path: &str, target_dir: &PathBuf) -> String {
94    match path {
95        "/" => list_binaries(target_dir),
96        "/health" => create_health_response(),
97        _ => {
98            let filename = &path[1..];
99            if filename.is_empty() {
100                return list_binaries(target_dir);
101            }
102            serve_binary_file(filename, target_dir)
103        }
104    }
105}
106fn list_binaries(target_dir: &PathBuf) -> String {
107    let mut html = String::new();
108    html.push_str("<!DOCTYPE html>\n");
109    html.push_str(
110        "<html><head><title>MSO Server - Available Binaries</title></head><body>\n",
111    );
112    html.push_str("<h1>MSO Server - Available Binaries</h1>\n");
113    html.push_str("<p>Available compiled HELIX binaries:</p>\n");
114    html.push_str("<ul>\n");
115    if let Ok(entries) = std::fs::read_dir(target_dir) {
116        for entry in entries.flatten() {
117            if let Some(filename) = entry.file_name().to_str() {
118                if filename.ends_with(".hlxb") {
119                    let size = entry.metadata().map(|m| m.len()).unwrap_or(0);
120                    html.push_str(
121                        &format!(
122                            "<li><a href=\"/{}\">{}</a> ({} bytes)</li>\n", filename,
123                            filename, size
124                        ),
125                    );
126                }
127            }
128        }
129    }
130    html.push_str("</ul>\n");
131    html.push_str("<hr>\n");
132    html.push_str("<p><a href=\"/health\">Health Check</a></p>\n");
133    html.push_str("</body></html>\n");
134    create_html_response(html)
135}
136fn serve_binary_file(filename: &str, target_dir: &PathBuf) -> String {
137    if filename.contains("..") || filename.contains("/") || filename.contains("\\") {
138        return create_error_response("400 Bad Request", "Invalid filename");
139    }
140    let file_path = target_dir.join(filename);
141    if !file_path.exists() {
142        return create_error_response(
143            "404 Not Found",
144            &format!("File '{}' not found", filename),
145        );
146    }
147    match std::fs::read(&file_path) {
148        Ok(content) => {
149            let mut response = String::new();
150            response.push_str("HTTP/1.1 200 OK\r\n");
151            response.push_str("Content-Type: application/octet-stream\r\n");
152            response.push_str(&format!("Content-Length: {}\r\n", content.len()));
153            response
154                .push_str(
155                    &format!(
156                        "Content-Disposition: attachment; filename=\"{}\"\r\n", filename
157                    ),
158                );
159            response.push_str("\r\n");
160            response
161        }
162        Err(_) => {
163            create_error_response("500 Internal Server Error", "Failed to read file")
164        }
165    }
166}
167fn create_health_response() -> String {
168    let health_json = r#"{"status": "healthy", "service": "mso-server", "timestamp": "2024-01-01T00:00:00Z"}"#;
169    let mut response = String::new();
170    response.push_str("HTTP/1.1 200 OK\r\n");
171    response.push_str("Content-Type: application/json\r\n");
172    response.push_str(&format!("Content-Length: {}\r\n", health_json.len()));
173    response.push_str("\r\n");
174    response.push_str(health_json);
175    response
176}
177fn create_html_response(html: String) -> String {
178    let mut response = String::new();
179    response.push_str("HTTP/1.1 200 OK\r\n");
180    response.push_str("Content-Type: text/html\r\n");
181    response.push_str(&format!("Content-Length: {}\r\n", html.len()));
182    response.push_str("\r\n");
183    response.push_str(&html);
184    response
185}
186fn create_error_response(status: &str, message: &str) -> String {
187    let html = format!(
188        "<!DOCTYPE html>\n<html><head><title>Error</title></head><body>\n<h1>{}</h1>\n<p>{}</p>\n</body></html>",
189        status, message
190    );
191    let mut response = String::new();
192    response.push_str(&format!("HTTP/1.1 {}\r\n", status));
193    response.push_str("Content-Type: text/html\r\n");
194    response.push_str(&format!("Content-Length: {}\r\n", html.len()));
195    response.push_str("\r\n");
196    response.push_str(&html);
197    response
198}
199fn find_project_root() -> Result<PathBuf> {
200    let mut current_dir = std::env::current_dir()
201        .context("Failed to get current directory")?;
202    loop {
203        let manifest_path = current_dir.join("project.hlx");
204        if manifest_path.exists() {
205            return Ok(current_dir);
206        }
207        if let Some(parent) = current_dir.parent() {
208            current_dir = parent.to_path_buf();
209        } else {
210            break;
211        }
212    }
213    Err(anyhow::anyhow!("No HELIX project found. Run 'helix init' first."))
214}