1.3K Views
October 09, 24
スライド概要
2024年10月8日に開催された「Osaki.rs Rustを使ったWeb開発のLT会」の登壇資料です。
コラボスタイル開発部の中の人 | チームマネージャー・テックリード | 趣味は登山とスキーとバックパックを作ること | Rust が好きです
axum で モック用の API サーバーを起動できる CLI ツール作ってみた 2024.10.08 Osaki.rs Rustを使ったWeb開発のLT会 Kazuno Fukuda
AGENDA 1. 自己紹介 2. Rust の Web フレームワークたち 3. axum について 4. axum で実装した CLI ツール "mocks" 5. まとめ
INTRODUCTION Kazuno Fukuda ふくだ かずの 株式会社コラボスタイル 開発部 チームマネージャー/テックリード ・推し言語: Rust ・趣味: 登山とスキー ・Rust.Nagoya を主催しました @codemountains
WEB FRAMEWORKS Rust の Web フレームワークたち
WEB FRAMEWORKS Rust の Web フレームワークたち Actix Web axum PAVEX Rocket Tide Ntex Poem Warp SALVO Loco
WEB FRAMEWORKS Rust の Web フレームワークたち ちょっと気になる Actix Web axum PAVEX Rocket Tide Ntex Poem Warp SALVO Loco
WEB FRAMEWORKS Rust の Web フレームワークたち Loco Rust on Rails = Rust 版の Ruby on Rails PAVEX Zero To Production In Rust の 著者 Luca Palmieri が開発 Ntex マイクロフレームワークを謳っており、開発が活発
WEB FRAMEWORKS Rust の Web フレームワークたち Actix Web axum PAVEX Rocket Tide Ntex Poem Warp SALVO Loco
WHAT IS AXUM? axum について... Tokio のエコシステムの利点をフル活用できる ・Tokio チームが開発している ・Tokio エコシステムと統合され、相性が良い ◦ 非同期ランタイム: Tokio ◦ ミドルウェアフレームワーク: Tower ◦ HTTP サーバー: Hyper ◦ トレーシング・ロギング: tracing ・マクロレスなルーティング ・柔軟なハンドラシステム (FromRequest, IntoResponse) シンプルでクセがない!
WHAT IS AXUM? axum について... 最も勢いがあるフレームワークの 1 つ ・actix-web ◦ Github: 21.5k stars ◦ crates.io: 25m downloads ・axum ◦ Github: 18.6k stars ◦ crates.io: 65.2m downloads ... DL はダントツ ・Rocket ◦ Github: 24.3k stars ◦ crates.io: 5.3m downloads
WHAT IS AXUM? Github stars の推移 Star History 20.0k 15.0k 10.0k 5.0k Rocket actix-web axum の伸び率が No.1 2018 2020 2022 2024 Date
WHAT IS AXUM? axum について... 参考書籍や記事が増えてきた Rust 言語入門 Webアプリ開発で学ぶ Webに特化したRustの教科書! 基礎文法からWeb開発まで網羅! RustによるWebアプリケーション開発 設計からリリース・運用まで axum が 採用されている
WHAT IS AXUM? axum について... 参考書籍や記事が増えてきた ・Zenn ◦ actix-web : 31 ◦ axum : 42 ◦ Rocket : 10 ・Qiita ◦ actix-web : 180 ◦ axum : 127 ◦ Rocket : 91 (actix や rust rocket で検索) ...と言うか、頑張って増やしてます笑
WEB FRAMEWORKS axum で実装した CLI ツール mocks Get a mock REST APIs with zero coding within seconds.
USING AXUM axum で実装した CLI ツール “mocks” について モック用の REST API サーバーを起動できます ~/work/mocks-storage mocks storage.json --no-overwrite `mocks` started Press CTRL-C to stop Index: http://localhost:3000 Storage files: storage.json Overwrite: NO Endpoints: http://localhost:3000/_hc http://localhost:3000/comments http://localhost:3000/friends http://localhost:3000/posts http://localhost:3000/profile ・npm でいうところの json-server ・クレートたち ◦ axum & tokio ◦ clap ◦ serde_json https://github.com/mocks-rs/mocks
USING AXUM
axum で実装した CLI ツール “mocks” について
JSON ファイルの情報から API エンドポイントを生成
~/work/mocks-storage
mocks storage.json --no-overwrite
`mocks` started
Press CTRL-C to stop
Index:
http://localhost:3000
Storage files:
storage.json
Overwrite:
NO
Endpoints:
http://localhost:3000/_hc
http://localhost:3000/comments
http://localhost:3000/friends
http://localhost:3000/posts
http://localhost:3000/profile
~/work/mocks-storage (0.172s)
bat storage.json
File: storage.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"comments": [
{
"id": 1,
"post_id": "01J7BAKH37HPG116ZRRFKHBD6B",
"text": "a comment"
},
{
"id": 2,
"post_id": "01J7BAKH37HPG116ZRRFKHBD6B",
"text": "another comment"
}
],
"friends": [],
"posts": [
{
"id": "01J7BAKH37HPG116ZRRFKHBD6B",
"title": "first post",
"views": 100
},
{
"id": "01J7BAKH37GE8B688PT4RC7TP4",
"title": "second post",
"views": 10
}
],
"profile": {
"id": "01J7BAQE1GMD78FN3J0FJCNS8T",
"name": "mocks"
}
}
USING AXUM
Web サーバーの起動処理 (/src/server.rs)
/// Mock server module
pub struct Server {}
impl Server {
/// Starts the mock server
///
/// # Arguments
/// * `socket_addr` - The socket address to bind the server to
/// * `url` - The base URL of the server
/// * `storage` - The storage instance to use
///
/// # Returns
/// * `Result<(), MocksError>` - Ok if the server starts successfully, Err otherwise
pub async fn startup(
socket_addr: SocketAddr,
url: &str,
storage: Storage,
) -> Result<(), MocksError> {
let listener = TcpListener::bind(socket_addr)
.await
.map_err(|e| MocksError::Exception(e.to_string()))?;
println!("Endpoints:");
print_endpoints(url, &storage.data);
let state = AppState::new(storage);
let router = create_router(state);
axum::serve(listener, router)
.await
.map_err(|e| MocksError::Exception(e.to_string()))
}
}
とてもスッキリ書ける
USING AXUM
ルーティングの定義 (/src/server.rs)
fn create_router(state: SharedState) -> Router {
let hc_router = Router::new().route("/", get(hc));
let storage_router = Router::new()
.route("/", get(get_all).post(post).put(put_one).patch(patch_one))
.route("/:id", get(get_one).put(put).patch(patch).delete(delete));
Router::new()
.nest("/_hc", hc_router)
.nest("/:resource", storage_router)
.with_state(state)
}
comments, profile などが :resource にあたる
・/_hc には GET
・/:resource には GET, POST, PUT, PATCH
・/:resource/:id には GET, PUT, PATCH, DELETE
File: storage.json
{
"comments": [
{
"id": 1,
"post_id": "01J7BAKH37HPG116Z
"text": "a comment"
},
{
"id": 2,
"post_id": "01J7BAKH37HPG116Z
"text": "another comment"
}
],
"friends": [],
"posts": [
{
"id": "01J7BAKH37HPG116ZRRFKHB
"title": "first post",
"views": 100
},
{
"id": "01J7BAKH37GE8B688PT4RC
"title": "second post",
"views": 10
}
],
"profile": {
"id": "01J7BAQE1GMD78FN3J0FJCNS8
"name": "mocks"
}
}
USING AXUM
State の定義 (/src/server/state.rs)
State とは アプリケーションの状態管理に関連する概念 です。
アプリケーション全体で共有される状態(例えばデータベース接続や設定情報など)を
管理するためのメカニズムです。
use crate::storage::Storage;
use std::sync::{Arc, Mutex};
pub type SharedState = Arc<Mutex<AppState>>;
pub struct AppState {
pub storage: Storage,
}
impl AppState {
pub fn new(storage: Storage) -> SharedState {
Arc::new(Mutex::new(AppState { storage }))
}
}
examples では
Arc<RwLock<T>> を使っている
USING AXUM
FromRequest トレイトでバリデーション (/src/error.rs)
リクエストボディの検証を追加している
・serde_json の Object 型かどうか
・id と言うキーを持っているか
#[derive(Debug, Clone, Default)]
pub struct PayloadWithId(pub Value);
#[async_trait]
impl<S> FromRequest<S> for PayloadWithId
where
S: Send + Sync,
Json<Value>: FromRequest<S, Rejection = JsonRejection>,
{
type Rejection = (StatusCode, Json<Value>);
async fn from_request(req: axum::extract::Request, state: &S) -> Result<Self, Self::Rejection> {
let Json(value) = Json::<Value>::from_request(req, state)
.await
.map_err(|e| to_rejection(&e.to_string()))?;
if !value.is_object() {
return Err(to_rejection(INVALID_JSON_REQUEST));
}
// ID is required for updates
if value.get("id").is_none() {
return Err(to_rejection("ID is required for creation or update."));
}
Ok(PayloadWithId(value))
}
}
examples では
validator クレートを使っている
USING AXUM axum で実装してみて。 とても実装しやすい。シンプルでクセがない。 ・Tokio チームが開発していると言う 安心感◎ ・トレーシング・ロギングが必要なら tracing で行けそう ・マクロレスなルーティングでわかりやすい
USING AXUM 今日のまとめ。 Rust はいいぞ! axum はいいぞ!
AS A SIDE NOTE... 余談ですが... Rust にコントリビュートできそうです! rust-lang/rust #131235 Rename NestedMetaItem to MetaItemInner 14 comments 0 reviews 43 files +186 -191 codemountains • October 4, 2024 2 commits (リネームするだけの簡単なお仕事)
GOOD BYE. THANK YOU!