かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

API Gateway で受信するデータを JSON Schema Validation でチェックしてから SQS へ送信する

概要

記事一覧はこちらです。

Using JSON Schema Validation with the AWS API Gateway という記事を見かけました。API Gateway で受信したメッセージを Lambda を呼び出す前に JSON Schema Validation で検証できるそうなので試してみます。Lambda を呼び出したら SQL へメッセージを送信し、別の Lambda でメッセージを受信します。

Serverless Framework の 1.8 から functions の events に sqs が記述できるようになるのですが、今回試した時のバージョンは 1.74 でしたので CloudWatch Events で 1分毎に Lambda を起動して SQS をチェックすることにします。

※(2020/07/05追記)何を見間違えてしまったのか SQS を events に設定できるのは 1.28 からでした。。。 今使用しているバージョンは 1.74 なので Using SQS with AWS Lambda and Serverless の記事通りに実装したらイベントベースで Lambda が呼び出されるようにできますね。

f:id:ksby:20200628115040p:plain:w450

SQS への送信は serverless-apigateway-service-proxy プラグインを使用したかったのですが、このプラグインを使用して API Gateway の Endpoint を定義した時に JSON Schema Validation をセットする方法がないようだったので諦めました。

参照したサイト・書籍

  1. Using JSON Schema Validation with the AWS API Gateway
    https://fernandomc.com/posts/schema-validation-serverless-framework/

  2. API Gateway でリクエストの検証を有効にする
    https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-method-request-validation.html

  3. JSON Schema
    https://json-schema.org/

  4. Getting Started Step-By-Step
    https://json-schema.org/learn/getting-started-step-by-step.html

  5. Understanding JSON Schema
    https://json-schema.org/understanding-json-schema/

  6. schema.org
    https://schema.org/

  7. Enabling JSON5
    https://www.jetbrains.com/help/idea/json.html#ws_json_choose_version

  8. 知らないうちにJSON5 in Babel
    https://qiita.com/jkr_2255/items/026e0fdb4570c88c4f51

  9. AWS::SQS::Queue
    https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-queues.html

  10. AWS Lambda を Amazon SQS に使用する
    https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-sqs.html

  11. Sending and receiving messages in Amazon SQS
    https://boto3.amazonaws.com/v1/documentation/api/latest/guide/sqs-example-sending-receiving-msgs.html

  12. Using SQS with AWS Lambda and Serverless
    https://www.serverless.com/blog/aws-lambda-sqs-serverless-integration/

  13. Serverless updates - SQS events, private endpoints, Event Gateway open source
    https://www.serverless.com/blog/serverless-updates-framework-v128/

目次

  1. jsonschema-sqs-project プロジェクトを作成する
  2. order_service サブプロジェクトを作成する
  3. 受信するメッセージのサンプルを作成する
  4. IntelliJ IDEA の .json を関連付ける File Type を JSON → JSON5 に変更する。。。が、API Gateway で JSON5 がサポートされていなかったので元に戻す
  5. JSON Schema Validation のための schema.json を作成する
  6. API Gateway 経由でメッセージを受信して SQS へ送信する Lambda を実装する
  7. SQS からメッセージを取り出してログに出力する Lambda を実装する
  8. serverless.yml を変更する
  9. deploy する
  10. 動作確認
  11. Validation エラーの時にどうやって原因を調べればよいのか?

手順

jsonschema-sqs-project プロジェクトを作成する

以下の手順で jsonschema-sqs-project プロジェクトを作成します。具体的な手順は IntelliJ IDEA+Node.js+npm+serverless framework+Python の組み合わせで開発環境を構築して AWS Lambda を作成してみる 参照。

  • jsonschema-sqs-project の Empty Project を作成する。
  • Python の仮想環境を作成する。
  • Serverless Framework をローカルインストールする。
  • .envrc を作成する。
  • pip install boto3

order_service サブプロジェクトを作成する

プロジェクトのルートディレクトリの下で npx sls create --template aws-python3 --path order_service を実行して order_service サブプロジェクトを作成します。

受信するメッセージのサンプルを作成する

今回は以下のサンプルメッセージを受信します。細かい仕様は後で JSON Schema を作成する時に決めます。

{
  "orderNumber": "1",
  "orderDate": "2020-06-27",
  "isGift": false,
  "orderedItem": [
    {
      "orderItemNumber": "1001",
      "identifier": "book",
      "name": "サンプル書籍",
      "orderQuantity": 1,
      "price": 3800
    },
    {
      "orderItemNumber": "61059",
      "identifier": "furniture",
      "name": "テスト椅子",
      "orderQuantity": 2,
      "price": 12000
    }
  ]
}

IntelliJ IDEA の .json を関連付ける File Type を JSON → JSON5 に変更する。。。が、API Gateway で JSON5 がサポートされていなかったので元に戻す

IntelliJ IDEA で .json のファイルを編集すると "$schema": "http://json-schema.org/draft-04/schema#" の定義を参照して自動補完してくれます(VSCode でも補完してくれましたので補完して当然のようです)。

f:id:ksby:20200627134049p:plain

schema を定義しておくと json の編集が便利になるんだなと思い IDEA のマニュアルで JSON を見にいくと、Enabling JSON5 という文字が。JSON5 って何?

調べてみると 知らないうちにJSON5 in Babel の記事を見つけました。JSON なのにケツカンマ有効でコメントが付けらたりできるとのこと。かなり前からあるのに全然知りませんでした。。。

Extend the JSON5 syntax to all JSON files に従い、.json を関連付ける File Type を JSON → JSON5 に変更してみます。

「Settings」ダイアログを開いて「Editor」-「File Types」の設定を確認すると、確かに今は「JSON」の File Type に .json が登録されています。

f:id:ksby:20200627140800p:plain f:id:ksby:20200627140921p:plain

「JSON5」の「Registered patterns」で「+」ボタンをクリックして「Add Wildcard」ダイアログを表示してから *.json を入力して「OK」ボタンをクリックします。

f:id:ksby:20200627141031p:plain

「Reassigned wildcard」ボタンをクリックします。

f:id:ksby:20200627141147p:plain

「JSON5」の File Type に .json が登録されますので「OK」ボタンをクリックしてダイアログを閉じます。

f:id:ksby:20200627141331p:plain

File Type が「JSON」の時は以下の画像のように赤波線が表示されていましたが、

f:id:ksby:20200627142036p:plain

「JSON5」に変更すると全て消えました!

f:id:ksby:20200627142229p:plain

すごいなと思いつつ API GatewayJSON Schema Validation でも JSON5 がサポートされているのか別にサンプルプロジェクトを作って試してみたところ、JSON.parse(...) が使われていてダメでした。。。 残念ですが、元に戻すことにします。

f:id:ksby:20200627145644p:plain

JSON5 を使いたい時には拡張子を .json5 にすればよいので覚えておくことにします。

JSON Schema Validation のための schema.json を作成する

order_service サブプロジェクトの下に schema.json を作成して、以下の内容を記述します。JSON Schema の定義で入れられそうなものは出来るだけ入れてみました。

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "definitions": {
    "orderedItem": {
      "type": "object",
      "properties": {
        "orderItemNumber": {
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "identifier": {
          "type": "string",
          "enum": [
            "book",
            "furniture"
          ]
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 10
        },
        "orderQuantity": {
          "type": "integer",
          "minimum": 0,
          "maximum": 999
        },
        "price": {
          "type": "integer",
          "minimum": 0
        }
      },
      "required": [
        "orderItemNumber",
        "identifier",
        "name",
        "orderQuantity",
        "price"
      ]
    }
  },
  "title": "Order",
  "type": "object",
  "properties": {
    "orderNumber": {
      "type": "string",
      "pattern": "^[0-9]+$"
    },
    "orderDate": {
      "type": "string",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
    },
    "isGift": {
      "type": "boolean"
    },
    "orderedItem": {
      "type": "array",
      "minItems": 1,
      "maxItems": 3,
      "uniqueItems": true,
      "items": {
        "$ref": "#/definitions/orderedItem"
      }
    }
  },
  "required": [
    "orderNumber",
    "orderDate",
    "orderedItem"
  ]
}

API Gateway 経由でメッセージを受信して SQS へ送信する Lambda を実装する

order_service/handler.py のファイル名を apigw_handler.py に変更し、以下の内容に変更します。

import json
import logging
import os

import boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def send_msg(event, context):
    sqs_client = boto3.client('sqs')

    logger.info(event['body'])
    response = sqs_client.send_message(
        QueueUrl=os.environ['QUEUE_URL'],
        MessageBody=event['body']
    )

    body = {
        "message": f"send message to sqs, MessageId = {response['MessageId']}",
        "input": event
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

SQS からメッセージを取り出してログに出力する Lambda を実装する

order_service ディレクトリの下に sqs_handler.py を作成し、以下の内容を記述します。

import logging
import os

import boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def recv_msg(event, context):
    sqs_client = boto3.client('sqs')

    response = sqs_client.receive_message(QueueUrl=os.environ['QUEUE_URL'])
    if 'Messages' in response:
        logger.info(response)
        for msg in response['Messages']:
            sqs_client.delete_message(QueueUrl=os.environ['QUEUE_URL'], ReceiptHandle=msg['ReceiptHandle'])

serverless.yml を変更する

order_service/serverless.yml を以下の内容に変更します。

service: order-service

custom:
  queueName: "SampleQueue"

provider:
  name: aws
  runtime: python3.8

  stage: dev
  region: ap-northeast-1

  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "sqs:*"
      Resource:
        - Fn::GetAtt: [ SampleQueue, Arn ]

functions:
  sendMsg:
    handler: apigw_handler.send_msg
    environment:
      QUEUE_URL: !Ref SampleQueue
    events:
      - http:
          path: send-msg
          method: post
          cors: true
          request:
            schema:
              application/json: ${file(schema.json)}

  recvMsg:
    handler: sqs_handler.recv_msg
    environment:
      QUEUE_URL: !Ref SampleQueue
    events:
      - schedule: rate(1 minute)

resources:
  Resources:
    SampleQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: "${self:custom.queueName}"

deploy する

deploy します。

f:id:ksby:20200628121938p:plain f:id:ksby:20200628122040p:plain f:id:ksby:20200628122150p:plain

動作確認

Postman から endpoint へ上に書いたサンプルメッセージを送信すると 200 OK が返ってきました。

f:id:ksby:20200628122729p:plain

ログにも送受信されたメッセージが出力されています。

f:id:ksby:20200628122904p:plain f:id:ksby:20200628123000p:plain f:id:ksby:20200628123047p:plain

JSON Schema の定義に合わないメッセージ(orderedItem の要素数が minItems 未満)を送信すると 400 Bad Request が返ってきました。

f:id:ksby:20200628123239p:plain

Validation エラーの時にどうやって原因を調べればよいのか?

1つ目は送信しているメッセージを適当なファイルに保存してから、IntelliJ IDEA のエディタ上で JSON Schema を適用してエラー箇所を表示させる方法です。

ファイルに保存してから画面右下の「No JSON schema」をクリックします。

f:id:ksby:20200628140323p:plain

メニューが表示されるので、一番上の「New Schema Mapping...」を選択します。

f:id:ksby:20200628140443p:plain

JSON Schema Mappings」ダイアログが表示されるので、「Schema file or URL」に上で作成した schema.json を選択してから「OK」ボタンをクリックします。

f:id:ksby:20200628140836p:plain

ファイルに保存したメッセージを変更して JSON Schema に合わないようにするとその箇所が分かるように表示されます。

f:id:ksby:20200628141749p:plain

もっと詳細な内容を知りたい場合には、エディタ上でコンテキストメニューを表示させて「Analyze」-「Inspect Code...」を選択した後、

f:id:ksby:20200628142047p:plain

「Specify Inspection Code」ダイアログが表示されるので、何も変更せずに「OK」ボタンをクリックします。

f:id:ksby:20200628142202p:plain

そうすると画面下に Inspection Results Window が表示されて、そこに詳細なエラー内容が表示されます。

f:id:ksby:20200628142453p:plain

2つ目は AWS マネジメントコンソールの API Gateway の画面からテスト送信してログを表示させる方法です。

今回作成された API Gateway の「リソース」の画面に遷移してから、登録した endpoint の POST メソッドの画面の「テスト」リンクをクリックします。

f:id:ksby:20200628142944p:plain

メソッドテストの画面が表示されるので「リクエスト本文」に送信する JSON を記述して「テスト」ボタンをクリックします。

f:id:ksby:20200628143239p:plain f:id:ksby:20200628143435p:plain

そうすると画面の「ログ」にエラーの内容が表示されます。ただしこの方法だと最初のエラーしか表示されないようです。

f:id:ksby:20200628143644p:plain

最後に npx sls remove -v を実行して構築した環境を削除します。

履歴

2020/06/28
初版発行。
2020/07/05
概要に "※(2020/07/05追記)..." の記述を追加しました。