You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Actix Web服务器中调用Tauri实例控制窗口状态?

解决Tauri应用与Actix Web共享实例的多线程控制问题

我开发了一款运行后台HTTP服务的Tauri应用,希望通过本地HTTP接口控制该应用,但遇到了实现障碍。以下是我的main.rs代码:

// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use actix_web::web;
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
use std::sync::{Once, ONCE_INIT};
use tauri::Manager;
use serde::Deserialize;

static INIT: Once = ONCE_INIT;
static mut APP: Option<tauri::App> = None;

#[derive(Deserialize)]
struct AppSession {
    action: String,
}

#[get("/")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello world!")
}

#[get("/actions")]
async fn handle_actions(query: web::Query<AppSession>) -> impl Responder {
    let action = &query.action;
    unsafe {
        if let Some(app) = APP.as_ref() {
            match action.as_str() {
                "session_end" => {
                    app.get_window("main").unwrap().show().unwrap();
                    HttpResponse::Ok().body("Session ending, app shown")
                }
                "session_start" => {
                    app.get_window("main").unwrap().hide().unwrap();
                    HttpResponse::Ok().body("Session starting, app hidden")
                }
                _ => HttpResponse::Ok().body("Unknown action"),
            }
        } else {
            HttpResponse::InternalServerError().body("App instance not available")
        }
    }
}

fn main() {
    let app = tauri::Builder::default()
        .plugin(tauri_plugin_websocket::init())
        .setup(|app| {
            tauri::async_runtime::spawn(
                HttpServer::new(move || {
                    let app = app.clone();
                    App::new()
                        .service(hello)
                        .service(handle_actions)
                        .app_data(app)
                })
                .bind(("127.0.0.1", 8080))
                .expect("Failed to bind address")
                .run(),
            );

            Ok(())
        })
        .build(tauri::generate_context!())
        .expect("error while running tauri application");

    INIT.call_once(|| {
        // Initialize your tauri::App instance here
        unsafe {
            APP = Some(app);
        }
    });

    app.run(|_, _| {})
}

我尝试使用Arc在多线程间共享Tauri App实例,但报错显示tauri::App未实现Send trait:

static INIT: std::sync::Once = std::sync::Once::new();
static mut APP: Option<Arc<Mutex<Option<tauri::App>>>> = None;

当前使用上述完整代码时,在let app = app.clone()行出现报错:

no method named `clone` found for mutable reference `&mut tauri::App` in the current scope
method not found in `&mut App`

解决方案

核心问题是Tauri的App实例本身不支持跨线程共享或克隆,它没有实现Send和Clone trait。正确的做法是使用Tauri提供的AppHandle,这是一个线程安全的轻量级句柄,专门用于在多线程环境中访问应用资源。

修改后的完整代码

// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use actix_web::web;
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
use tauri::Manager;
use serde::Deserialize;

#[derive(Deserialize)]
struct AppSession {
    action: String,
}

#[get("/")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello world!")
}

#[get("/actions")]
async fn handle_actions(
    query: web::Query<AppSession>,
    app_handle: web::Data<tauri::AppHandle>
) -> impl Responder {
    let action = &query.action;
    
    match action.as_str() {
        "session_end" => {
            match app_handle.get_window("main") {
                Some(window) => {
                    if let Err(e) = window.show() {
                        eprintln!("Failed to show window: {}", e);
                        return HttpResponse::InternalServerError().body("Failed to show window");
                    }
                    HttpResponse::Ok().body("Session ending, app shown")
                }
                None => HttpResponse::NotFound().body("Main window not found")
            }
        }
        "session_start" => {
            match app_handle.get_window("main") {
                Some(window) => {
                    if let Err(e) = window.hide() {
                        eprintln!("Failed to hide window: {}", e);
                        return HttpResponse::InternalServerError().body("Failed to hide window");
                    }
                    HttpResponse::Ok().body("Session starting, app hidden")
                }
                None => HttpResponse::NotFound().body("Main window not found")
            }
        }
        _ => HttpResponse::Ok().body("Unknown action"),
    }
}

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_websocket::init())
        .setup(|app| {
            // 获取线程安全的AppHandle
            let app_handle = app.handle();
            
            // 启动Actix Web服务,传递AppHandle
            tauri::async_runtime::spawn(
                HttpServer::new(move || {
                    let handle = app_handle.clone();
                    App::new()
                        .app_data(web::Data::new(handle))
                        .service(hello)
                        .service(handle_actions)
                })
                .bind(("127.0.0.1", 8080))
                .expect("Failed to bind address")
                .run(),
            );

            Ok(())
        })
        .build(tauri::generate_context!())
        .expect("error while running tauri application")
        .run(|_, _| {});
}

关键修改点说明

  1. 使用AppHandle替代App:在setup中通过app.handle()获取AppHandle,它实现了Clone、Send、Sync,可以安全地在多线程间传递。
  2. 通过Actix Web的web::Data传递实例:将AppHandle包装为web::Data,Actix Web会自动处理其线程安全共享,无需手动使用Arc或Mutex。
  3. 移除不安全的全局静态变量:避免了unsafe块的使用,代码更安全可靠。
  4. 增加错误处理:替换原来的unwrap()为match或if let Err,避免程序因窗口操作失败而崩溃。

可选:全局共享AppHandle(不推荐)

如果因需求必须使用全局变量存储AppHandle,可以用lazy_static配合Mutex实现:

use lazy_static::lazy_static;
use std::sync::Mutex;

lazy_static! {
    static ref APP_HANDLE: Mutex<Option<tauri::AppHandle>> = Mutex::new(None);
}

// 在setup中初始化
.setup(|app| {
    let handle = app.handle();
    *APP_HANDLE.lock().unwrap() = Some(handle);
    // ... 启动HTTP服务
})

// 在接口中使用
#[get("/actions")]
async fn handle_actions(query: web::Query<AppSession>) -> impl Responder {
    let handle = APP_HANDLE.lock().unwrap().as_ref().unwrap();
    // ... 后续逻辑
}

但这种方式不如直接通过web::Data传递优雅,尽量优先使用框架提供的依赖注入方式。


内容的提问来源于stack exchange,提问作者mandaputtra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 08:48:16