1. HOME
  2. ブログ
  3. IT技術
  4. Rust/ActixWebでRestAPIのルーティング、エラーハンドリングなどなど

Rust/ActixWebでRestAPIのルーティング、エラーハンドリングなどなど

はじめに

最近Rustのお勉強してます。
C/C++並に早く、かつ安全性が高いと言われているあれです。
色々な用途で利用されてますが、バックエンドを生業としている身としてはWebフレームワークが気になりまして。

  • ・ActixWeb
  • ・Rocket
  • ・Axum

上記が人気みたいですね。

で、その中からActixWebを使用し、簡単なRestAPI作ってみようかと思いました。採用した理由はだいたい1番目に紹介されてたので。吟味はしてないです、許してください。

いきなりですが感想

ドキュメント ありますが、これを読めばすぐにビジネスロジック書けます、な感じでは無かったです。
フレームワークをベースにバリデータやロギングなどは他のライブラリ(Rsutではクレートと言う)を追加して作り込んでいく感じでした。

他のフレームワーク(Axum)もそんな感じ。

うーん、覚えることたくさん?

ともあれ導入部やってみました。

  • 動作環境
    • ・Mac(Intel)
      • ・vscode / devcontainer でよしなに
    • ・Rust: 1.75.0
    • ・actix-web: 4(4.4.1?)

HelloWorld

プロジェクト作成して、フレームワーク追加して( > cargo add actix-web )、main.rsに ドキュメント(getting-started) の記載内容を記述します。

> cargo run で実行し、 http://127.0.0.1:8080  へのアクセスでHello world!確認。

エントリポイント(mainメソッド)ではルーティングの定義とサーバー起動を実施、
ルーティングで設定されたメソッドの属性/アトリビュートでHTTPメソッドとURLパスを記載するみたいですね。

シンプル。

パスパラメータ

何らかのIDなど一意なリソースの取得等でおなじみのパスパラメータの定義。

{param1}actix_web::web::Path<u32> のu32型(符号無しな整数型)として定義/紐付け。u32型以外(文字列等)でリクエストすると404エラーとなる。

複数定義もできる。その場合はタプルで受け取る。

スラッシュを含めたURLパラメータ(というかパス)をそのまま受け取りたい、なんてこともできちゃう。

{param:.*} と正規表現(全ての値)指定するだけ。
これで例えば http://localhost:8080/path3/do/re/mi へアクセスすると、paramには do/re/mi が設定される。
素敵!需要は無さそうだけど。

Jsonリクエスト/レスポンス

RestApiなのでJson形式でリクエスト/レスポンスしたい。
という時に利用するのが serde というライブラリ。
serialize/deseliarize、略してserdeみたいな?しかし読み方が分からない。素直に読めばサーデとかなんでしょうけどセルデやシリデもあるかもしれない、とか考え出すとおちおち眠れない。Rustって読みが難しいワード多い気がするですよね。deriveとかもそう。道草。

serdeライブラリを追加して( > cargo add serde --features derive)、リクエスト/レスポンスのstruct/構造体を作成する。

リクエストはデシリアライズして、レスポンスはシリアライズする。

つづいて処理を定義する。

actix_web::web::Json<JsonReq> でJson形式のリクエストをリクエスト構造体に変換してくれる。
非Json形式や項目指定に不備がある場合にリクエストと400エラーとなる。
また、HttpResponse::Ok().json(JsonRes {〜 でレスポンス構造体をJson形式に変換してくれる。

Json形式の取扱もシンプルですね。

エラーハンドリング

ここまではシンプルでしたが問題がありまして。
RestAPIなので正常/エラー問わずレスポンスはJson形式で返したいのですがエラーの際に手を加えないとJson形式ではなくテキストでレスポンスされてしまうんですね。

ので手を加えます。

以下ざっくり手順

  1. ライブラリ追加
  2. Enum(Apiエラー種について)とエラーレスポンスの構造体を定義する
  3. 定義したEnumに対し、actixのResponseError(トレイト)を実装する
  4. エラー判定を追加する

1. ライブラリ追加

エラーハンドリングを簡単に実施するライブラリとなります。

  • thiserror
  • anyhow

(> cargo add thiserror, anyhow)

2. Enumとエラーレスポンスの構造体を定義

ざっくり言うと引数の無い列挙子(NotFound)は #[error("Not Found.")] の()内がエラーメッセージになり、それ以外(ActixWebError、Other)は引数(エラー)の std::fmt::Display の設定値がメッセージになります。

うーん、説明難しい。

3. actixのResponseError(トレイト)を実装

定義したエラーEnum(ApiCustomError)に応じて、ステータスとレスポンス内容を定義してます。
ActixWebErrorで分岐を入れてるですが、これは actix_web::Error が取りうるエラーは単一では無いようでしたのでエラー内容に応じたレスポンスをするための措置となります(404と400を定義してますが足りてないかもですmm)。

4. エラー判定追加

4-1. ルーティング未定義URLへのアクセス

.default_service() 追加が肝ですね。これでルーティング未定義のURLへのアクセス時はJsonでレスポンスされます。

4-2. actix内の処理で発生したエラー

パスパラメータ、Jsonリクエスト/レスポンス時のを変更する

どちらも引数を Result<T, E> に変更してます。actix_web::web::Path<T>actix_web::web::Json<T> で不整合(変換できないなど)がある場合actix_web::Errorとして処理されるので、それを利用して独自エラー(ApiCustomError)に渡してよしなにレスポンス内容を制御する、という感じとなります。

これでエラー時もJson形式でレスポンスするようになりました。しかし、急にややこしい。

今回はここまでmm

まとめ

という感じで覚えることたくさんです。全然ビジネスロジック書くに至らないって言う。
ちなみに導入部の追加の対応/覚えることは下記な感じですかね?

  • バリデータ
  • ログ関連
  • DB関連
  • AOP/ミドルウェア?
  • (みんな大好き)DI関連

機会があれば次回に続きを書こーかなと思います。乞うご期待(?)

おまけ

RustでのDIですが色んな記事見てると サービスロケータ なんじゃ?とか思ったり思わなかったりしてまして。

サービスロケータパターンってゴリゴリのアンチパターンだからちょっとあれだなと思う今日この頃でした。

関連記事

採用情報

\ あの有名サービスに参画!? /

バックエンドエンジニア

\ クリエイティブの最前線 /

フロントエンドエンジニア

\ 世界を変える…! /

Androidエンジニア

\ みんなが使うアプリを創る /

iOSエンジニア