API Gateway に統合されたバックエンドからの JSON 形式のレスポンスを API Gateway だけで編集する

2023.04.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

いわさです。

何かしらのバックエンドと統合された API Gateway を持っている場合に、統合されたバックエンドからのレスポンス内容をちょっと加工したい時があります。

次のように API Gateway の後ろに Lambda が配置されていて、Lambda がレスポンス内容を生成している場合は Lambda でどうにでも出来そうなのはイメージしやすいと思います。

API Gateway ではマッピングテンプレートという概念があるので、簡単な加工であれば次のように Lambda 以外のバックエンドが統合されている場合でも実はレスポンス内容を編集出来たりします。
CloudFront でオリジン Body の編集をしたいけど制限があって出来ずに悔しい思いをした方もいると思うのですが、API Gateway は出来ます。

今回は JSON 形式でレスポンスする API を統合した API Gateway でレスポンス内容を編集してみたので方法を紹介します。

統合するバックエンドを用意

API Gateway のバックエンドを用意します。
今回は HTTP 統合を使うので HTTP リクエストが処理出来れば何でも良いです。

私は API Gateway でモックを用意しました。
次のような静的 JSON をレスポンスするようにしています。

まずは直接実行してみます。

% curl https://gnfgyf4w83.execute-api.ap-northeast-1.amazonaws.com/hoge   
{
  "id": 123,
  "name": null,
  "email": "example@example.com",
  "phoneNumbers": [
    { "label": "home", "number": "123-456-7890" },
    { "label": "work", "number": "456-789-0123" }
  ],
  "balance": 1234.56,
  "isActive": true,
  "favoriteFruits": ["apple", "banana"],
  "lastLogin": null
}

統合リクエストを設定

続いて、作成したバックエンドのフロントにプロキシする API Gateway を作成します。
統合タイプは HTTP で、エンドポイント URL には先程作成したモック API の URL を指定します。

ここではまずは「HTTP プロキシ統合の使用」を ON にします。

この状態だけでデプロイした API Gateway のレスポンスを観察してみましょう。

% curl https://sqox8bxw4f.execute-api.ap-northeast-1.amazonaws.com/hoge
{
  "id": 123,
  "name": null,
  "email": "example@example.com",
  "phoneNumbers": [
    { "label": "home", "number": "123-456-7890" },
    { "label": "work", "number": "456-789-0123" }
  ],
  "balance": 1234.56,
  "isActive": true,
  "favoriteFruits": ["apple", "banana"],
  "lastLogin": null
}

先程と同じレスポンスが取得出来ていますね。
このようにただプロキシするだけの API を作成するだけならこれでほぼ終わりです。

統合レスポンスを設定

ここからさらにレスポンス内容を編集したい場合は統合レスポンスを設定する形になります。
ただし先程は「HTTP プロキシ統合の使用」を有効にしていたので、この統合レスポンス部分を良い感じに構成してくれています。
一方でカスタマイズが出来ません。

ここではプロキシするだけじゃなくて、編集を行いたいので「HTTP プロキシ統合の使用」をオフにします。

そうすると次のように統合レスポンスが設定出来るようになります。

ここでは一旦バックエンドのモックと同じように静的な JSON レスポンスを設定してみます。
ほぼ同じではあるのですが、少しだけフィールドを削ったりして差分を発生させています。

リクエストしてみます。

% curl https://sqox8bxw4f.execute-api.ap-northeast-1.amazonaws.com/hoge
{
  "id": 123,
  "email": "example@example.com",
  "phoneNumbers": [
    { "label": "home", "number": "123-456-7890" },
    { "label": "work", "number": "456-789-0123" }
  ],
  "balance": 1234.56,
  "isActive": true,
  "lastLogin": null
}

統合レスポンスのマッピングテンプレートで定義した静的な JSON レスポンスが取得されていますね。

ちなみにこのマッピングテンプレートは複数定義することが可能で、レスポンスペイロードの場合は Accept ヘッダーの MIME タイプをキーにしています。

マッピングテンプレートを使う(新規レスポンス生成)

ここからが本題ですね。
API Gateway は要は統合レスポンスでマッピングさせる仕組みを使って最終的なレスポンスを生成しています。
先程試したように統合されたバックエンドのレスポンスは無視して、API Gateway 側でマッピングテンプレートを使ってレスポンスを一から組み立てることも出来るわけです。

さらに、マッピングテンプレートでは VTL (Velocity Template Language) を使うことで動的なレスポンス生成やマッピングを行うことも出来ます。

今回は VTL を使ってレスポンス内容を編集してみたいと思います。

まず、VTL で各データにアクセスするにあたって、API Gateway でサポートされているパラメータを把握しておく必要があります。
以下の公式ドキュメントを眺めましょう。

統合レスポンスの場合は$inputで統合先から取得されたレスポンスにアクセスが出来ます。
ここでは先程のレスポンスの ID プロパティを 2 倍の数値にするために次のようなテンプレートを作成しました。

#set($inputRoot = $input.path('$'))
#set($hogeid = $inputRoot.id * 2)
{
  "id": $hogeid,
  "email": "example@example.com",
  "phoneNumbers": [
    { "label": "home", "number": "123-456-7890" },
    { "label": "work", "number": "456-789-0123" }
  ],
  "balance": 1234.56,
  "isActive": true,
  "lastLogin": null
}

リクエストを送信します。

% curl https://sqox8bxw4f.execute-api.ap-northeast-1.amazonaws.com/hoge
{
  "id": 246,
  "email": "example@example.com",
  "phoneNumbers": [
    { "label": "home", "number": "123-456-7890" },
    { "label": "work", "number": "456-789-0123" }
  ],
  "balance": 1234.56,
  "isActive": true,
  "lastLogin": null
}

良いですね。
独自に出力した値がレスポンスで使用されています。

マッピングテンプレートを使う(オリジンレスポンスを上書き)

先程は JSON の構造を新規に作成し、一部のプロパティにオリジンプロパティの値を使用しました。
次は、ベースはオリジンレスポンスで、一部プロパティのみ編集してみたいと思います。

先程のように$input.path()で取得すると VTL 用のキーバリュー型のオブジェクトが取得されます。

#set($inputRoot = $input.path('$'))
#set($inputRoot.origin_id = $inputRoot.id)
#set($inputRoot.id = $inputRoot.id * 2)
$inputRoot

そのため上記のようにそのままレスポンスに設定してしまうと次のように非 JSON 構造で出力されてしまいます。

# プロキシのレスポンス
% curl https://sqox8bxw4f.execute-api.ap-northeast-1.amazonaws.com/hoge
{id=123, name=null, email=example@example.com, phoneNumbers=[{"label":"home","number":"123-456-7890"},{"label":"work","number":"456-789-0123"}], balance=1234.56, isActive=true, favoriteFruits=["apple","banana"], lastLogin=null}

# オリジンのレスポンス
% curl https://gnfgyf4w83.execute-api.ap-northeast-1.amazonaws.com/hoge     
{
  "id": 123,
  "name": null,
  "email": "example@example.com",
  "phoneNumbers": [
    { "label": "home", "number": "123-456-7890" },
    { "label": "work", "number": "456-789-0123" }
  ],
  "balance": 1234.56,
  "isActive": true,
  "favoriteFruits": ["apple", "banana"],
  "lastLogin": null
}

$input.json で出力

ここでは$input.pathが操作しつつ、$input.jsonで出力することで編集後のオブジェクトを JSON 形式で出力出来ます。

#set($inputRoot = $input.path('$'))
#set($inputRoot.origin_id = $inputRoot.id)
#set($inputRoot.id = $inputRoot.id * 2)
$input.json('$')
% curl https://sqox8bxw4f.execute-api.ap-northeast-1.amazonaws.com/hoge | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   254  100   254    0     0   1748      0 --:--:-- --:--:-- --:--:--  1801
{
  "id": 246,
  "name": null,
  "email": "example@example.com",
  "phoneNumbers": [
    {
      "label": "home",
      "number": "123-456-7890"
    },
    {
      "label": "work",
      "number": "456-789-0123"
    }
  ],
  "balance": 1234.56,
  "isActive": true,
  "favoriteFruits": [
    "apple",
    "banana"
  ],
  "lastLogin": null,
  "origin_id": 123
}

うまく編集した内容でレスポンスを生成することが出来ました。

利用料金

マッピングテンプレートは標準の API Gateway 利用料金に含まれています。
そのため、加工のためだけに Lambda を挟んでいるような場合であれば、マッピングテンプレートに置き換えることで Lambda のコストを削減出来る可能性があります。

あまりコスト削減の観点でマッピングテンプレートを考えたことは無かったのですが、これはちょっと興味深いですね。

さいごに

本日は API Gateway に統合されたバックエンドからの JSON 形式のレスポンスを API Gateway だけで編集してみました。

API Gateway を Lambda や様々なバックエンドのフロントに配置するアーキテクチャーは非常に多いと思いますが、マッピングテンプレートを活用出来ていないケースも多いのではと思っています。
不要な中継用の Lambda を削減出来る可能性もあるので、マッピングテンプレートで実現出来るかも是非検討してみてください。