S3署名付きURL発行ノードの利用例:iPhoneで写真を撮ってS3に保存する

はじめに

前回は、S3の署名付きURLで一定時間だけ有効なURLを発行して、安全にファイルをアップロード・ダウンロードするために、S3署名付きURLを発行するプライベートノードを紹介しました。

今回は、より実践的な例としてiPhoneで撮影した高解像度の写真をS3にアップロードしてみます。

フローについて

まずはenebularでフローを準備します。

フロー(JSON)はこちら
[{"id":"b16cb1621aa4b3e9","type":"tab","label":"フロー 1","disabled":false,"info":"","env":[]},{"id":"cc27c481104e5183","type":"junction","z":"b16cb1621aa4b3e9","x":480,"y":260,"wires":[["2c7d2820083e2d49"]]},{"id":"c666dcac3f3fdecb","type":"LCDP-in","z":"b16cb1621aa4b3e9","name":"","x":90,"y":80,"wires":[["ccb3819f2701d1d4"]]},{"id":"2c7d2820083e2d49","type":"LCDP-out","z":"b16cb1621aa4b3e9","name":"","x":1430,"y":260,"wires":[]},{"id":"ccb3819f2701d1d4","type":"switch","z":"b16cb1621aa4b3e9","name":"triggeredBy","property":"triggeredBy","propertyType":"msg","rules":[{"t":"eq","v":"http","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":310,"y":180,"wires":[["33b07392efe64e83"],["cc27c481104e5183"]]},{"id":"33b07392efe64e83","type":"switch","z":"b16cb1621aa4b3e9","name":"method","property":"req.method","propertyType":"msg","rules":[{"t":"eq","v":"GET","vt":"str"},{"t":"eq","v":"OPTIONS","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":3,"x":540,"y":80,"wires":[["b5a2941dc4e3c69b"],["56909012301b1b95"],["aefb65a6ad561b09"]]},{"id":"32915673f969fd1a","type":"create s3 url","z":"b16cb1621aa4b3e9","name":"PutObject URL","accesskeyid":"MY_AWS_ACCESS_KEY_ID","accesskeyidType":"env","secretaccesskey":"MY_AWS_SECRET_ACCESS_KEY","secretaccesskeyType":"env","rolearn":"MY_AWS_STS_ROLE_ARN","rolearnType":"env","useSTS":true,"awsregion":"MY_AWS_REGION","awsregionType":"env","s3bucket":"MY_S3_BUCKET","s3bucketType":"env","s3action":"PutObject","x":1180,"y":100,"wires":[["43fcd7881ed86247","2c7d2820083e2d49"]]},{"id":"b5a2941dc4e3c69b","type":"change","z":"b16cb1621aa4b3e9","name":"set param","rules":[{"t":"set","p":"key","pt":"msg","to":"payload.key","tot":"msg"},{"t":"set","p":"bucket","pt":"msg","to":"payload.bucket","tot":"msg"},{"t":"set","p":"region","pt":"msg","to":"payload.region","tot":"msg"},{"t":"set","p":"action","pt":"msg","to":"payload.action","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":720,"y":80,"wires":[["8f02b8457caca4c6"]]},{"id":"99e852e10d4bcf12","type":"catch","z":"b16cb1621aa4b3e9","name":"","scope":["32915673f969fd1a","249927214908a6b8"],"uncaught":false,"x":1050,"y":300,"wires":[["4650486a5d9df6e9","97f3a4f79431420e"]]},{"id":"56909012301b1b95","type":"change","z":"b16cb1621aa4b3e9","name":"cors","rules":[{"t":"set","p":"headers","pt":"msg","to":"{\"Access-Control-Allow-Origin\":\"*\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":750,"y":180,"wires":[["2c7d2820083e2d49"]]},{"id":"aefb65a6ad561b09","type":"change","z":"b16cb1621aa4b3e9","name":"bad request","rules":[{"t":"set","p":"statusCode","pt":"msg","to":"400","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":770,"y":220,"wires":[["2c7d2820083e2d49"]]},{"id":"4650486a5d9df6e9","type":"change","z":"b16cb1621aa4b3e9","name":"error","rules":[{"t":"set","p":"statusCode","pt":"msg","to":"500","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":1230,"y":300,"wires":[["2c7d2820083e2d49"]]},{"id":"97f3a4f79431420e","type":"debug","z":"b16cb1621aa4b3e9","name":"debug 1","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1250,"y":340,"wires":[]},{"id":"43fcd7881ed86247","type":"debug","z":"b16cb1621aa4b3e9","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1420,"y":60,"wires":[]},{"id":"a8c22796d6d1aa73","type":"inject","z":"b16cb1621aa4b3e9","name":"test","props":[{"p":"req","v":"{\"method\":\"GET\"}","vt":"json"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"key\":\"123\"}","payloadType":"json","x":350,"y":80,"wires":[["33b07392efe64e83"]]},{"id":"8f02b8457caca4c6","type":"switch","z":"b16cb1621aa4b3e9","name":"key","property":"$boolean(key) and $type(key)=\"string\"\t","propertyType":"jsonata","rules":[{"t":"true"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":870,"y":80,"wires":[["eb9569510281a7c6"],["3379705d0321103c"]]},{"id":"3379705d0321103c","type":"change","z":"b16cb1621aa4b3e9","name":"bad request","rules":[{"t":"set","p":"statusCode","pt":"msg","to":"400","tot":"num"},{"t":"set","p":"payload","pt":"msg","to":"no key","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1030,"y":160,"wires":[["2c7d2820083e2d49"]]},{"id":"eb9569510281a7c6","type":"switch","z":"b16cb1621aa4b3e9","name":"action","property":"action","propertyType":"msg","rules":[{"t":"eq","v":"GetObject","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":1010,"y":80,"wires":[["249927214908a6b8"],["32915673f969fd1a"]]},{"id":"249927214908a6b8","type":"create s3 url","z":"b16cb1621aa4b3e9","name":"GetObject URL","accesskeyid":"MY_AWS_ACCESS_KEY_ID","accesskeyidType":"env","secretaccesskey":"MY_AWS_SECRET_ACCESS_KEY","secretaccesskeyType":"env","rolearn":"MY_AWS_STS_ROLE_ARN","rolearnType":"env","useSTS":true,"awsregion":"MY_AWS_REGION","awsregionType":"env","s3bucket":"MY_S3_BUCKET","s3bucketType":"env","s3action":"GetObject","x":1180,"y":60,"wires":[["2c7d2820083e2d49","43fcd7881ed86247"]]}]

このNode-REDフローは、HTTPリクエストを受け取り、リクエストパラメータをもとにS3の署名付きURLを生成して、そのURLをレスポンスとして返します。
以下はフローの主要なポイントとその役割です:

  1. LCDP-inノード:
    • フローの入力部分で、HTTPリクエストや他のトリガーからデータを受け取ります。
  2. switchノード(triggeredBy):
    • メッセージのtriggeredByプロパティをチェックし、HTTPリクエストによってトリガーされたかどうかを確認します。
    • HTTPリクエストでトリガーされた場合、次のswitchノード(method)に進み、それ以外の場合はフローのアウトプットに進みます。
  3. switchノード(method):
    • HTTPリクエストのメソッドに基づいて処理を分岐します。
  4. changeノード(set param):
    • メッセージのプロパティ(HTTPリクエストのクエリパラメータ)を、S3の署名付きURLを生成するためのパラメータに設定しなおします。
  5. switchノード(keyとaction):
    • keyが存在し、正しい形式であることを確認し、actionがGetObjectかそれ以外かで処理を分岐します。
  6. create s3 urlノード:
    • ここで、S3のPutObjectまたはGetObjectの署名付きURLを生成します。
    • PutObject URLノードはファイルアップロード用、GetObject URLノードはファイルダウンロード用のURLを生成します。

フローのデプロイ

enebularのクラウド実行環境を作成してフローをデプロイします。
クラウド実行環境の作成が完了したら、環境変数を設定します。

合わせて、HTTPトリガー機能を有効化して作成したエンドポイントのURLをメモします。

このURLによってHTTPリクエストを受け付けて、S3の署名付きURLを生成してクライアントに返すことができます。

iPhoneのショートカットを利用

iPhoneで写真を撮ってS3に保存するアプリは、iPhoneのショートカットを使って構築します。
ショートカットを使えば、モバイルで実行するシンプルなアプリをノーコードで作ることができます。

このショートカットは、写真を撮影し、その写真をS3にアップロードする自動化されたプロセスを実行します。
以下は各アクションの処理内容です:

  1. 写真を撮影
    • 最初のステップでは、iPhoneのカメラを使って1枚の写真を撮影します。撮影時にはカメラのプレビューが表示されます。
  2. 変数keyを現在の日付に設定
    • 変数 key を現在の日付に設定します。今回は、この変数をS3にアップロードする際のファイル名として利用します。
    • 以下のように値が「現在の日付+’.jpg’」の形式となるように設定しておきます。
  3. URLの内容を取得
    • enebularのクラウド実行環境で設定したHTTPトリガーのURLにクエリパラメータ「key」を付けてGETリクエストを送信します。
    • リクエストのレスポンスとしてS3の署名付きURLを取得します。
  4. 署名付きURLの内容を取得
    • 取得した署名付きURLに対して、PUTリクエストを使って写真ファイルをアップロードします。このアクションにより、iPhoneで撮影した写真がS3バケットに保存されます。

ショートカットと今回作成したクラウド実行環境のHTTPトリガーを組み合わせることで、例えば外出先で素早く写真を撮ってクラウドに保存したり、撮影した画像を自動的にバックアップしたりするアプリをローコードで作成することができます。

実行結果

ショートカットを実行して写真を撮影します。

アップロード完了時↓

処理が完了したら、S3のコンソールを確認します。

S3コンソールからファイルをダウンロードして表示してみます。

iPhoneで撮影した6MBを超える写真をアップロードすることができました。

まとめ

S3の署名付きURLを発行するプライベートノードの利用方法の例をご紹介しました。
enebularのクラウド実行環境は、AWS Lambdaの仕組みを利用しているためLambdaのペイロードサイズ制限がありますが、S3の署名付きURLを使うことでこの制限を回避してenebularを利用したアプリケーションを作ることができます。
興味がある方は、ぜひ試してみてください!