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ステータスコード | 意味 | ハンドリング箇所 |
---|---|---|
400 | BadRequest - リクエストとして送るペイロードがバリデーションエラーになった場合など | 個別 |
401 | Unauthorized - ログイン資格情報(HTTP APIが生成したセッション)の期限が切れた場合 | 共通 |
404 | Not found - リソースが見つからなかった場合 | 個別 |
412 | Precondition Failed - 操作しているモバイルアプリケーションが強制アップデート対象の場合 | 共通 |
429 | Too Many Requests - クライアントから大量のリクエストが送信された場合 | 共通 |
503 | Service 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に送信して、新しいセッションを発行してもらいます。
セッションの有効期限が切れた場合のフローは以下になります。
- 有効期限が切れたセッションIDを使用してHTTP APIにリクエストを送信する
- HTTP APIからHTTPステータスコードが401で返却される
- HTTPステータスコードが401の場合、アカウントIDと端末IDを利用して認証リクエストをログイン用のHTTP APIに送信する
- HTTP APIは、新しいセッションIDをCookieに設定してレスポンスを返却する
- SantokuAppは、Cookieに設定されているセッションIDを使用して、HTTP APIに再度リクエストを送信する
OpenID Connectを使用した認証
一方で、OpenID Connectを採用した場合は、認証状態の判定にIDトークンやアクセストークンを利用します。通常IDトークンやアクセストークンには有効期限が含まれており、HTTP APIにアクセスしなくてもモバイルアプリ内で有効期限の判定が可能です。そのため、HTTP APIにリクエストを送信する前に有効期限を判定して、有効期限が切れている場合はリフレッシュトークンを使用して新しいIDトークンや、アクセストークンを発行してもらうことが可能です。
IDトークンやアクセストークンの有効期限が切れた場合のフローは以下になります。
- 認証トークンの期限が切れている、または期限が間近の場合はIDトークンやアクセストークンを破棄する
- リフレッシュトークンを利用して、IDトークンやアクセストークンを再取得する
- 取得した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ステータスコードが429 | 2回 |
HTTPステータスコードが503 | リトライしない |
HTTPステータスコードが504 | 2回 |
HTTPステータスコードが上記以外 | 2回 |
HTTP APIからレスポンスが返却されない場合 | 2回 |
しかし、議論を重ねる中で以下のような疑問が挙がりました。
リトライは本当に必要か
HTTP API通信をする場合、HTTP APIからの応答が返ってくるまでは他の操作をできないようにする機能もあります。そういった場合に自動でリトライすると、リトライ数に応じた時間だけユーザは操作できずに待ち続けなければいけません。ユーザはそれをストレスに感じる可能性があります。
そのため、SantokuAppではHTTP API通信でエラーが発生した場合に自動で再試行しない方針とします。代わりに、ユーザ自身の操作でHTTP APIを再試行できるUIを提供することにします。
決定
HTTPステータスコードが400以上のエラーハンドリング
特定のHTTPステータスコードの場合、ユーザに適切なエラー内容と復帰手順を伝えます。それ以外のHTTPステータスコードの場合は、想定外のエラーが発生したことをスナックバーに表示します。
HTTPステータスコード | SantokuAppで発生する場面 | ハンドリング箇所 |
---|---|---|
400 | BadRequest - リクエストとして送るペイロードがバリデーションエラーになった場合など | 個別 |
401 | Unauthorized - ログイン資格情報(HTTP APIが生成したセッション)の期限が切れた場合 | 共通 |
404 | Not found - リソースが見つからなかった場合 | 個別 |
412 | Precondition Failed - 操作しているモバイルアプリケーションが強制アップデート対象の場合 | 共通 |
429 | Too Many Requests - クライアントから大量のリクエストが送信された場合 | 共通 |
503 | Service Unavailable - HTTP APIがシステムメンテナンスで止まっている場合 | 共通 |
504 | Gateway Timeout - HTTP APIから時間内に応答がなかった場合 | 共通 |
その他 | 想定外のエラーが発生した場合 | 共通 |
HTTP APIからレスポンスが返却されない場合のエラーハンドリング
モバイル端末のネットワークがOFFなどでHTTP APIからレスポンスが返却されない場合は、共通でエラーのハンドリングを実施してユーザにエラーの内容を伝えます。
HTTP API通信のタイムアウト
リソースを取得する場合は、一定時間が経過してもHTTP APIから応答がない場合はタイムアウトさせます。リソースの登録や更新をする場合はタイムアウトしません。
HTTP API通信のリトライ
HTTP API通信でエラーが発生した場合にリトライ処理を実施しません。代わりに、ユーザ自身がHTTP APIの再試行を選択できるUIを提供することにします。
- 利用するライブラリをどのように議論して決めたかは、HTTP通信で使用するライブラリを参照してください。↩
useMutation
を使用した場合のキャンセル機能については、React QueryのGitHubリポジトリでディスカッションされています。↩