2018.02.13

【Techの道も一歩から】第8回「API GatewayとAWS Lambda PythonでAPI開発」Vol. 3:エラー処理

こんにちは。DSOC R&Dグループの高橋寛治です。

WebAPIの勉強をしなければ! と感じる機会があり、思い立ったが吉日と、早速書籍『Web API: The Good Parts』を購入して学んでいるところです。

シリーズ3回目となる今回は、AWS Lambdaを利用したAPI開発の際のエラー処理について紹介します。

APIに求められるエラー処理

WebAPIを簡単に説明すると、WebAPIとはHTTPプロトコルにより提供されるAPIです。一般的にJSONが、クライアントとサーバー間で受け渡しされます。

どのようなエラー処理が必要になるかを考えてみます。

例えば、無効なJSONがリクエストされた際に、どのような情報があるといいでしょうか。

GithubのAPIの例ですと、HTTPステータスコードは400 Bad Requestを返し、JSONのmessageにエラーの詳細を記述しています(以下のコードは、Githubのマニュアルから引用しています)。

HTTP/1.1 400 Bad Request
Content-Length: 35

{"message": "Problems parsing JSON"}

HTTPステータスコードで、リクエスト内容にエラーの要因があることが分かり、messageを読むことで無効なJSONを投げたことが原因であると分かります。

HTTPステータスコードとmessageの役割を掘り下げると、HTTPステータスコードはクライアントのプログラムが受け取り、次の処理を定めるものと考えられます。それに対して、messageは、開発者が読み解くことのできるものとなります。

さまざまなWebAPIのエラー表現をまとめた素晴らしい記事がQiitaにありますので、先人の力を借りて、良いAPIを設計しましょう。

今回、作成するAPIは以下の仕様としました。

  • HTTPステータスコード
    • 200:成功
    • 404:無効なリクエスト
    • 500:内部エラー発生
  • JSONの形式
{
    "message": "メッセージ",
    "hogehogeResult": [何らかの結果]
}

AWS LambdaとAPI Gatewayでどのようにエラー処理を行うか

API Gatewayでは、Lambda関数が送出する例外に応じて、HTTPステータスコードおよびメッセージを設定できます。

以下のスクリーンショットは、API GatewayのPOSTエンドポイントの管理画面です。

api gateway 1280x466 - 【Techの道も一歩から】第8回「API GatewayとAWS Lambda PythonでAPI開発」Vol. 3:エラー処理

対応付けたいHTTPステータスコードの定義は、メソッドレスポンスで行います。

以下の図は、メソッドレスポンスに400, 404, 500を追加したものです(200はデフォルトです)。

44d29d2792be3bad9a9ed98c7bbb20d6 - 【Techの道も一歩から】第8回「API GatewayとAWS Lambda PythonでAPI開発」Vol. 3:エラー処理

次に、設定したHTTPステータスコードをLambdaの例外にひも付けます。ひも付けは、Lambdaエラーに対して正規表現マッチングを行います。

具体的には、PythonのExceptionクラスの__str__に設定された文字列表現が、正規表現の対象となります。

以下の図は、HTTPステータスコード404に対して、^[404:NotFound]$をひも付けたものです。正規表現のエスケープに注意してください。

0e4aae38a1f5494592124a884058f7cb 1145x720 - 【Techの道も一歩から】第8回「API GatewayとAWS Lambda PythonでAPI開発」Vol. 3:エラー処理

Pythonのコードでは、Exceptionクラスを継承し、__str__に対して、^[404:NotFound]$を記述します。

class NotFoundError(Exception):
    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return "[404:NotFound]"

ベストプラクティスかどうかは分かりませんが、404や500といった接頭辞を__str__に記述することで、正規表現として取り扱いやすく設定しました。

Lambda関数は実行時間の制限もあるため、制限時間を超える想定外の処理はエラーとしてハンドリングさせてみます。

正規表現は、(^[500:InternalServerError].*|.*Task timed out.*)となり、[500:InternalServerError]から始まるエラー、もしくはTask timed out.が含まれるエラーの場合に500エラーとなります。

戻り値のJSONは、本文マッピングテンプレートにより設定可能です。本文マッピングテンプレートのリファレンスを参考に設定します。Javascriptと似た記法のテンプレートになります。本文マッピングテンプレートを空にした場合、Lambdaのレスポンスがそのまま通過します。

以下のマッピングテンプレートでは、Lambdaから送出されたエラーメッセージ(Exceptionの__str__)を”message”にひも付け、結果は空を返します。

{
    "message" : "$input.path('$.errorMessage')",
    "hogehogeResult": []
}

Lambda側のエラーをどう設計していくか

ここまでで、API GatewayとAWS Lambdaを連携させた場合のエラーの取り扱いについて、流れが分かったかと思います。

私も悩んでいますが、正規表現マッチは1つのHTTPステータスコードに対して、1行の正規表現しか記述できないため、HTTPステータスコードのひも付けという枠組の中で、どのようにエラー処理を設計するかが難しいです(もちろん|により複数の正規表現を含めることは可能ですが、可読性は落ちます)。

コードで404や500と分かるような文字列を含めるとしても、どのレイヤーでHTTPステータスコードとひもづくエラーを送出するかの設計が難しいです。何かのモジュールを用いた場合は、そのモジュールが送出する例外をCatchして、__str__を設定する必要があります。

今、開発しているAPIでは、一つ一つの例外クラスに、[404:NotFound][500:InternalServerError]といった文字列を__str__にセットしています。404系エラーのクラスを作り、子クラスで具体的なエラーを定義するのも一つの手かもしれません。

もしかすると、たくさんの例外を考慮するようなLambda関数の利用が、そもそも設計ミスかもしれません。とはいえ、手軽にサーバーレスAPIが作れるため、使わないわけにはいきません。

使いやすいAPIを目指して

使いやすく、そして分かりやすいAPIを提供する上で、適切なメッセージやエラーコードを返すことは重要です。

今回は、WebAPIのエラーについて今一度考えた後に、API GatewayとAWS Lambdaを用いた際のAPIエラーの取り扱いについて紹介しました。

次回はデプロイについて紹介します。

執筆者プロフィール

過去記事

▼第7回
「API GatewayとAWS Lambda PythonでAPI開発」Vol. 2:ローカルでの開発環境構築

▼第6回
「API GatewayとAWS Lambda PythonでAPI開発」Vol. 1:API GatewayとAWS Lambdaを知る

▼第5回
快適なシェル環境の再構築を自動化する

▼第4回
第16回情報科学技術フォーラム(FIT2017)で登壇

▼第3回
第11回テキストアナリティクス・シンポジウム

▼第2回
R&D論文読み会勉強会

▼第1回
言語処理100本ノック勉強会

text:DSOC R&Dグループ 高橋寛治