axum 0.6 の開発環境を Docker で構築、公式サンプルコードの内容把握

はじめに

本記事では Rust の Web アプリフレームワーク axum 0.6 の開発環境Docker を使って構築する方法と、サンプルコードを理解するために調べたことをご紹介します。

今回のファイル構成は下記のようになっていて、apps フォルダが Docker コンテナの /var/rust と同期されています。

/path/to/your-project/
  ├ apps/
  │  └ axum-app/
  │      ├ src/
  │      │  └ main.rs
  │      └ Cargo.toml
  │
  └ docker/
      └ docker-compose.yml
OS
Windows 11 Home
Docker
Docker Desktop 4.23.0
Rust
rustc 1.72.1
目次
  1. Docker 環境を構築
  2. axum 0.6 でプログラムを作成
  3. サンプルコードの内容把握
  4. おわりに

1. Docker 環境を構築

ベースは先日の「Rust の学習環境を Docker と VSCode で構築」で紹介したもので、違いは ports でポートマッピングを追加しただけです。

docker/docker-compose.yml
version: "3"

services:
  rust-app:
    container_name: rust-app
    image: rust:latest
    ports:
      - 3000:3000
    volumes:
      - ../apps:/var/rust
    working_dir: /var/rust
    tty: true

image で latest を指定しているので、最新版の Rust で axum 0.6 が動作しない等の場合はバージョン番号を指定してみると良いかもしれません。
(今回は 1.72.1 を使用しています)

2. axum 0.6 でプログラムを作成

axum の公式サイトで紹介されている下記コードを使ってサンプルプログラムを作成します。

Docker のコンテナに入り、下記コマンドで新規プロジェクトを作成します。
axum-app は任意の名称で OK です。

root@0123456789ab:/var/rust# cargo new axum-app
    Created binary (application) `axum-app` package

次に Cargo.toml の [dependencies] に下記のように追加します。

apps/axum-app/Cargo.toml
[package]
name = "axum-app"
version = "0.1.0"
edition = "2021"

[dependencies]
# ↓ これらを追加
axum = "0.6.20"
hyper = { version = "0.14.27", features = ["full"] }
tokio = { version = "1.32.0", features = ["full"] }
tower = "0.4.13"

各バージョン番号は下記 GitHub のページを参考に、作成時点での最新版を記載しました。

そして main.rs を下記内容に変更します。

apps/axum-app/src/main.rs
use axum::{
    routing::get,
    Router,
};

#[tokio::main]
async fn main() {
    // ルーティング設定
    let app = Router::new()
        .route("/", get(|| async { "Hello, world!" }));

    // サーバを起動
    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

あとは axum-app 内で cargo run コマンドで実行です。
下記のようになれば正常完了です。

root@0123456789ab:/var/rust# cd axum-app
root@0123456789ab:/var/rust/axum-app# cargo run
    Updating crates.io index
    (省略)
    Running `target/debug/axum-app`

ブラウザで http://localhost:3000 にアクセスすると「Hello, world!」と表示されます。

3. サンプルコードの内容把握

まずは下記のルーティング部分。

axum-app/src/main.rs
let app = Router::new()
    .route("/", get(|| async { "Hello, world!" }));

route() の第1引数 "/" は URLで、第2引数の get() はそこに GET メソッドでアクセスしたときに行う処理を表しています。

get() が取る引数は関数で、指定されている || async { "Hello, world!" } はクロージャです。("Hello, world!" を返しています)

次にサーバー起動部分。

axum-app/src/main.rs
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
    .serve(app.into_make_service())
    .await
    .unwrap();

bind() はサーバのアドレスを設定する関数で、引数は &SocketAddr になります。
その引数 "0.0.0.0:3000".parse().unwrap() ですが、これは文字列スライス "0.0.0.0:3000" を SocketAddr に変換しています。

parse().unwrap() は、変換したい型が FromStr トレイトを実装している場合に、その型に変換することができます。
今回の場合は SocketAddr に FromStr が実装(インプリメント)されているで、これで型変換になるんですね。

serve() はサーバを生成するもので、ルーティング設定を渡すために app を into_make_service() 関数で IntoMakeService に変換しています。

このサーバ生成はあくまで非同期タスクとして作成するだけなので、.await をつけて実行させているようです。

await の後に unwrap() しているのはエラー時に panic (クラッシュ) させるためでしょうか。

4. おわりに

以前の投稿にも書きましたが、今 Rust の学習を書籍「Webアプリ開発で学ぶ Rust 言語入門」を読みながら進めています。

この本にある axum のサンプルコードは上記とは若干異なるのですが Route や SocketAddr 等の説明があって理解の手助けになりました。

Rust はネット上にも多くの資料がありますが、特に英語が苦手な場合はこういった入門書があると学習効率が上がるかもしれません。

ただし書籍だけでは情報量が不十分だったり、バージョンが古かったりするので、より理解を深めるために公式ドキュメントや他資料も参照しならが学習を進めるのが大切と考えています。