use std::path::{Path, PathBuf};
use std::fs;
use std::collections::HashMap;
use crate::error::{Error, Result};
use crate::wrappers::{WrapperGenerator, WrapperSpec, ApplicationType};
use crate::templates::HTTP_SERVER_TEMPLATE;
use crate::compiler::{CompilerOptions, BuildProfile, OptimizationLevel};
use crate::compiler::{CargoCompiler, Compiler};
#[derive(Clone)]
pub struct HttpServerConfig {
pub port: u16,
pub host: String,
pub max_body_size: usize,
pub request_timeout: u64,
pub cors_enabled: bool,
pub cors_allowed_origins: Vec<String>,
pub cors_allowed_methods: Vec<String>,
pub cors_allowed_headers: Vec<String>,
pub serve_static: bool,
pub static_dir: Option<PathBuf>,
}
impl Default for HttpServerConfig {
fn default() -> Self {
Self {
port: 8080,
host: "127.0.0.1".to_string(),
max_body_size: 10 * 1024 * 1024, request_timeout: 30,
cors_enabled: false,
cors_allowed_origins: vec!["*".to_string()],
cors_allowed_methods: vec!["GET".to_string(), "POST".to_string(), "PUT".to_string(), "DELETE".to_string()],
cors_allowed_headers: vec!["Content-Type".to_string(), "Authorization".to_string()],
serve_static: false,
static_dir: None,
}
}
}
pub struct HttpServerGenerator {
config: HttpServerConfig,
compiler: CargoCompiler,
}
impl HttpServerGenerator {
pub fn new() -> Self {
Self {
config: HttpServerConfig::default(),
compiler: CargoCompiler::new(),
}
}
pub fn with_config(config: HttpServerConfig) -> Self {
Self {
config,
compiler: CargoCompiler::new(),
}
}
fn generate_cargo_toml(&self, app_name: &str) -> String {
format!(
r#"[package]
name = "{}-http-server-wrapper"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.84"
wasm-bindgen-futures = "0.4.34"
js-sys = "0.3.61"
console_error_panic_hook = "0.1.7"
serde = {{ version = "1.0.152", features = ["derive"] }}
serde_json = "1.0.93"
rmp-serde = "1.1.1"
log = "0.4.17"
futures = "0.3.28"
once_cell = "1.17.1"
anyhow = "1.0.70"
hyper = {{ version = "0.14.25", features = ["full"] }}
tokio = {{ version = "1.27.0", features = ["full"] }}
http = "0.2.9"
bytes = "1.4.0"
base64 = "0.21.0"
[dependencies.web-sys]
version = "0.3.61"
features = [
"console",
"Document",
"Element",
"HtmlElement",
"Window",
]
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
"#,
app_name
)
}
fn render_wrapper_code(&self, spec: &WrapperSpec) -> Result<String> {
match &spec.app_type {
ApplicationType::HttpServer { port } => {
let config = HttpServerConfig {
port: *port,
..self.config.clone()
};
let mut context = HashMap::new();
context.insert("app_path".to_string(), spec.app_path.to_string_lossy().to_string());
context.insert("app_args".to_string(), serde_json::to_string(&spec.arguments)?);
context.insert("port".to_string(), port.to_string());
context.insert("host".to_string(), config.host);
context.insert("max_body_size".to_string(), config.max_body_size.to_string());
context.insert("request_timeout".to_string(), config.request_timeout.to_string());
context.insert("cors_enabled".to_string(), config.cors_enabled.to_string());
context.insert("cors_allowed_origins".to_string(), serde_json::to_string(&config.cors_allowed_origins)?);
context.insert("cors_allowed_methods".to_string(), serde_json::to_string(&config.cors_allowed_methods)?);
context.insert("cors_allowed_headers".to_string(), serde_json::to_string(&config.cors_allowed_headers)?);
let mut template = HTTP_SERVER_TEMPLATE.to_string();
for (key, value) in context {
let placeholder = format!("{{{{ {} }}}}", key);
template = template.replace(&placeholder, &value);
}
Ok(template)
}
_ => Err(Error::WrapperGeneration {
reason: "Not an HTTP server application".to_string(),
wrapper_type: Some("http_server".to_string()),
}),
}
}
}
impl WrapperGenerator for HttpServerGenerator {
fn generate_wrapper(&self, spec: &WrapperSpec) -> Result<String> {
self.render_wrapper_code(spec)
}
fn compile_wrapper(&self, code: &str, output_path: &Path) -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let project_dir = temp_dir.path();
let src_dir = project_dir.join("src");
fs::create_dir_all(&src_dir)?;
fs::write(src_dir.join("lib.rs"), code)?;
let app_name = "http_server_app";
let cargo_toml = self.generate_cargo_toml(app_name);
fs::write(project_dir.join("Cargo.toml"), cargo_toml)?;
let compiler_options = CompilerOptions {
target: "wasm32-wasi".to_string(),
opt_level: OptimizationLevel::Speed,
profile: BuildProfile::Release,
..CompilerOptions::default()
};
self.compiler.compile(project_dir, output_path, &compiler_options)?;
Ok(())
}
}