API Gatewayを2つつないでJSON以外の値をJSON Schemaでバリデーションしてみた

2017.09.01

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

API GatewayではJSON schemaによるrequest bodyの検証ができますが、JSON以外の形式で送信される値は検証できません。 Lambdaなどロジックのかけるサービスに値を渡せば複雑な検証や処理ができますが、単純な値の検証だけをしたかったので2つのAPI Gatewayをつないで値を検証してみました。

概要

以下の流れで値を検証します。

スクリーンショット 2017-09-01 13.11.23

  1. 値を受け取る(API Gateway1)
  2. 値をJSONに変換しAPI Gateway2に送信(API Gateway1)
  3. 変換された値を受け取り検証する(API Gateway2)
  4. 最終的に送信したいエンドポイントにJsonの値を送信(API Gateway2)

また、本記事ではJSONの整形にjqを使用していますが、設定自体にはjqは不要です。 jqをインストールしていない環境で検証する際は、コマンドから| jqで整形している部分を削除してください。

やってみた

API Gateway1の設定

API Gateway1では統合リクエストで受け取ったデータをJSONに変換するための設定をします。今回はフォームから送信されたデータを対象にします。

APIを作成し、POSTメソッドをセットアップする

APIを作成しPOSTメソッドを定義します。統合タイプとエンドポイントを設定します。 スクリーンショット 2017-09-01 13.20.26

設定値は以下です。

  • 統合タイプ:HTTP
  • エンドポイント:https://postman-echo.com/post

エンドポイントは最終的にAPI Gateway2を指定しますが、変換された値を確認するためhttps://postman-echo.com/postを指定します。 https://postman-echo.com/postはPostman Echoというツールのエンドポイントで、送信された値を返却してくれます。

Postman Echoについての詳細はこちら

統合リクエストを設定する

統合リクエストにて、送信された値をJSONに変換するための設定をします。

まずHTTPヘッダーを設定します。 スクリーンショット 2017-09-01 13.31.02

設定値は以下です。

  • 名前:Content-Type
  • マッピング元:'application/json'

※マッピング元はシングルクオートで括る必要があります。

次に本文マッピングテンプレートを指定します。

スクリーンショット 2017-09-01 13.31.02

application/x-www-form-urlencodedのマッピングテンプレートを定義し、以下の値を設定します。

{
#set( $tmpstr = $input.body )
#foreach( $keyandvaluestr in $tmpstr.split( '&' ) )
#set( $keyandvaluearray = $keyandvaluestr.split( '=' ) )
        "$keyandvaluearray[0]" : "$keyandvaluearray[1]"#if( $foreach.hasNext ),#end
#end
}

この値は、実際にリクエストされる際のContent-Type及びrequest bodyによって変更が必要です。 今回はフォームから送信される値を前提に設定してみました。

設定値はこちらの記事を参考にしています。

今回設定するマッピングテンプレートは、送信される値を全て文字列型としてJSONに変換しています。データ型が全て文字列になってしまうため、もし数値などの型で値を使用する場合は、キーの名前に対して適切な形式でJSONに変換できるよう、マッピングテンプレートを修正する必要があります。

ここまで設定できたらAPIをデプロイし、念のため変換されたデータを確認してみます。

$ curl --request POST --url https://xxxxxxxx.execute-api.us-west-2.amazonaws.com/test \
--header 'content-type: application/x-www-form-urlencoded' \
--data 'testkey1=testvalue1&testkey2=testvalue2' | jq
{
  "args": {},
  "data": {
    "testkey1": "testvalue1",
    "testkey2": "testvalue2"
  },
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "content-length": "70",
    "accept": "application/x-www-form-urlencoded",
    "content-type": "application/json",
    "user-agent": "AmazonAPIGateway_xxxxxxxx",
    "x-amzn-apigateway-api-id": "xxxxxxxx",
    "x-amzn-trace-id": "Root=1-59a838b3-8fa42a1c373a23daf5ac1ccd",
    "x-forwarded-port": "443",
    "x-forwarded-proto": "https"
  },
  "json": {
    "testkey1": "testvalue1",
    "testkey2": "testvalue2"
  },
  "url": "https://postman-echo.com/post"
}

dataの項目で、入力値がJSON形式に変換されていることが確認できます。また、headersでcontent-typeがapplication/jsonになっていることが確認できます。

API Gateway2の設定

API Gateway2ではAPI Gateway1から送られたJSONの値をJSON schemaにより検証し、問題がなければエンドポイントに値を渡します。問題がある場合、エンドポイントに値を渡さずエラーレスポンスを返却します。

APIを作成し、POSTメソッドをセットアップする

API Gateway1と全く同様です。

APIを作成しPOSTメソッドを定義します。統合タイプとエンドポイントを設定します。 スクリーンショット 2017-09-01 13.20.26

モデルを作成する

実際に送信されるデータのモデルを定義します。

スクリーンショット 2017-09-01 1.30.50

コンテンツタイプをapplication/jsonに設定し、モデルのスキーマを以下に設定します。

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "TestaModel",
  "type": "object",
  "properties": {
    "count": { "pattern": "^([0-9]|[1-9][0-9]+)$" }
  }
}

countが数値であることを検証しています。前述のマッピングテンプレートの設定のため、全ての値が文字列となるので型ではなく正規表現による検証をしています。

メソッドリクエストを設定する

作成したモデルをメソッドリクエストのリクエスト本文に設定します。

スクリーンショット 2017-09-01 17.10.19

リクエストの検証を本文の検証に設定し、コンテンツタイプapplication/jsonのモデルに先ほど作成したモデルを設定します。

ここまで設定できたらAPIをデプロイし、定義した通りに検証が行われているか確認してみます。

$ curl --request POST --url https://xxxxxxxx.execute-api.us-west-2.amazonaws.com/test \
--header 'content-type: application/json' \
--data '{"count":"1"}' | jq
{
  "args": {},
  "data": {
    "count": "1"
  },
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "content-length": "13",
    "accept": "application/json",
    "content-type": "application/json",
    "user-agent": "AmazonAPIGateway_xxxxxxxx",
    "x-amzn-apigateway-api-id": "xxxxxxxx",
    "x-amzn-trace-id": "Root=1-59a91819-f488d5b0406891a760aa9372",
    "x-forwarded-port": "443",
    "x-forwarded-proto": "https"
  },
  "json": {
    "count": "1"
  },
  "url": "https://postman-echo.com/post"
}

countが数値の場合はPostman Echoから応答が返ってきます。

$ curl --request POST --url https://xxxxxxxx.execute-api.us-west-2.amazonaws.com/test \
--header 'content-type: application/json' --data '{"count":"abc"}' | jq
{
  "message": "Invalid request body"
}

countが数値でない場合は、メッセージ「Invalid request body」が返ってきます。

API Gateway1をAPI Gateway2につなぐ

最後にAPI Gateway1の統合リクエストの設定で、エンドポイントURLをAPI Gateway2のエンドポイントに変更します。

スクリーンショット 2017-09-01 17.24.57

再度デプロイしたら設定完了です。

実際にAPI Gateway1のエンドポイントにapplication/x-www-form-urlencodedのデータを送って検証が行われることを確認します。

$ curl --request POST --url https://xxxxxxx.execute-api.us-west-2.amazonaws.com/test \
--header 'content-type: application/x-www-form-urlencoded' --data 'count=10' | jq
{
  "args": {},
  "data": {
    "count": "10"
  },
  "files": {},
  "form": {},
  "headers": {
    "host": "postman-echo.com",
    "content-length": "25",
    "accept": "application/x-www-form-urlencoded",
    "content-type": "application/json",
    "user-agent": "AmazonAPIGateway_xxxxxxx",
    "x-amzn-apigateway-api-id": "xxxxxxx",
    "x-amzn-trace-id": "Root=1-59a83b0e-24c5e02d35b6c057c9fd4a00",
    "x-forwarded-port": "443",
    "x-forwarded-proto": "https"
  },
  "json": {
    "count": "10"
  },
  "url": "https://postman-echo.com/post"
}

countが数値の場合はPostman Echoから応答が返ってきます。

$ curl --request POST --url https://xxxxxxx.execute-api.us-west-2.amazonaws.com/test \
--header 'content-type: application/x-www-form-urlencoded' --data 'count=abc' | jq
{
  "message": "Invalid request body"
}

countが数値でない場合は、メッセージ「Invalid request body」が返ってきます。

JSON以外の値をAPI Gatewayだけで検証することができました。

感想

要するにJSON以外の値をAPI GatewayでJSONに変換し、別のAPI Gatewayに渡す事でJSON schemaでのバリデーションをしてみた、という話でした。

別の処理を挟んだりJSON Schemaで表現できない検証をする事は出来ないため、基本的にはLambdaなどロジックをかけるサービスに検証を任せた方が良いように思いますが、

  • 送信元のフォーマットが決まっている
  • 送信先にロジックがかけない
  • でもちょっとした値の検証をしたい

ようなケースで簡単に値の検証ができないかと思い試してみた次第です。

私からは以上です。

参考URL