RustでAPIサーバーを立てる練習をする

RustでAPIサーバーを立てる練習をしましょう。

動機は「Rustですか？　少しは使えますけど……」と答えてみたいからです。

どうしてAPIサーバーか。

他のプログラムを作ったときに、組み合わせやすいからです。

というわけで、`axum`を導入するところから始めます。

https://github.com/tokio-rs/axum

ドキュメントの例示コードを動かすところから始めます。

# パッケージのインストール

`Cargo.toml`は以下を参照し、必要なパッケージをインストールします。

https://github.com/tokio-rs/axum/blob/main/examples/readme/Cargo.toml

`cargo add axum`みたいな感じで、ひとまずコマンドラインから追加しました。

例示とはバージョンがずれると思うので動かない可能性がありますから、後で随時直します。


```
cargo new test-api-server
cd test-api-server
cargo add axum
...
```

```toml:Cargo.toml
[package]
name = "test-api-server"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7.9"
serde = "1.0.216"
tokio = "1.42.0"
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
```

# ソースコードのコピペ

ソースコードを以下からコピペします。

https://github.com/tokio-rs/axum/blob/main/examples/readme/src/main.rs

# 動作確認

```
cargo run
```

動かすと、コンパイル段階でエラーになります。

そりゃそうですね。

エラーメッセージから検討をつけると、features関連っぽいので、exampleを参考にして、featuresフラグを付加します。

```diff:cargo.toml
- serde = "1.0.216"
+ serde = { version = "1.0.216", features = ["derive"] }
- tokio = "1.42.0"
+ tokio = { version = "1.42.0", features = ["full"] }
```

もう一度動かしてみます。

```
...
Compiling test-api-server v0.1.0 (E:\test-api-server)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 25.03s
 Running `target\debug\test-api-server.exe`
```
こんな感じのログになりましたので、APIサーバーが立ちあがったようです。

確認してみましょう。

# リクエストを送信してみるテスト

https://marketplace.visualstudio.com/items?itemName=humao.rest-client

VSCodeへ上記の拡張機能を追加し、`request.http`というファイルを作成します。

```http:request.http
GET http://127.0.0.1:3000
```

そして、上記の記載の上に表示された「Send Request」をクリックして、以下のレスポンスが表示されればOKです。

```
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 13
connection: close
date: Mon, 23 Dec 2024 09:55:08 GMT

Hello, World!
```

さらにPOSTもテストします。

`request.http`へ`###`で区切って、リクエストを追加します。

```http:request.http
GET http://127.0.0.1:3000

###

POST http://127.0.0.1:3000/users
Content-Type: application/json

{
  "username": "Alice"
}
```

これを同様に実行して、以下のようになればPOST側もOKです。

```
HTTP/1.1 201 Created
content-type: application/json
content-length: 30
connection: close
date: Mon, 23 Dec 2024 09:57:20 GMT

{
  "id": 1337,
  "usern1ame": "Alice"
}
```

ほぼコピペしかしていませんが、無事APIサーバーが立ちました。

# それから、ついでにビンゴカードを生成する

これだけではつまらないため、何かしてみます。

少し考えた末、別の記事で作ったのと同じ、ビンゴカードを生成するエンドポイントをつくることにしました。

```rust
let app = Router::new()
    .route("/", get(root))
    .route("/users", post(create_user))
    .route("/cards", get(create_cards));
```

`/cards`エンドポイントを作成します。

`create_card`はそれっぽく書きます。

```rust
async fn create_cards() -> impl IntoResponse{
    let mut cards: Vec<Vec<u8>> = Vec::new();
    let mut rng = rand::thread_rng();
    let mut fy = FisherYates::default();

    for n in 0..5 {
        let mut tmp: Vec<u8> = (n*15..n*15+15).collect();
        let _ = fy.shuffle(&mut tmp, &mut rng);
        cards.push(tmp[0..5].to_vec());
    }

    (StatusCode::CREATED, Json(cards))
}
```

シャッフル処理は以下を参照しました。

https://docs.rs/shuffle/latest/shuffle/fy/struct.FisherYates.html

shuffleの第2引数は乱数生成器を入れるイメージでしょうか。

https://docs.rs/rand/latest/rand/fn.thread_rng.html

これで、以下のようなレスポンスが得られました。

```
HTTP/1.1 201 Created
content-type: application/json
content-length: 83
connection: close
date: Mon, 23 Dec 2024 10:54:11 GMT

[
  [
    8,
    7,
    13,
    12,
    4
  ],
  [
    19,
    20,
    18,
    21,
    24
  ],
  [
    43,
    44,
    37,
    32,
    33
  ],
  [
    45,
    54,
    52,
    57,
    49
  ],
  [
    66,
    74,
    72,
    65,
    68
  ]
]
```

# 終わりに

これで基本的なAPIサーバーの立ち上げ方がわかりました。

実際に複雑な処理をしようとすると、Rustの勉強不足で挫折しそうなので、まずは小さなステップを踏めたところで満足しています。

ここまで読んでいただきありがとうございました。