API Gateway と Lambda の構成で、Lambda に対してバイナリデータをバイナリデータのまま渡す方法を教えてください

現在の仕様では、Lambda に対してバイナリデータをそのまま渡すことはできません。
2023.02.09

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

困っていた内容

API Gateway と Lambda の構成で、Lambda に対して Base64 エンコードされていない状態で Lambda にバイナリデータを渡したいです。
バイナリデータのまま渡すことはできるのでしょうか?

どう対応すればいいの?

現在の仕様として、Lambda にはデータを JSON 形式で渡す必要があるため、Lambda にバイナリデータをそのまま渡すことはできません。

Invoke
Payload
入力として Lambda 関数に提供される JSON。

そのため、Lambda ではバイナリデータが Base64 エンコードされた状態で受け取ることを前提としたソースコードを記述してください。

Lambda がバイナリデータをそのまま受け取れないことを確認してみた

Lambda プロキシ統合の場合、バイナリメディアタイプの設定を行えばバイナリデータが Base64 エンコードされた状態で Lambda に渡されるので、 Lambda 非プロキシ統合で確認してみます。

設定は以下のとおり行い、Lambda に対してバイナリデータを渡すようにしています。
API Gateway の設定 バイナリメディアタイプ:image/png
統合リクエストのマッピングテンプレート:定義せず、「テンプレートが定義されていない場合(推奨)」(when_no_templates)を選択
contentHandling:未定義

以下 curl コマンドより、画像データの POST リクエストを行います。

curl -X POST -H 'Content-Type: image/png' -d @white.png https://xxx.execute-api.ap-northeast-1.amazonaws.com/test/notproxy

すると、以下のエラーが返ってきました。

{"message": "Could not parse request body into json: Could not parse payload into json: Unexpected character ((CTRL-CHAR, code 137)): expected a valid value (JSON String, Number, Array, Object or token \'null\', \'true\' or \'false\')\n at [Source: (byte[])\"\uFFFDPNG\u001AiTXtXML:com.adobe.xmp   <rdf:RDF xmlns:rdf=\"http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#\">      <rdf:Description rdf:about=\"\"            xmlns:tiff=\"http:\/\/ns.adobe.com\/tiff\/1.0\/\"            xmlns:exif=\"http:\/\/ns.adobe.com\/exif\/1.0\/\">         <tiff:ResolutionUnit>2<\/tiff:ResolutionUnit>         <exif:PixelXDimension>188<\/exif:PixelXDimension>         <exif:PixelYDimension>246<\/exif:PixelYDimension>      <\/rdf:Description>   <\/rdf:RDF><\/x:xmpmeta>\u0013\uFFFDV\u0081\\a \u0017\uFFFD&\'3\uFFFD\uFFFD\uFFFD\\h\u0018\uFFFD\u0005\uFFFD\uFFFD\uFFFD\f\uFFFDa \u0017\uFFFD&\'3\uFFFD\uFFFD\uFFFD\\\"; line: 1, column: 2]"}%

API Gateway の実行ログを確認すると、バイナリデータをそのまま Lambda に渡そうとした結果、上記のエラーが発生したことが分かりました(以下ログは関係箇所のみ抜粋しています)。

(3dd77df1-8bd2-489e-a4f1-3cc9ba9a1357) Method request body before transformations: [Binary Data]
(3dd77df1-8bd2-489e-a4f1-3cc9ba9a1357) Endpoint request body after transformations: [Binary Data]
(3dd77df1-8bd2-489e-a4f1-3cc9ba9a1357) Sending request to https://lambda.ap-northeast-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:xxx:function:binary/invocations
(3dd77df1-8bd2-489e-a4f1-3cc9ba9a1357) Received response. Status: 400, Integration latency: 30 ms
(3dd77df1-8bd2-489e-a4f1-3cc9ba9a1357) Endpoint response headers: {Date=Mon, 06 Feb 2023 08:11:03 GMT, Content-Type=application/json, Content-Length=854, Connection=keep-alive, x-amzn-RequestId=f038a7da-9668-420e-a761-a5686d8c4c44, x-amzn-ErrorType=InvalidRequestContentException}"
(3dd77df1-8bd2-489e-a4f1-3cc9ba9a1357) Endpoint response body before transformations: {""Type"":""User"",""message"":""Could not parse request body into json: Could not parse payload into json: Unexpected character ((CTRL-CHAR, code 137)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
 at [Source: (byte[])\""�PNG\u001AiTXtXML:com.adobe.xmp   <rdf:RDF xmlns:rdf=\""http://www.w3.org/1999/02/22-rdf-syntax-ns#\"">      <rdf:Description rdf:about=\""\""            xmlns:tiff=\""http://ns.adobe.com/tiff/1.0/\""            xmlns:exif=\""http://ns.adobe.com/exif/1.0/\"">         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>         <exif:PixelXDimension>188</exif:PixelXDimension>         <exif:PixelYDimension>246</exif:PixelYDimension>      </rdf:Description>   </rdf:RDF></x:xmpmeta>\u0013�V\\a \u0017�&'3���\\h\u0018�\u0005���\f�a \u0017�&'3���\\\""; line: 1, column: 2]""}"
(3dd77df1-8bd2-489e-a4f1-3cc9ba9a1357) Lambda invocation failed with status: 400. Lambda request id: f038a7da-9668-420e-a761-a5686d8c4c44
(3dd77df1-8bd2-489e-a4f1-3cc9ba9a1357) Execution failed: Could not parse request body into json: Could not parse payload into json: Unexpected character ((CTRL-CHAR, code 137)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
 at [Source: (byte[])""�PNGiTXtXML:com.adobe.xmp   <rdf:RDF xmlns:rdf=""http://www.w3.org/1999/02/22-rdf-syntax-ns#"">      <rdf:Description rdf:about=""""            xmlns:tiff=""http://ns.adobe.com/tiff/1.0/""            xmlns:exif=""http://ns.adobe.com/exif/1.0/"">         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>         <exif:PixelXDimension>188</exif:PixelXDimension>         <exif:PixelYDimension>246</exif:PixelYDimension>      </rdf:Description>   </rdf:RDF></x:xmpmeta>�V\a �&'3���\h�����a �&'3���\""; line: 1, column: 2]"
(3dd77df1-8bd2-489e-a4f1-3cc9ba9a1357) Method completed with status: 400

上記のログでx-amzn-ErrorType=InvalidRequestContentExceptionとありますが、このエラーは Lambda の Invoke API を呼び出した際、リクエストボディを JSON として解析できない時に発生します。

Invoke
InvalidRequestContentException
リクエストボディを JSON として解析できませんでした。

上記の API Gateway 実行ログでは Lambda の Invoke API 実行時のエラーであることの実感が湧かなかったので、試しに API Gateway 経由ではなく直接 Lambda を呼び出す際にも同様のエラーが発生するかを確認してみます。
以下では、AWS CLI で Lambda を呼び出す際、Payload にバイナリデータを渡しています。

$ aws lambda invoke --function-name binary --payload fileb://white.png response.json

An error occurred (InvalidRequestContentException) when calling the Invoke operation: Could not parse request body into json: Could not parse payload into json: Unexpected character ((CTRL-CHAR, code 137)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
 at [Source: (byte[])"�PNG

IHDRn�ly+sRGB���leXIfMM>(�iN���n��da1'  pHYs%%IR$�
iTXtXML:com.adobe.xmp<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
     "[truncated 25892 bytes]; line: 1, column: 2]

想定通り、InvalidRequestContentException エラーが発生しました。

以上の検証により、改めて Lambda にはバイナリデータをそのまま渡せないことが確認できました。

参考資料

Invoke

REST API のバイナリメディアタイプの使用