ℹ️

【Axum 0.7.9】WindowsでAxum環境構築

2024/12/27に公開

AxumWindowsで利用する

この記事では、WindowsAxumを「最低限」扱えるだけの情報を雑集してございます。
RustVSCodeのインストールは説明を省いております。

Axumの概要

Axumとは、言語Rustに於いて「Webサーバー側のプログラムを実装できるもの」、等と筆舌し得るものです。単なるWebサイトを作成することも、Webアプリケーションを作成することもできます。

https://www.shuwasystem.co.jp/book/9784798067315.html

我が国ではこの著書が一つ有名なものと存じます。此書をきっかけにして、RustでのWeb開発を始めたという方も少なくないのでしょう。
Webサーバー側」などと言い表したように、ブラウザーでの見て呉れ(所謂クライアント側)を云云という話はAxumの与り知るところではなく、従って此書も、そうしたところはRustではなくTypeScriptなどを使用しています。そうした箇所をもRustで実装する場合は、別にWebAssemblyを扱うこととなります。

余談

私事ですが、Axumは疎か、Rustという言語の存在を初めて知り、挙句の果てはWeb技術に初めて触れる機会となったのが此書でありました。当時は未だIPアドレスを納得していない頃であり、getpostを単なる英単語としか認識していなかったことを鮮明に覚えています。JavaScriptPHPを学んだのはその後のことでしたから、そこで漸く合点が行きました。
即ち⋯

Axumを扱う上では、Web技術に関する基礎的な理解がないと(私のように)徒労するでしょう。まずはRustという特殊な場面ではなく、PHPなどの標準的な場面でWeb技術を学ぶことが先決です。
そうした理解の上、未だ自信がないのであれば、私からはFlaskをご覧ぜよと申し上げます。平易な言語として知られるPythonWebアプリケーションを実装する様は、Axumとそう遠くありません。そう近くもありませんが。

本記事で用いるAxumのチュートリアル

https://github.com/tokio-rs/axum?tab=readme-ov-file#usage-example

なぜ斯うも付属が多いのかと嘆息するを堪え、Usage exampleとあるのを試すことと致します。

開発環境としてのVSCode

\overset{\small \text{こんな記事で説明するのはあまりに面倒}}{\text{このような記事が説明するにはあまりにも有名}}

ということで、Visual Studio Code、通称VSCodeの説明は扨置きます。
ここでは既にVSCodeを導入した前提で、拡張機能についてのお話に移ります。

rust-analyzer Rust開発の必需機能

https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer

VSCodeRust開発を行うという話で大抵見かけるのが、このrust-analyzerです。私が感じた利点は次。

  1. 誤っている箇所が分かる
    誤りを指摘されている様子
    誤りを指摘されている様子
    波線を引いて誤謬を強調される外、マウスカーソルを合わせることで、当該エラーメッセージを確認できるというものです。
  2. 適当な補完候補が分かる
    候補が一覧される
    候補が一覧される
    通常にプログラムコードを書く際にも、誤りを訂正せんとする際にも言える事ですが、何の頼りもないのと、有効な候補から選択するのとでは、難易に大きな差があることは明白というもの。殊に、バージョン更新により互換性が失われた場合、候補の一覧からいち早く嫌な予感を察知できます。
  3. データ型が分かる
    データ型が表示される
    データ型が表示される
    Rustは「型推論」を採用しているため、とりあえずletと書けばよく、データ型を明記する必要がありません。型推論の明確な欠点として、プログラムコードを見ただけではデータ型が分からないことがあります。そうした欠点を補完して、プログラムの理解に供することができます。

なお、ファイルを開くだけでは機能しません。的外れなフォルダーを開いていても機能しません。VSCodeが「cargo package」を開いていなければ、rust-analyzerは正しく動作しません。cargo packageとは、cargo newで生成されるフォルダーを指します。

フォルダーを開く
「フォルダーを開く」からcargo packageを選択する

Postman Webアプリケーションの動作確認

https://marketplace.visualstudio.com/items?itemName=Postman.postman-for-vscode

昨日初めて知りました。にわか仕込みの知識で高説上申候。

Postmanでは、Webアプリケーションの動作を実際に確認することができます。デスクトップ版も存在しますが、VSCodeの拡張機能さえインストールすれば、そちらは不要でした。もう一つ、Web版も存在しますが、そちらはlocalhostなどへは使用できないとのことです。

can’t send requests to the Localhost
Web版でのエラーメッセージ

その他にも同類の拡張機能としてREST Clientがありますし、一般のHTTP Client(REST Client)ツールを使用しても結果は変わりません。上述した著書ではInsomniaというものを推薦していますが、そちらはVSCodeの拡張機能としては存在しないようでした。

Postmanの所感は次。

  1. ユーザー登録が求められる
    拡張機能を利用するだけにしては仰々しいものだと感じましたが、Googleアカウントなどで登録すれば、特に操作もなく完了しました。
  2. UIがある
    キーボード入力を完全に省略するほどではありませんが、初めて知った私でも充分理解できました。一方で同じく拡張機能であるREST Clientは、.httpファイルを作成し(🤔)、送信内容を手入力(🤔)する手法を取っているらしく、私は全然やりたくありませんでした。私が手入力の有用性に全く思い至らない間は、どのみち無用の長物でしょう。

PowerShellの環境変数

WindowsVSCodeでは、「ターミナル」としてPowerShellを採用しているようです。「コマンド プロンプト」との違いは、lsが使えるという点です。dirを入力し慣れない方なら、些細なストレス軽減になるでしょう。

さて、Windowsであるが為に障壁となるのが、環境変数の設定です。先に示したチュートリアル中にはありませんが、上述の著書では早い段階で環境変数を使用します。ところが、Mac(UNIX系)環境を前提に扱っているため、Windowsについては特に触れられていません。(勿論)WindowsではUNIX系と同じ手段が使えないため、万策尽きた私は一度ここで諦めています。

例に、RUST_LOGという環境変数にdebugという値を定めます。この設定の直後、cargo runでプログラムを実行します。

UNIX系の場合

unix系
RUST_LOG=debug cargo run

PowerShellの場合

powershell
$Env:RUST_LOG = "debug"
cargo run

https://doc.rust-jp.rs/book-ja/ch12-05-working-with-environment-variables.html

cargo add

Rustでは、主題に据えているAxumのように、独立した便利なプログラムの纏まりを(大抵)crateと呼びます。packagecratelibrarymoduleなどの言葉は混濁されがちですが、Rustでは案外細かく区別されています

packageで使われるcrateは、Cargo.tomlというファイルで管理されます。その方法は主に二つ。

  1. 直接編集する
  2. cargo addコマンドに委ねる

直接編集する場合は置いておいて、問題はcargo addの場合にあります。

チュートリアルのCargo.tomlを見てみると、featuresというものが見られます。

[dependencies]
axum = { path = "../../axum" }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

単にcargo add serdeなどとした場合、featuresの部分は反映されず、バージョン番号のみが書かれた状態になります。featuresもコマンドで反映するためには、次のように記述することとなります。

cargo add serde --features derive

従って、全てのcrateを追加するコマンドはそれぞれ次の通りです。

cargo add axum
cargo add serde --features derive
cargo add tokio --features full
cargo add tracing
cargo add tracing-subscriber --features env-filter

実演手順

導入・インストールは終えたものとして、実演の手順を示します。

1. cargo new packageを作る

package名:axum_example
名前に大文字を含めると文句を言われるので注意。

packageを作る
cargo new axum_example

axum_exampleというフォルダーができるので、VSCodeで開くと良いでしょう。
以降は、全てaxum_exampleでの操作となります。cargoコマンドは、Cargo.tomlの存在する位置でなければエラーになります。

treeコマンドで確認すると次のように表示されました。targetフォルダーは中身が多くて邪魔なので、消した上での結果です。

treeコマンドの結果(一部飲す)
PS C:\\axum_example> rmdir .\target\

Confirm
The item at C:\\axum_example\target\ has children and the Recurse parameter was not specified. If you continue, all children    
will be removed with the item. Are you sure you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
PS C:\\axum_example> tree /f
Folder PATH listing for volume OS
Volume serial number is ⋯
C:.
│   .gitignore
│   Cargo.lock
│   Cargo.toml
│
└───src
        main.rs

2. cargo add crateを追加する

crateを追加する
cargo add axum
cargo add serde --features derive
cargo add tokio --features full
cargo add tracing
cargo add tracing-subscriber --features env-filter

3. main.rsを編集する

axum_example/src/main.rsが存在するので、これを編集します。
敢えて環境変数を確認するため、チュートリアルを次の通り改変しております。

main.rs
use axum::{
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Json,
    Router,
    serve
};
use serde::{Deserialize, Serialize};
use tokio::net::TcpListener;
use std::env;

// the input to our `create_user` handler
#[derive(Deserialize)]
struct CreateUser {
    username: String,
}

// the output to our `create_user` handler
#[derive(Serialize)]
struct User {
    id: u64,
    username: String,
}

#[tokio::main]
async fn main() {
    // initialize logging
    let log_level: String = env::var("RUST_LOG").unwrap_or(String::from("info"));
    println!("log_level: {log_level}");
    env::set_var("RUST_LOG", log_level);

    // initialize tracing
    tracing_subscriber::fmt::init();

    // build our application with a route
    let app: Router =
        Router::new()
        // `GET /` goes to `root`
        .route("/", get(root))
        // `POST /users` goes to `create_user`
        .route("/users", post(create_user));

    // run our app with hyper
    let listener: TcpListener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
    tracing::debug!("listening on {}", listener.local_addr().unwrap());

    // serve app
    serve(listener, app).await.unwrap();
}

// basic handler that responds with a static string
async fn root() -> &'static str {
    "Hello, World"
}

async fn create_user(
    // this argument tells axum to parse the request body
    // as JSON into a `CreateUser` type
    Json(payload): Json<CreateUser>,
) -> impl IntoResponse {
    // insert your application logic here
    let user: User = User {
        id: 1337,
        username: payload.username,
    };

    // this will be converted into a JSON response
    // with a status code of `201 Created`
    (StatusCode::CREATED, Json(user))
}

4. cargo run プログラムを実行する

環境変数RUST_LOGの値をdebugに定めます。

環境変数を設定する
$Env:RUST_LOG = "debug"
プログラムを実行する
cargo run

または、一行に纏めることもできます。

一行で環境変数の設定と実行を記述する場合
$Env:RUST_LOG = "debug"; cargo run

localhost3000番で接続待機するようになります。

5. Postmanで確認する

VSCode版での操作です。
チュートリアルのプログラムは、GET methodPOST methodの二つを実装しているので、それぞれ検証します。

該当箇所
    // build our application with a route
    let app: Router =
        Router::new()
        // `GET /` goes to `root`
        .route("/", get(root))
        // `POST /users` goes to `create_user`
        .route("/users", post(create_user));

Postmanのメニューから、New HTTP Requestを選びます。

New HTTP Requestを選ぶ
New HTTP Requestを選ぶ

GET methodのまま、URL http://127.0.0.1:3000を記述します。http://localhost:3000でも可。
記述した後、Sendを選んで送信します。

URLを記述してSendを選ぶ
URLを記述してSendを選ぶ

この結果は、rootと実装された内容に基づいていると確認できます。

rootの実装箇所
// basic handler that responds with a static string
async fn root() -> &'static str {
    "Hello, World"
}

応答が表示される
応答が表示される

ここでは、続けてmethodPOSTに、URLhttp://127.0.0.1:3000/usersに変更します。.route("/users", post(create_user))"/users"という定義によるものです。

POSTを選ぶ
POSTを選ぶ

URLを変更する
URLを変更する

ここで、JSONデータを送信するため、Headersメニューに記述を加えます。決まりなのでその通りに書くしかありません。

key value
content-type application/json

Headersを編集する
Headersを編集する

BodyrawJSONに設定し、送信するJSONデータを手入力します。

rowからJSONを選ぶ
rowからJSONを選ぶ

送信データの形式はCreateUserとして定められています。

CreateUser
// the input to our `create_user` handler
#[derive(Deserialize)]
struct CreateUser {
    username: String,
}

ここでは、{"username": "Axum太郎"}として送信します。

Sendを選ぶ
Sendを選ぶ

応答の型式はUserとして定められています。

User
// the output to our `create_user` handler
#[derive(Serialize)]
struct User {
    id: u64,
    username: String,
}

応答
応答

1337という数字が入っているのは、まさにcreate_userによる処理の結果です。

async fn create_user(
    // this argument tells axum to parse the request body
    // as JSON into a `CreateUser` type
    Json(payload): Json<CreateUser>,
) -> impl IntoResponse {
    // insert your application logic here
    let user: User = User {
        id: 1337,
        username: payload.username,
    };

    // this will be converted into a JSON response
    // with a status code of `201 Created`
    (StatusCode::CREATED, Json(user))
}

Discussion