Rustを使ってサーバーを構築する!

2024/11/03に公開

nodejsでもやったから次はRustどと思いました
環境:
OS: MacOS
editor: vscode

デレクトリ

.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── static
    └── index.html

3 directories, 4 files

コード

cargo new server
cargo.toml
cargo.toml
[package]
name = "Server"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
warp = "0.3"
tokio = { version = "1", features = ["full"] }

main.rs
main.rs
use warp::Filter;
use std::{sync::{Arc, Mutex}, time::Duration};
use tokio::time::interval;

#[tokio::main]
async fn main() {
    // isSelectedの状態を保持する
    let is_running = Arc::new(Mutex::new(false));
    
    // HTMLファイルを読み込む
    let html = std::fs::read_to_string("static/index.html").expect("HTMLファイルが見つかりません");

    // GETリクエスト用のフィルター
    let get_route = warp::path::end()
        .map(move || warp::reply::html(html.clone()));

    // POSTリクエスト用のフィルター
    let post_route = warp::path("toggle")
        .and(warp::post())
        .and(warp::body::json())
        .map({
            let is_running = Arc::clone(&is_running);
            move |body: std::collections::HashMap<String, bool>| {
                let is_selected = body.get("isSelected").copied().unwrap_or(false);
                let mut running = is_running.lock().unwrap();
                
                if is_selected && !*running {
                    *running = true;
                    println!("処理を開始します...");
                    tokio::spawn({
                        let is_running = Arc::clone(&is_running);
                        async move {
                            let mut interval = interval(Duration::from_secs(1));
                            while *is_running.lock().unwrap() {
                                interval.tick().await;
                                println!("処理中...");
                            }
                        }
                    });
                } else if !is_selected && *running {
                    *running = false;
                    println!("処理を停止します...");
                }

                warp::reply::json(&"State received")
            }
        });

    // ルートを結合
    let routes = get_route.or(post_route);
    
    let addr = ([127, 0, 0, 1], 8000); // ここで addr を定義
    println!("Server running on http://{}", format!("{}:{}", addr.0.iter().map(|b| b.to_string()).collect::<Vec<_>>().join("."), addr.1));
    // サーバーを起動
    warp::serve(routes).run(([127, 0, 0, 1], 8000)).await;
    
}

index.html
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>遠隔操作ボタン</title>
    <style>
        body {
            background-color: #1a1a1a;
            color: #fff;
            font-family: 'Arial', sans-serif;
            overflow: hidden;
        }

        .center {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            position: relative;
        }

        .toggle-btn {
            background-color: #007bff;
            color: #fff;
            padding: 15px 30px;
            border: none;
            border-radius: 10px;
            cursor: pointer;
            font-size: 1.5em;
            position: relative;
            outline: none;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
            transition: transform 0.3s;
            z-index: 1;
        }

        .selected {
            background-color: #26ff00;
            animation: glow 1s infinite alternate;
        }

        @keyframes glow {
            from {
                box-shadow: 0 0 20px rgba(47, 255, 0, 0.5);
            }
            to {
                box-shadow: 0 0 40px rgb(94, 255, 0);
            }
        }

        .error-message {
            color: rgb(115, 255, 0);
            font-weight: bold;
            position: absolute;
            top: 0; /* ボタンの上に表示 */
            left: 50%;
            transform: translateX(-50%);
            display: none; /* 初期は非表示 */
            z-index: 2;
        }
        .server-message {
            color: blue;
            font-weight: bold;
            position: absolute;
            top: 0; /* ボタンの上に表示 */
            left: 50%;
            transform: translateX(-50%);
            display: none; /* 初期は非表示 */
            z-index: 2;
        }

        .server_err {
            background-color: rgb(251, 59, 59);
            color: #fff;
            padding: 15px 30px;
            border: none;
            border-radius: 10px;
            /*cursor: pointer;*/
            font-size: 1.5em;
            position: relative;
            outline: none;
            /*box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);*/
            transition: transform 0.3s;
            z-index: 1;
        }
        .server_sf {
            background-color: rgb(78, 99, 255);
            color: #fff;
            padding: 15px 30px;
            border: none;
            border-radius: 10px;
            /*cursor: pointer;*/
            font-size: 1.5em;
            position: relative;
            outline: none;
            /*box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);*/
            transition: transform 0.3s;
            z-index: 1;
        }
    </style>
</head>
<body>
    <div class="center">
        <div class="server_sf" id="serverMessage">安全</div>
        <div class="server_err" id="errorMessage">エラー</div>
        <button class="toggle-btn" id="toggleButton">実行</button>
    </div>

    <script>
        const button = document.getElementById('toggleButton');
        const errorMessage = document.getElementById('errorMessage');
        const serverMassage = document.getElementById('serverMessage');
        let isSelected = false;

        errorMessage.style.display = 'none'; // 正常な応答時にエラーメッセージを非表示
        serverMassage.style.display = 'block'; // 正常な応答時にエラーメッセージを非表示

        button.addEventListener('click', () => {
            isSelected = !isSelected;
            button.classList.toggle('selected', isSelected);
            button.textContent = isSelected ? "運転中..." : "実行";

            fetch('/toggle', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ isSelected })
            })
            .then(response => {
                console.log('サーバーの応答:', response); // サーバー応答のデバッグ
                if (!response.ok) {
                    throw new Error("サーバーの応答が不正です");
                }
                return response.json();
            })
            .then(data => {
                console.log('サーバーからの応答:', data);
                errorMessage.style.display = 'none'; // 正常な応答時にエラーメッセージを非表示
                serverMassage.style.display = 'block'; // 正常な応答時にエラーメッセージを非表示
            })
            .catch(error => {
                console.error('エラー:', error);
                console.error('サーバーからの応答')
                errorMessage.style.display = 'block'; // エラー時にエラーメッセージを表示
                serverMassage.style.display = 'none'; // 正常な応答時にエラーメッセージを非表示
            });
        });
    </script>
</body>
</html>

実行

実行
cargo run

最後に

重要

他のパソコンや端末でやてみたいと思うかもしれませんが、http://localhost:8000このURLを打ち込んでもうまくいきません!
ターミナルで

ifconfig

でわかります

僕の場合
% ifconfig   
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
	options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
	inet 127.0.0.1 netmask 0xff000000 <= この行!
	inet6 ::1 prefixlen 128 
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 
	nd6 options=201<PERFORMNUD,DAD> ...

つまり他のパソコンや端末で動かすためには、
調べたやつ:127.0.0.1 :8000ということなのでhttp://127.0.0.1:8000に行けばうまくいきます

よくわからない時はGeminiChatGPTに聞くとわかったりします

Discussion