Amazon Web Services ブログ

Amazon API Gateway のHTTP API を使用したバイナリデータの処理

この投稿は、スタートアップソリューションアーキテクトのRudolf Potucekによって書かれました。

Amazon API Gateway の REST API は、2016 年からバイナリデータをサポートしています。2020年3月に GA した Amason API Gateway のHTTP API を使用 すると、バイナリメディアタイプとテキストメディアタイプの両方をより簡単に操作できます。新しいペイロード形式バージョンをサポートし、リクエストおよびレスポンス形式に基づいてエンコーディングを推測します。この投稿では、HTTP APIAWS Lambda を使用して、テキストまたは画像のいずれかを受け入れて返すAPIを構築する方法を示します。Amazon API Gatewayの base64 エンコーディングは、AWS Lambda でのテキストとバイナリデータの処理を統合します。バイナリデータと非バイナリデータ(テキストデータ)は、JSON オブジェクトの文字列として Lambda 関数に渡されます。HTTP API の Lambda 統合では、リクエストで渡されたcontent-type ヘッダーに基づいてエンコードの必要性を自動的に推測します。

curl を使用してプレーンテキストオブジェクトを渡す場合:

curl -X POST -H 'content-type: text/plain' --data-binary "Hello World" $ECHO_JSON_API | jq .

Lambda 関数は以下を受け取ります。

{
  ...
  "body": "Hello World",
  "isBase64Encoded": false
}

バイナリオブジェクトを渡す場合:

curl -X POST -H 'content-type: image/jpeg' --data-binary @../testdata/rainbow-small.jpg $ECHO_JSON_API | jq .

Lambda 関数は以下を受け取ります。

{
  ...
  "body": "/9j/4AAQSkZ...",
  "isBase64Encoded": true
}

Lambda 関数は isBase64Encoded フラグを検査し、必要な場合、デコードすることでオリジナルデータを取得できます。

応答パスについて、Amazon API Gateway は Lambda 関数から返されたisBase64Encoding フラグを検査します。
Lambda 関数 からの次の応答は、テキストが「base64 でエンコードされたテキストデータ」になります。

{
    "statusCode": 200,
    "body": "YmFzZTY0IGVuY29kZWQK",
    "isBase64Encoded": true
}

次の Lambda 関数からの応答は、平文による応答です。

{
    "statusCode": 200,
    "body": "plain text",
    "isBase64Encoded": false
}

どちらの場合も、content-type は text/plain です。

HTTP API がバイナリデータを処理する方法


この例では、 AWS Serveless Application Model(AWS SAM) を使用して、Lambda 統合を使用した HTTP API がバイナリデータとテキストデータを処理する方法を示します。コードは GitHub で入手できます。
例に従うには、 git クライアント、 AWS SAM CLI (最小バージョン0.48)、および AWS アカウントが必要です。

  1. AWS アカウント をお持ちでない場合は作成後、ログインします。
  2. リポジトリをローカル開発マシンに複製します。
    git clone https://github.com/aws-samples/handling-binary-data-using-api-gateway-http-apis-blog.git
    cd aws-samples/handling-binary-data-using-api-gateway-http-apis-blog/sam-code
  3.  依存性を解決してビルドします。
    sam build --use-container

  4. template.yamlファイルで指定されたAWSリソースをデプロイします。
    sam deploy --guided
  5.  プロンプトの中で、スタック名 http-api を入力し、デプロイする AWS リージョンを入力します。残りの質問はデフォルト値を利用します。ただし「定義されている関数が権限を持っていないかもしれないけどよいか?」という三つの質問はY(YES)を答えてください。

    デプロイされると、AWS SAM は 3 つの API の場所を出力します。
  6. この投稿の例では、出力の値を次の環境変数に割り当てます
    ECHO_RAW_API=https://99mx7f1kg0.execute-api.us-east-1.amazonaws.com/prod/echoraw
    ECHO_JSON_API=https://99mx7f1kg0.execute-api.us-east-1.amazonaws.com/prod/echojson
    NOISE_API=https://p5kbm51wm7.execute-api.us-east-1.amazonaws.com/prod/noise

API の探索

全ての HTTP リクエストを Lambda関数 に転送する /noiseエンドポイントAWS SAM テンプレートHTTP API で定義します。選択されたロジックは、単一の関数ハンドラー内でリクエストをルーティングします。GET と POST を分離するために、個別の関数を作成できます。この例では、単一のハンドラーを使用して、FastAPIMangum などのフレームワークの動作を模倣します。

Lambda 関数は、Python Pillow ライブラリを使用して画像を生成および処理します。関数は、クエリパラメータを受け取り生成する画像を定義します。この関数は、demo64Flag というテキストデータを返す方法を指定するクエリパラメータを受け取ります。また、MIME タイプを指定するためのAcceptヘッダーも受け取ります。MIME タイプはデフォルトでは JPEG です。

デプロイされると、Lambda 関数はルートに応じて次の手順を実行します。GET リクエストは画像を生成し、POST リクエストはアップロードされた画像のノイズをオーバーレイします。

JPEGを生成するためのHTTP GET

  1. AWS SAM が出力した NoiseHttpApiURL を参照します。HTTP API エンドポイントは、リクエストを Lambda 関数に転送し、Lambda 関数に GET リクエストとしてルーティングします。Pillow ライブラリは、ランダムノイズを変換し、バイナリ JPEG 画像を生成します。その結果、ノイズで満たされた大きな横長の長方形が生成されます。
    デフォルト設定で生成したノイズ画像

    デフォルト設定で生成したノイズ画像

    Lambda 関数は、クエリパラメーターが渡されたかどうかを確認します。コードは、画像の高さと幅や、minmaxパラメータでノイズ画素の輝度を受け付けます。

  2. URL に次のパラメータを追加します。?w=50&h=100&min=96&max=192

    これにより、幅 50 ピクセル、高さ 100 ピクセル、明るさ96〜192 / 255のノイズ画像が要求され、ノイズで満たされた小さな縦長の長方形が生成されます。

    パラメータ指定して生成したノイズ画像

    パラメータ指定して生成したノイズ画像

    Acceptヘッダーに基づいて、Lambda 関数は出力画像形式を設定します。

  3. curl を使用してAcceptヘッダーを追加し、GIF 画像をダウンロードします。
    # Ask for GIF
    curl "${NOISE_API}?w=50&h=50" --output test-get.gif -H 'Accept: image/gif'
  4. file コマンドを使用して、返されたファイルのタイプが GIF であることを確認します。
    file test-get.gif
    test-get.gif: GIF image data, version 87a, 50 x 50

    Lambda 関数は、demo64Flag というクエリパラメーターも受け付けます。これは、Accept ヘッダーを介して不明な画像タイプを要求されたときにテキストデータを返す方法を指定します。 demo64Flag=0で呼び出すことにより、コードはプレーンテキスト文字列 Text path: Unknown encoding requested を応答します。これは、文字列がtext/plainタイプであり、 base64でエンコードされていないことを API Gateway に伝えます。

  5. curl を使用して不明な画像形式を要求し、プレーンテキストの応答を返します。
    curl "${NOISE_API}?demo64Flag=0" -H 'Accept: image/unknown'
    
    Text path: Unknown encoding requested

    demo64Flag=1を設定すると、コードQmluYXJ5IHBhdGg6IFVua25vd24gZW5jb2RpbmcgcmVxdWVzdGVkを戻します。これは、文字列 Binary path: Unknown encoding requested が base64 でエンコードされたものであり、タイプtext/plainであり、 base64 でエンコードされていることを API Gateway に通知します。

    HTTP API エンドポイントは、isBase64Encoded フラグを使用してテキストをデコードしてから、呼び出し元に応答を返します。

  6. curl を使用して不明な画像形式を要求し、base64 でエンコードされた応答をします。
    curl "${NOISE_API}?demo64Flag=1" -H 'Accept: image/unknown'
    Binary path: Unknown encoding requested

    HTTP API エンドポイントは、base64 でエンコードされた文字列をテキストに変換し直しました。

オーバーレイノイズを生成するHTTP POST

次の例は、同じ API でテキストメディアタイプとバイナリメディアタイプの両方を処理する方法を示しています。Lambda 関数の POST パスは画像を受け付け、ノイズを追加します。また、ノイズを適用する前に、テキストをアップロードして画像にレンダリングすることもできます
リポジトリには、テスト画像 rainbow-small.jpg とテストテキストファイル multiline.txt が含まれています。

レインボー画像とテキスト

レインボー画像とテキスト

  1. curl を使用して、JPEG のレインボー画像をアップロードし、ノイズをオーバーレイし、GIF ファイルを要求します。
    curl "${NOISE_API}" -X POST --data-binary @../testdata/rainbow-small.jpg -H 'content-type: image/jpeg' -H 'Accept: image/gif' --output test-post-image.gif
  2. file コマンドで GIF であることを確認します。
     file test-post-image.gif
    test-post-image.gif: GIF image data, version 87a, 100 x 100
  3. 結果の画像を表示して、生成されたノイズを確認します

    ノイズ付きのレインボー画像

    ノイズ付きのレインボー画像

  4. curlを使用して、テキストファイルをアップロードし、ノイズをオーバーレイしてレンダリングした画像の GIF ファイルを要求します。テキストファイルは --data-binaryフラグを付けてアップロードされ、curl が改行を削除しないようにします。
    curl "${NOISE_API}?w=100&h=100" -X POST --data-binary @../testdata/multiline.txt -H 'content-type: text/plain' -H 'Accept: image/gif' --output /tmp/test-post-text.gif
  5. file コマンドで GIF であることを確認します。
    file test-post-text.gif
    test-post-text.gif: GIF image data, version 87a, 100 x 100
  6. 結果画像を表示してノイズが生成されことを確認してください。
    ノイズ付きテキスト

    ノイズ付きテキスト

    両例ともに、HTTP API エンドポイントは、--data-binary フラグを使用して変更されていないバイナリファイルを受信しました。content-type へッダーに基づいて、API はそれがバイナリファイルであることを認識し、base64 がそれをエンコードし、Lambda 関数のコードにisBase64Encodedフラグを通知します。Lambda 関数はデータを受け取り、必要に応じてbase64でデコードします。アップロードされた画像を読み取るか、テキストを新しい画像にレンダリングし、最後にノイズを追加します

    CloudWatch Logsを見てテキストがbase64でエンコードされずに受信されたことを確認します。

    ... 'body': 'Lorem ipsum dolor sit amet, \nconsectetur adipiscing elit,...', 'isBase64Encoded':
    False ...

ブラウザ呼び出し時のCORSのサポート

静的 Web ページを表示するには、HTTP API エンドポイントがどこからでも CORS リクエストを受け入れる必要があります。これは AWS SAMテンプレート 内のHTTP APICorsConfiguration ステートメントによって有効になります。Lambda 関数もOPTIONS をサポートし、応答データで Access-Control-Allow-Origin ヘッダー を返す必要があります。

この例では、 HTML と JavaScript を含む 静的なWebページ を使用しています。ポインタを使用して何かを描画し、送信ボタンを選択すると fetch  呼び出してノイズをオーバーレイできます。

このGitHubのプロジェクトページを参照して、これをテストします。ページで、左上のボックスに描画して画像データを生成します。NOISE_API の URL を URL を入力し、送信を選択します。Lambda 関数は、HTTP API POST を介して画像データを受け取ります。この関数はノイズを生成し、バイナリファイルをクライアントに返します。その結果、次のような画像が生成されます。

Webページ上の画像から作られたノイズ付き画像

Webページ上の画像から作られたノイズ付き画像

結論

Amazon API Gateway の HTTP API を使用すると、バイナリメディアタイプとテキストメディアタイプの両方を簡単に操作できます。HTTP API Lambda 統合はリクエストで渡されたcontent-typeヘッダーに基づいてエンコードの必要性を自動的に推測できます。isBase64Encodedデータとともにブール値を渡すと、バイナリデータのエンコードとデコードが簡単になります。

HTTP API を使用すると、CORS を使用してドメイン名ベースのアクセス制御をより簡単に提供することもできます。これにより、静的にホストされているサイトから API を呼び出すなどのユースケースが可能になります。

サーバーレス学習リソースの詳細については、https://serverlessland.comにアクセスしてみてください。
 
日本語訳は Professional Services のコンサルタント、堀場 隆文が担当しました。原文はこちらです。