helix_core/compiler/workflow/
serve.rs1use 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}