tauri-plugin-httpd 0.1.0

A Tauri plugin that provides multi-instance HTTP server support.
Documentation
use crate::models::*;
use crate::Result;

use axum::Router;
use serde::de::DeserializeOwned;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Mutex;
use tauri::{plugin::PluginApi, AppHandle, Manager, Runtime};
use tokio::net::TcpListener;
use tokio_util::sync::CancellationToken;
use tower_http::services::ServeDir;

pub fn init<R: Runtime, C: DeserializeOwned>(app: &AppHandle<R>, _api: PluginApi<R, C>) -> Result<Httpd<R>> {
  Ok(Httpd { app_handle: app.clone(), services: Mutex::new(Vec::new()), controllers: Mutex::new(HashMap::new()) })
}

/// Access to the httpd APIs.
pub struct Httpd<R: Runtime> {
  app_handle: AppHandle<R>,
  services: Mutex<Vec<Service>>,
  controllers: Mutex<HashMap<String, CancellationToken>>,
}

impl<R: Runtime> Httpd<R> {
  pub async fn listen(&self, label: &str, port: u16, router: Router) -> Result<()> {
    let addr = SocketAddr::from(([127, 0, 0, 1], port));

    let listener = TcpListener::bind(addr).await?;

    let token = CancellationToken::new();
    let cloned_token = token.clone();
    self.controllers.lock().unwrap().insert(label.to_string(), token);

    tauri::async_runtime::spawn(async move {
      axum::serve(listener, router)
        .with_graceful_shutdown(async move {
          cloned_token.cancelled().await;
        })
        .await
        .unwrap();
    });

    self.services.lock().unwrap().push(Service { label: label.to_string(), port });
    Ok(())
  }

  // Static Server
  pub async fn listen_static(&self, label: &str, port: u16, root: &str) -> Result<()> {
    let parsed_root = self.app_handle.path().parse(&root)?;
    let router = Router::new().fallback_service(ServeDir::new(parsed_root));
    self.listen(label, port, router).await?;
    Ok(())
  }

  // get all services
  pub fn get_services(&self) -> Result<Vec<Service>> {
    Ok(self.services.lock().unwrap().clone())
  }

  // Stops a service by label
  pub fn stop_service(&self, label: &str) -> crate::Result<()> {
    if let Some(token) = self.controllers.lock().unwrap().remove(label) {
      token.cancel();
      self.services.lock().unwrap().retain(|s| s.label != label);
      Ok(())
    } else {
      Err(crate::Error::ServiceNotFound(label.to_string()))
    }
  }
}