use crate::server::utils::{get_free_port, wait_for_port}; use mktemp::Temp; use std::error::Error; use std::io::{Cursor, Read}; use std::process::{Child, Stdio}; use zip::ZipArchive; pub struct EmbeddedServer { _srv_dir: Temp, port: u16, child: Child, } impl EmbeddedServer { /// Start embedded Grammalecte server on a random free port pub fn start() -> Result> { Self::start_listen_on_port(get_free_port()?) } /// Start embedded Grammalecte server on a given port pub fn start_listen_on_port(port: u16) -> Result> { log::info!("Will start server"); // First, unpack server let dest = mktemp::Temp::new_dir()?; let cursor = Cursor::new(include_bytes!("GrammalecteDist.zip")); let mut zip = ZipArchive::new(cursor)?; for i in 0..zip.len() { let mut file = zip.by_index(i)?; if file.is_dir() { log::debug!("Create directory: {}", file.name()); std::fs::create_dir_all(dest.join(file.name()))?; } else { log::debug!("Decompress file: {}", file.name()); let mut buff = Vec::with_capacity(file.size() as usize); file.read_to_end(&mut buff)?; std::fs::write(dest.join(file.name()), buff)?; } } log::info!("Will start to listen on port {}", port); let server_file = dest .join("grammalecte/grammalecte-server.py") .to_string_lossy() .to_string(); log::info!("Will execute file {}", server_file); // Start server let child = std::process::Command::new("/usr/bin/python3") .arg(server_file) .arg("-p") .arg(port.to_string()) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()?; wait_for_port(port)?; Ok(Self { _srv_dir: dest, port, child, }) } /// Get embedde instance base URL pub fn base_url(&self) -> String { format!("http://localhost:{}", self.port) } } impl Drop for EmbeddedServer { fn drop(&mut self) { let _ = self.child.kill(); } } mod utils { use std::io::ErrorKind; use std::time::Duration; /// Get a free port pub fn get_free_port() -> std::io::Result { let mut port = 0; while !(2000..=64000).contains(&port) { port = rand::random::() % 64000; } while port_scanner::scan_port(port) { port += 1; } Ok(port) } pub fn wait_for_port(port: u16) -> std::io::Result<()> { for _ in 0..50 { if port_scanner::scan_port(port) { return Ok(()); } std::thread::sleep(Duration::from_millis(100)); } Err(std::io::Error::new( ErrorKind::Other, format!("Port {} did not open in time!", port), ))? } }