1use std::net::{IpAddr, SocketAddr};
4use std::path::PathBuf;
5
6use serde::{Serialize, Deserialize};
7
8use crate::error::Result;
9use crate::wrappers::{WrapperGenerator, WrapperSpec};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub enum HttpMethod {
14 GET,
16
17 POST,
19
20 PUT,
22
23 DELETE,
25
26 PATCH,
28
29 HEAD,
31
32 OPTIONS,
34}
35
36impl std::fmt::Display for HttpMethod {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 Self::GET => write!(f, "GET"),
40 Self::POST => write!(f, "POST"),
41 Self::PUT => write!(f, "PUT"),
42 Self::DELETE => write!(f, "DELETE"),
43 Self::PATCH => write!(f, "PATCH"),
44 Self::HEAD => write!(f, "HEAD"),
45 Self::OPTIONS => write!(f, "OPTIONS"),
46 }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct HttpRequest {
53 pub method: HttpMethod,
55
56 pub path: String,
58
59 pub query: Option<String>,
61
62 pub headers: Vec<(String, String)>,
64
65 pub body: Option<Vec<u8>>,
67
68 pub remote_addr: Option<SocketAddr>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct HttpResponse {
75 pub status: u16,
77
78 pub headers: Vec<(String, String)>,
80
81 pub body: Vec<u8>,
83}
84
85impl HttpResponse {
86 pub fn new(status: u16, body: Vec<u8>) -> Self {
88 Self {
89 status,
90 headers: Vec::new(),
91 body,
92 }
93 }
94
95 pub fn with_headers(status: u16, headers: Vec<(String, String)>, body: Vec<u8>) -> Self {
97 Self {
98 status,
99 headers,
100 body,
101 }
102 }
103
104 pub fn text(body: &str) -> Self {
106 Self {
107 status: 200,
108 headers: vec![
109 ("Content-Type".to_string(), "text/plain; charset=utf-8".to_string()),
110 ("Content-Length".to_string(), body.len().to_string()),
111 ],
112 body: body.as_bytes().to_vec(),
113 }
114 }
115
116 pub fn json<T: Serialize>(body: &T) -> Self {
118 let json = serde_json::to_vec(body).unwrap_or_default();
119 Self {
120 status: 200,
121 headers: vec![
122 ("Content-Type".to_string(), "application/json".to_string()),
123 ("Content-Length".to_string(), json.len().to_string()),
124 ],
125 body: json,
126 }
127 }
128
129 pub fn not_found() -> Self {
131 Self::text("Not Found")
132 .with_status(404)
133 }
134
135 pub fn server_error(message: &str) -> Self {
137 Self::text(message)
138 .with_status(500)
139 }
140
141 pub fn with_status(mut self, status: u16) -> Self {
143 self.status = status;
144 self
145 }
146
147 pub fn with_header(mut self, name: &str, value: &str) -> Self {
149 self.headers.push((name.to_string(), value.to_string()));
150 self
151 }
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct HttpServerConfig {
157 pub address: IpAddr,
159
160 pub port: u16,
162
163 pub threads: Option<usize>,
165
166 pub timeout_seconds: Option<u64>,
168
169 pub max_request_size: Option<usize>,
171
172 pub cors: Option<CorsConfig>,
174
175 pub tls: Option<TlsConfig>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct CorsConfig {
182 pub allowed_origins: Vec<String>,
184
185 pub allowed_methods: Vec<String>,
187
188 pub allowed_headers: Vec<String>,
190
191 pub allow_credentials: bool,
193
194 pub max_age: Option<u32>,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct TlsConfig {
201 pub cert_path: PathBuf,
203
204 pub key_path: PathBuf,
206}
207
208pub struct HttpServerGenerator;
210
211impl WrapperGenerator for HttpServerGenerator {
212 fn generate_wrapper(&self, spec: &WrapperSpec) -> Result<String> {
213 let config = HttpServerConfig {
215 address: spec.template_variables.get("address")
216 .and_then(|s| s.parse().ok())
217 .unwrap_or_else(|| "127.0.0.1".parse().unwrap()),
218 port: spec.template_variables.get("port")
219 .and_then(|s| s.parse().ok())
220 .unwrap_or(8080),
221 threads: spec.template_variables.get("threads")
222 .and_then(|s| s.parse().ok()),
223 timeout_seconds: spec.template_variables.get("timeout_seconds")
224 .and_then(|s| s.parse().ok()),
225 max_request_size: spec.template_variables.get("max_request_size")
226 .and_then(|s| s.parse().ok()),
227 cors: None,
228 tls: None,
229 };
230
231 let code = self.generate_code(&config, spec)?;
233
234 Ok(code)
235 }
236
237 fn compile_wrapper(&self, code: &str, output_path: &std::path::Path) -> Result<()> {
238 std::fs::write(output_path, code)
240 .map_err(|e| crate::error::Error::WrapperGeneration {
241 reason: format!("Failed to write wrapper code: {}", e),
242 wrapper_type: Some("http_server".to_string()),
243 })?;
244 Ok(())
245 }
246}
247
248impl HttpServerGenerator {
249 fn generate_code(&self, config: &HttpServerConfig, spec: &WrapperSpec) -> Result<String> {
251 let address = format!("{}:{}", config.address, config.port);
252 let _threads = config.threads.unwrap_or(4);
253 let _timeout = config.timeout_seconds.unwrap_or(30);
254
255 let mut code = format!(
257 r#"//! HTTP server wrapper for WASM sandbox
258
259use std::{{
260 net::{{IpAddr, SocketAddr}},
261 sync::{{Arc, Mutex}},
262 time::Duration,
263}};
264
265use tokio::{{
266 net::TcpListener,
267 runtime::Runtime,
268 io::{{AsyncReadExt, AsyncWriteExt}},
269}};
270use hyper::{{
271 Body, Request, Response, Server, StatusCode,
272 service::{{make_service_fn, service_fn}},
273 header::{{HeaderValue, CONTENT_TYPE}},
274}};
275use serde_json::{{Value, json}};
276use tracing::{{info, error, debug, warn}};
277
278use wasm_sandbox::{{
279 WasmSandbox, InstanceId, SandboxConfig, InstanceConfig,
280 security::{{ResourceLimits, Capabilities}},
281}};
282
283/// Main function
284#[tokio::main]
285async fn main() -> Result<(), Box<dyn std::error::Error>> {{
286 // Initialize logging
287 tracing_subscriber::fmt::init();
288 info!("Starting HTTP server on {address}");
289
290 // Create sandbox
291 let mut sandbox = WasmSandbox::new()?;
292
293 // Load the WASM module
294 let wasm_bytes = include_bytes!("{wasm_path}");
295 let module_id = sandbox.load_module(wasm_bytes)?;
296
297 info!("Loaded WASM module");
298
299 // Create instance config
300 let instance_config = {instance_config};
301
302 // Create the instance
303 let instance_id = sandbox.create_instance(module_id, Some(instance_config))?;
304 info!("Created WASM instance: {{instance_id}}");
305
306 // Store the instance ID
307 let instance_id = Arc::new(instance_id);
308
309 // Create HTTP server
310 let make_svc = make_service_fn(move |conn| {{
311 let remote_addr = conn.remote_addr();
312 let instance_id = instance_id.clone();
313 let sandbox = sandbox.clone();
314
315 async move {{
316 Ok::<_, hyper::Error>(service_fn(move |req| {{
317 let instance_id = instance_id.clone();
318 let sandbox = sandbox.clone();
319 handle_request(req, sandbox, instance_id, remote_addr)
320 }}))
321 }}
322 }});
323
324 let addr = "{address}".parse()?;
325 let server = Server::bind(&addr)
326 .serve(make_svc)
327 .with_graceful_shutdown(shutdown_signal());
328
329 info!("Server running on http://{address}");
330
331 if let Err(e) = server.await {{
332 error!("Server error: {{}}", e);
333 }}
334
335 Ok(())
336}}
337
338/// Handle HTTP request
339async fn handle_request(
340 req: Request<Body>,
341 sandbox: Arc<Mutex<WasmSandbox>>,
342 instance_id: Arc<InstanceId>,
343 remote_addr: SocketAddr,
344) -> Result<Response<Body>, hyper::Error> {{
345 // Convert hyper request to our format
346 let (parts, body) = req.into_parts();
347 let body_bytes = hyper::body::to_bytes(body).await?;
348
349 // Create request object
350 let request = json!({{
351 "method": parts.method.as_str(),
352 "path": parts.uri.path(),
353 "query": parts.uri.query(),
354 "headers": parts.headers.iter().map(|(k, v)| {{
355 (k.as_str(), v.to_str().unwrap_or_default())
356 }}).collect::<Vec<_>>(),
357 "body": body_bytes.to_vec(),
358 "remote_addr": remote_addr.to_string(),
359 }});
360
361 // Call handler function in sandbox
362 let response = match sandbox.lock().unwrap().call_function::<_, Value>(
363 *instance_id,
364 "handle_http_request",
365 &request,
366 ).await {{
367 Ok(res) => res,
368 Err(e) => {{
369 error!("Error calling WASM function: {{}}", e);
370 return Ok(Response::builder()
371 .status(StatusCode::INTERNAL_SERVER_ERROR)
372 .body(Body::from("Internal Server Error"))?);
373 }}
374 }};
375
376 // Convert JSON response to hyper response
377 let status = response["status"].as_u64().unwrap_or(500) as u16;
378 let headers = response["headers"].as_array().unwrap_or(&Vec::new());
379 let body = response["body"].as_array().unwrap_or(&Vec::new())
380 .iter()
381 .filter_map(|v| v.as_u64().map(|n| n as u8))
382 .collect::<Vec<_>>();
383
384 // Build response
385 let mut builder = Response::builder().status(status);
386
387 // Add headers
388 for header in headers {{
389 if let (Some(name), Some(value)) = (
390 header[0].as_str(),
391 header[1].as_str(),
392 ) {{
393 builder = builder.header(name, value);
394 }}
395 }}
396
397 // Add default content type if not present
398 if !headers.iter().any(|h| h[0].as_str() == Some("content-type")) {{
399 builder = builder.header(CONTENT_TYPE, "text/plain");
400 }}
401
402 Ok(builder.body(Body::from(body))?)
403}}
404
405/// Shutdown signal handler
406async fn shutdown_signal() {{
407 tokio::signal::ctrl_c()
408 .await
409 .expect("Failed to install CTRL+C signal handler");
410
411 info!("Shutdown signal received, stopping server...");
412}}
413"#,
414 address = address,
415 wasm_path = spec.app_path.display(),
416 instance_config = "InstanceConfig::default()", );
418
419 if let Some(_tls_config) = &config.cors {
421 code = code.replace(
423 "use hyper::",
424 r#"use rustls::{{Certificate, PrivateKey, ServerConfig}};
425use tokio_rustls::TlsAcceptor;
426use std::fs::File;
427use std::io::BufReader;
428use rustls_pemfile::{certs, rsa_private_keys};
429use hyper::"#
430 );
431
432 code = code.replace(
434 "let addr = \"{address}\".parse()?;",
435 r#"let addr = "{address}".parse()?;
436
437 // Configure TLS
438 let tls_config = {
439 // Load certificate and private key
440 let cert_file = File::open("certificate.pem")?;
441 let mut reader = BufReader::new(cert_file);
442 let certs = certs(&mut reader)?;
443 let certs = certs.into_iter().map(Certificate).collect();
444
445 let key_file = File::open("private_key.pem")?;
446 let mut reader = BufReader::new(key_file);
447 let keys = rsa_private_keys(&mut reader)?;
448 let key = PrivateKey(keys[0].clone());
449
450 let mut config = ServerConfig::builder()
451 .with_safe_defaults()
452 .with_no_client_auth()
453 .with_single_cert(certs, key)?;
454
455 Arc::new(config)
456 };"#
457 );
458 }
459
460 if let Some(cors_config) = &config.cors {
462 let _allowed_origins = cors_config.allowed_origins
463 .iter()
464 .map(|o| format!("\"{}\"", o))
465 .collect::<Vec<_>>()
466 .join(", ");
467
468 let _allowed_methods = cors_config.allowed_methods
469 .iter()
470 .map(|m| format!("\"{}\"", m))
471 .collect::<Vec<_>>()
472 .join(", ");
473
474 let _allowed_headers = cors_config.allowed_headers
475 .iter()
476 .map(|h| format!("\"{}\"", h))
477 .collect::<Vec<_>>()
478 .join(", ");
479
480 code = code.replace(
482 "/// Handle HTTP request",
483 r#"/// Apply CORS headers
484fn apply_cors(builder: &mut http::response::Builder) {
485 builder
486 .header("Access-Control-Allow-Origin", "*")
487 .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
488 .header("Access-Control-Allow-Headers", "Content-Type, Authorization")
489 .header("Access-Control-Allow-Credentials", "true")
490 .header("Access-Control-Max-Age", "3600");
491}
492
493/// Handle HTTP request"#
494 );
495
496 code = code.replace(
498 "// Add default content type if not present",
499 r#"// Add CORS headers
500 apply_cors(&mut builder);
501
502 // Add default content type if not present"#
503 );
504 }
505
506 Ok(code)
507 }
508}