【Laravel】AWSのS3へデータをアップロードするためのPresignedURLを作る方法

どーも!

今回はAWSにアクセスするためのPresignedURLを作っていきます!

初見でpresignedURL?と言われてもはてなまーくですよね...w

僕もそうでした!

なので、そこから解説していきたいと思います!

PresignedURLって?

さて、AWSにはS3というバケットが存在しています。

簡単に言うとクラウドサービス的なものと捉えてもらっていいです。

画像や動画、テキストファイルなど、様々なファイルをアマゾンのクラウド上に保存して、手元のサービスからあーだこーだして持ってきたり、保存したり色々できるわけですね。

そのS3にデータを置くためには一般的にサービス側でcredentials(認証情報)、例えばAWSアクセスキーやAWSシークレットアクセスキー等(俗に言うIAM Userとして)使ってアクセスする必要があります。

ただ、場合によってはその権限を持っていない人にもアクセスやアップロードを許可したい場合があります。

それを一時的に可能にするURLがPresignedURLです!

バックエンドで処理を挟まず、サービスのフロントエンドからS3へデータを送信したり...

不特定多数の人に一時的にS3のデータを閲覧できるようにしたり...

そういった場合に活用できる機能ですね。

ちなみに署名付きURLなんて呼ばれたりもします。

それでは早速コードをみていきましょう。

PresignedURLを作ってみる。

今回、Laravelで実装していくわけですが、使用する前にSDKの設定を忘れないようにしてください。

(同じプロジェクトでAWSを使用する箇所があるなら大抵設定済みかもです)

以下が参考になると思います。

では、早速なんですが...

先にコードをお見せしちゃいましょう!

 // clientの作成
  $s3Client = app()->make('aws')->createClient('s3');

  // コマンドを作成
  $command = $s3Client->getCommand('PutObject', array(
    'Bucket' => config('aws.s3')
    'Key'    => 'key',
  ));

  // 1分間有効な署名つきURLを発行
  $request = $s3Client->createPresignedRequest($command, '+1 minutes');

  return $request->getUri();
}

まず最初にLaravelのDIを用いてクライアントの作成を行っています。

もちろん、その場で作ってもいいと思います。

上記引用先で行っていますが、Laravelではautoloadは必要ありません(autoloadにあたる処理を裏でやってくれてるみたいです)。

なので、SDKの準備ができていればほとんど例の通りでクライアント作成もうまくいくはずです。

次に行っているのが、コマンドの作成です。

コマンド...?となるかもしれませんが、先ほど作成したクライアントはそのS3バケットへのアクセスする権限を持っているコマンド実行をする物、そしてそのコマンドが実行する内容を書いたものをコマンドと理解してもらえれば大丈夫かと思います。

クライアントがコマンドを実行するために、事前にコマンドを用意するわけですね。

getCommandというAPIに対して、第一引数にコマンド名(ここではPutObjectですね)、そして第二引数にそのコマンド設定に必要なパラメータを配列として渡しています。

以下にてコマンドの詳細についてみることができます。

リンク先ではコマンドとして作っていませんね。

簡単に比較してみます。

// コマンド生成
$command = $s3Client->getCommand('PutObject', [/* ... */]));
// リンク先の方法
$result = $client->putObject([/* ... */]);

前者ではPutObjectのコマンドを作成して、後者(リンク先の方法)ではクライアントから直接putObjectを実行しています。

リンク先のページの一番上の方をみてみると...

Each of the following operations can be created from a client using $client->getCommand('CommandName'), where "CommandName" is the name of one of the following operations. Note: a command is a value that encapsulates an operation and the parameters used to create an HTTP request.

ざっくり訳すと...

以下に記載されている操作はクライアントを用いて $client->getCommand('CommandName') とすることで作成することができます。ただし、"CommandName"の箇所は以下に記載されている操作名を入れてください。コマンドは操作をカプセル化した値になっており、そのパラメータはHTTPリクエストを行うために扱われます。

つまり、あらゆる操作をカプセル化してHTTPリクエストで扱えるようにしているのがコマンドで、リンク先の例は直接その命令を実行しているわけです。

今回はPutObjectで、どのバケット(ルートとなるフォルダ的なもの)、どのキー(ルートフォルダ以下のどんなファイルかを示すパス的なもの)にそのオブジェクト(データ)をおけばいいの?という命令をここで実行するわけではなく、presignedURLとして実行したいわけなので、一旦カプセル化してあげる必要があるわけですね。

実際の例だと...

configの環境変数として設定しているバケット名のバケットに対して"key"というオブジェクトキーのファイルとしてデータを保存する...

という一連の操作をカプセル化しています。

そのカプセル化したコマンドを使ってpresigneURLを発行すれば、そのURLに対してHTTPでPUTリクエストをすればいい感じにS3にPUTできるようになるわけです。

(直接S3へPUTしてアップロードする場合はコマンド化する必要はありませんが、例えばフロント側から直接動画をS3へあげたい場合などは今回のようにコマンドをカプセル化した操作と一緒にpresignedURLを発行して、フロント側にpresignedURLを渡した上でアップロード...等する必要があります。)

先ほどファイルやフォルダのお話をしましたが、厳密にはS3のバケットの概念はフォルダとは似て非なるものらしいんですが...

S3の概形理解のため、ここでは上記で理解していただければいいかなと思います...

(本来はS3というのはキーと値のペアでデータを保存しているんですが...一応S3のコンソール上でも"ファイル"という用語が使われているので、大丈夫かと思います。)

そんな感じで、どのバケットのどのキーにおけばいいのかと言うのを指定してpresignedURLで行う操作をコマンドとして作成します。

そして、その情報を用いてクライアントにcreatePresignedRequestを用いてコマンドをURLにするわけですね。

第二引数にはどれくらいの時間そのURLを有効にするかを指定できるようになっています。

あまりにも長すぎるとURLが外部に漏れた場合にそのURLを知っている人が誰でもアクセスできてしまい、逆に短すぎるとそもそもそのURLを使う前に有効期限切れになってアクセスできなくなったりします。

サービスごとに適切な値を指定する必要がありそうですね。

そして、取得した情報の中からgetUri()で実際のpresignedURLを取得することができます。

あとは生成できたpresignedURLに対してPUTリクエストで保存したいファイルを送ってあげればS3へ反映されるはずです。

筆者はPostManを使用して行いましたが、PostManだと以下のように確認できました。

PUTにする。

Body選択後、binalyを選択

画像や動画を追加して、先ほど生成したpresignedURL入力してからSend。

最初ずっとform-dataからやろうとしてしまって全然できなかった...w

実行を追えたらS3の管理画面をみに行って、対象のbucket, keyに対してデータが追加されていればOKです!

エラーがあるとするなら大抵はcredentials(クライアント生成時にAWSへの適切な権限が設定できていない)周りとか、regionが日本の方は大体東京リージョン `ap-northeast` 辺りだと思う...ので、確認してみてください。

あとはコマンド作成時のS3バケットがちゃんとあるか?等も確認するいいかと思います。

メタデータを付与する

さて、先ほどPUTすることができましたが、今回実装した際、メタデータを付けてアップロードしたいなぁ...となり、それもやってみたので合わせて書いておきます。

方法は至って簡単でコマンド生成時に合わせて付与することができます。

例えばgroup_idとvideo_idという名前でメタデータを付与したい場合は以下のようにすれば可能です。

// コマンドを生成
$command = $s3Client->getCommand('PutObject', array(
    'Bucket' => config('aws.s3')
    'Key'    => 'key',
    'Metadata' => [
	       'group_id' => $groupId,
	       'video_id' => $videoId
    ]
));

簡単ですね!

ちなみにメタデータを付けてS3で確認すると...

このような感じで確認ができます。( `x-amz-meta-` は自動で付与されるようです。)

まとめ

さて、正直初めて知ったpresignedURLだったんですが...

最初知ったときはなんのことかさっぱりでした...w

なんでわざわざURL発行するの...?とか。

ただ、知ってみるとなるほどぉ!

そんなことができるのかぁ!って感じです!

ただ、ある程度はその動きを理解しておく必要があるかなぁとも思います。

presigneURLは有効期間中、URLを知っていれば誰でもS3へアクセスできてしまうわけなので、そこはしっかりと把握した上で使いたいですね。

それでわ!

おすすめの記事