メインコンテンツまでスキップ

HTTP API通信で発生するエラーのハンドリング

Status: Accepted

要約

  • 以下の場合は、ユーザに適切なエラー内容と復帰手順を伝える。
    • HTTP APIから400以上のHTTPステータスコードが返却された場合
    • モバイル端末のネットワークがOFFになっていたり、接続先サーバのポートにアクセスできない場合など
  • リソースの取得時に、一定時間が経過してもHTTP APIから応答がない場合はタイムアウトさせる
  • HTTP API通信でエラーが発生した場合にリトライ処理を実施しない
    • ユーザ自身が操作の再試行を選択できるUIを提供する

コンテキスト

HTTP APIはHTTPステータスコードが400以上の場合に処理が失敗したことを表します。また、モバイル端末のネットワークがOFFになっている場合などは、HTTP APIとの通信ができません。

ここでは、これらのエラーをどこでハンドリングしてどのように対処をするかを検討します。また、エラーが発生した場合のリトライ処理やHTTP API通信のタイムアウト方法についても検討していきます。

なお、HTTP API通信に使用するライブラリとして、axios1を利用する前提とします。また、HTTP API通信のエラーハンドリングには、React Query1を利用する前提とします。

議論

HTTPステータスコードが400以上のエラーハンドリング(検討開始時点)

HTTP APIとの通信時には、処理が失敗した場合にHTTPステータスコードが400以上のレスポンスが返却されます。当初の議論では、以下のような方式にしようと考えていました。

  • SantokuAppでは特定のHTTPステータスコードの場合、ユーザに適切なエラー内容と復帰手順を伝える
  • それ以外のHTTPステータスコードの場合は、想定外のエラーが発生したことをスナックバーに表示する
HTTPステータスコード意味ハンドリング箇所
400BadRequest - リクエストとして送るペイロードがバリデーションエラーになった場合など個別
401Unauthorized - ログイン資格情報(HTTP APIが生成したセッション)の期限が切れた場合共通
404Not found - リソースが見つからなかった場合個別
412Precondition Failed - 操作しているモバイルアプリケーションが強制アップデート対象の場合共通
429Too Many Requests - クライアントから大量のリクエストが送信された場合共通
503Service Unavailable - HTTP APIがシステムメンテナンスで止まっている場合共通
その他想定外のエラーが発生した場合共通

しかし、いくつかのHTTPステータスコードで以下のような疑問が挙がりました。

403 - Forbiddenは想定外のエラーなのか

現時点では、リソースにアクセス制限を設けないため403が返却される想定はありません。ただし、使用するミドルウェアやクラウドサービスによっては、HTTP APIのリソースに対するアクセス制限以外にも403が返却される可能性はあります。その点については、使用するミドルウェアやクラウドサービスの設計後に追加します。

504 - Gateway Timeoutは想定外のエラーなのか

想定外のエラーとも言えそうですが、時間をおいてから再操作することで正常なレスポンスが返却される場合もあると思われるので、それがわかるような内容をユーザに伝えます。

【補足】有効期限が切れたログイン資格情報の更新方法

Cookieベースの認証

SantokuAppでは、認証方式としてCookieベースの認証方式を採用しています。Cookieベースの認証方式では、ログイン処理後にHTTP APIが払い出したセッションIDを、Cookie経由で受け渡すことにより認証状態を判定します。

セッションの有効期限はHTTP APIが稼働しているアプリケーションサーバで管理されています。モバイルアプリではHTTP APIから返却されるHTTPステータスコードが401の場合に、セッションの有効期限が切れたと判定します。セッションの有効期限が切れた場合は、アカウントIDと端末IDを利用して認証リクエストをHTTP APIに送信して、新しいセッションを発行してもらいます。

セッションの有効期限が切れた場合のフローは以下になります。

  1. 有効期限が切れたセッションIDを使用してHTTP APIにリクエストを送信する
  2. HTTP APIからHTTPステータスコードが401で返却される
  3. HTTPステータスコードが401の場合、アカウントIDと端末IDを利用して認証リクエストをログイン用のHTTP APIに送信する
  4. HTTP APIは、新しいセッションIDをCookieに設定してレスポンスを返却する
  5. SantokuAppは、Cookieに設定されているセッションIDを使用して、HTTP APIに再度リクエストを送信する

OpenID Connectを使用した認証

一方で、OpenID Connectを採用した場合は、認証状態の判定にIDトークンやアクセストークンを利用します。通常IDトークンやアクセストークンには有効期限が含まれており、HTTP APIにアクセスしなくてもモバイルアプリ内で有効期限の判定が可能です。そのため、HTTP APIにリクエストを送信する前に有効期限を判定して、有効期限が切れている場合はリフレッシュトークンを使用して新しいIDトークンや、アクセストークンを発行してもらうことが可能です。

IDトークンやアクセストークンの有効期限が切れた場合のフローは以下になります。

  1. 認証トークンの期限が切れている、または期限が間近の場合はIDトークンやアクセストークンを破棄する
  2. リフレッシュトークンを利用して、IDトークンやアクセストークンを再取得する
  3. 取得したIDトークンやアクセストークンを使用して、HTTP APIに再度リクエストを送信する

上記の場合だと、HTTPスタータスコードが401で返却されることはなくなり(ログイン処理を除く)、HTTP APIとの通信回数を減らすことができます。

HTTP APIからレスポンスが返却されない場合のエラーハンドリング

モバイル端末のネットワークがOFFになっていたり、接続先サーバのポートにアクセスできない場合などは、HTTP APIからレスポンスが返却されません。これらの場合は、共通でエラーのハンドリングを実施してユーザにエラーの内容を伝えます。

HTTP API通信のタイムアウト

axiosでは、timeoutを設定できます。しかし、axiosのtimeoutはAndroidではコネクションタイムアウトのみに使用されるため、コネクション接続後にサーバの応答が遅い場合などはタイムアウトしません。

そのため、SantokuAppではaxiosのtimeoutを使用せず、CancelTokenを利用してHTTP API通信をキャンセルすることによりタイムアウトを実現します。React Queryを使用した場合は、Query Cancellationが参考になります。

Query Cancellationは、リソースを取得するuseQueryを使用した場合にHTTP API通信をキャンセルする方法です。リソースの登録や更新時に使用するuseMutationではHTTP API通信をキャンセルできません。2

そのため、リソースの登録や更新時にHTTP API通信をキャンセルしたい場合は、axiosのCancel Tokenを使用して独自に実装する必要があります。

ただし、リソースの登録や更新時はHTTP API通信のキャンセルが適切ではない場面もあります。HTTP APIの通信をキャンセルした場合、モバイルアプリではHTTP APIの処理が成功したか失敗したかを把握できません。ユーザには通信のタイムアウトを伝えているにも関わらず、HTTP APIの処理は正常に完了してリソースが登録、更新されている可能性もあります。

ユーザに正確な情報を伝えることができないのは良くないと考えたため、SantokuAppではリソースの取得時のみHTTP API通信のタイムアウト処理を実施します。

HTTP API通信のリトライ(検討開始時点)

当初の議論では、以下のような方式にしようと考えていました。

  • HTTP API通信で発生したエラー内容に応じて、HTTP APIのリクエストを再試行する
  • リトライ間隔は1000msから始まり、リトライ回数に応じて指数関数的に増やす(1000ms、2000ms、4000ms...)
エラー原因リトライ回数
HTTPステータスコードが400リトライしない
HTTPステータスコードが401リトライしない
HTTPステータスコードが404リトライしない
HTTPステータスコードが412リトライしない
HTTPステータスコードが4292回
HTTPステータスコードが503リトライしない
HTTPステータスコードが5042回
HTTPステータスコードが上記以外2回
HTTP APIからレスポンスが返却されない場合2回

しかし、議論を重ねる中で以下のような疑問が挙がりました。

リトライは本当に必要か

HTTP API通信をする場合、HTTP APIからの応答が返ってくるまでは他の操作をできないようにする機能もあります。そういった場合に自動でリトライすると、リトライ数に応じた時間だけユーザは操作できずに待ち続けなければいけません。ユーザはそれをストレスに感じる可能性があります。

そのため、SantokuAppではHTTP API通信でエラーが発生した場合に自動で再試行しない方針とします。代わりに、ユーザ自身の操作でHTTP APIを再試行できるUIを提供することにします。

決定

HTTPステータスコードが400以上のエラーハンドリング

特定のHTTPステータスコードの場合、ユーザに適切なエラー内容と復帰手順を伝えます。それ以外のHTTPステータスコードの場合は、想定外のエラーが発生したことをスナックバーに表示します。

HTTPステータスコードSantokuAppで発生する場面ハンドリング箇所
400BadRequest - リクエストとして送るペイロードがバリデーションエラーになった場合など個別
401Unauthorized - ログイン資格情報(HTTP APIが生成したセッション)の期限が切れた場合共通
404Not found - リソースが見つからなかった場合個別
412Precondition Failed - 操作しているモバイルアプリケーションが強制アップデート対象の場合共通
429Too Many Requests - クライアントから大量のリクエストが送信された場合共通
503Service Unavailable - HTTP APIがシステムメンテナンスで止まっている場合共通
504Gateway Timeout - HTTP APIから時間内に応答がなかった場合共通
その他想定外のエラーが発生した場合共通

HTTP APIからレスポンスが返却されない場合のエラーハンドリング

モバイル端末のネットワークがOFFなどでHTTP APIからレスポンスが返却されない場合は、共通でエラーのハンドリングを実施してユーザにエラーの内容を伝えます。

HTTP API通信のタイムアウト

リソースを取得する場合は、一定時間が経過してもHTTP APIから応答がない場合はタイムアウトさせます。リソースの登録や更新をする場合はタイムアウトしません。

HTTP API通信のリトライ

HTTP API通信でエラーが発生した場合にリトライ処理を実施しません。代わりに、ユーザ自身がHTTP APIの再試行を選択できるUIを提供することにします。


  1. 利用するライブラリをどのように議論して決めたかは、HTTP通信で使用するライブラリを参照してください。
  2. useMutationを使用した場合のキャンセル機能については、React QueryのGitHubリポジトリでディスカッションされています。