アプリ内にローカル Web サーバーを立てる

Recipe ID: net-011

Tauri アプリケーションのバックグラウンドプロセスとして、ローカル Web サーバー (HTTP Server) を起動する方法を解説します。

Web ブラウザ上のアプリから自分自身のバックエンド API を呼び出す構成にしたり、他のデバイス(スマホなど)からネットワーク経由で Tauri アプリを操作させるインターフェースを提供したり、OAuth のコールバック (http://localhost:xxxx/callback) を受け取ったりする場合に有用です。

Tauri v2 は tokio ランタイム上で動作しているため、axum などの Tokio ベースの Web フレームワークと非常に相性が良いです。

実装手順 (Axum を使用)

ここでは、Rust のモダンな Web フレームワークである axum を使用して、Tauri アプリの起動と同時にローカルサーバーを立ち上げる方法を紹介します。

1. 依存関係の追加 (src-tauri/Cargo.toml)

axum と、必要に応じて tokio の機能を有効にします。

[dependencies]
tauri = { version = "2.0.0", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Web フレームワーク axum を追加
axum = "0.7"
# Tokio は Tauri に含まれていますが、full features があると安心です
tokio = { version = "1", features = ["full"] }

2. Rust 側でサーバーを起動 (src-tauri/src/lib.rs)

Tauri の setup フックの中で、非同期タスクとしてサーバーを起動します。

use tauri::{AppHandle, Emitter, Manager};
use axum::{
    routing::{get, post},
    Router,
    extract::{State, Json},
};
use std::net::SocketAddr;
use serde::{Deserialize, Serialize};

// サーバー内で共有したい状態(例:TauriのAppHandle)
#[derive(Clone)]
struct ServerState {
    app_handle: AppHandle,
}

#[derive(Deserialize)]
struct PostPayload {
    message: String,
}

// ハンドラ: Hello World
async fn root() -> &'static str {
    "Hello from Tauri Local Server!"
}

// ハンドラ: フロントエンドにイベントを送る
async fn notify_ui(
    State(state): State<ServerState>,
    Json(payload): Json<PostPayload>,
) -> String {
    println!("Received from external: {}", payload.message);
    
    // Tauri のメインウィンドウ等にイベントを送信
    let _ = state.app_handle.emit("server-event", &payload.message);
    
    format!("Message '{}' received and sent to UI", payload.message)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            // AppHandle を複製してサーバーに渡す準備
            let app_handle = app.handle().clone();

            tauri::async_runtime::spawn(async move {
                // ルーティング設定
                let app_router = Router::new()
                    .route("/", get(root))
                    .route("/notify", post(notify_ui))
                    .with_state(ServerState { app_handle });

                // ポート設定 (0を指定して空きポートを探すことも可能ですが、固定が扱いやすい場合も多い)
                let port = 3000;
                let addr = SocketAddr::from(([127, 0, 0, 1], port));

                println!("Local server running at http://{}", addr);

                // サーバー起動
                let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
                axum::serve(listener, app_router).await.unwrap();
            });

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

3. 動作確認

アプリを起動すると、裏で http://127.0.0.1:3000 が立ち上がります。
ブラウザや curl コマンドでアクセスして確認できます。

GET リクエスト確認

curl http://127.0.0.1:3000/
# Output: Hello from Tauri Local Server!

POST リクエストとアプリ連携確認

外部から POST リクエストを送ると、Rust サーバーがそれを受け取り、Tauri のイベントシステムを通じてフロントエンド画面に通知を送ります。

curl -X POST -H "Content-Type: application/json" -d '{"message": "Hello from Curl"}' http://127.0.0.1:3000/notify

JavaScript 側 (受信コード)

import { listen } from '@tauri-apps/api/event';

async function setupServerListener() {
    // イベントリスナーの登録
    // listen は Promise を返し、解決されると解除用の関数(unlisten)が返ってきます
    const unlisten = await listen<string>('server-event', (event) => {
        console.log('Server received:', event.payload);
        // => "Hello from Curl"
    });

    // リスナーを解除したいタイミングで unlisten() を呼び出します
    // unlisten(); 
}

その他のアプローチ

Sidecar (サイドカー) パターン

Python (Flask/FastAPI) や Node.js (Express) など、Rust 以外の言語でサーバーを書きたい場合は、Tauri の Sidecar 機能を使用します。 これは実行ファイルを同梱して子プロセスとして起動する仕組みです。 (参照: 公式ドキュメント "Sidecar"

静的ファイルの配信

単に動画ファイルやローカルのドキュメントを表示したいだけであれば、Web サーバーを立てる代わりに Custom Protocol (tauri-plugin-localhost など) を使うほうが、ポート管理が不要で安全な場合があります。

注意点

1. ポートの競合: 固定ポート (3000など) を使う場合、既に他のアプリが使っていると起動に失敗します。堅牢にするにはポート 0 を指定して動的ポートを使い、そのポート番号をファイルやイベントでアプリに通知する仕組みが必要です。
2. セキュリティ (localhost): 127.0.0.1 にバインドしている場合、同じマシンの他のユーザーやブラウザからもアクセス可能です。機密データを扱う場合は認証トークンを設けるなどの対策が必要です。
3. セキュリティ (LAN公開): 0.0.0.0 にバインドすると、同じ Wi-Fi ネットワーク内のスマホなどからもアクセスできるようになります。便利な反面、意図しないアクセスを受けるリスクがあるため、ファイアウォールや認証の実装を検討してください。