ユーザにAmazon S3からファイルを直接ダウンロードしてもらいたいことがありますが、その際にファイル名を差し替えたいこともあります。
調べたところ、S3には、ファイル名を差し替えるための機能が用意されていました。
権限まわりの仕様を知らなくて少し手間取ったので、今回はそのあたりの内容を交えてメモします。
追記: LaravelのFilesystemを利用している場合はこのメソッドを使うと一発でPre-Signed URLを発行したりダウンロード時のファイル名を指定したりできます。v5.4あたりで追加されたようです。
どんな機能が用意されているか
S3のオブジェクトは、以下のようなURLを使ってGETで取りにいくことができますが、
- http://[bucket].s3.amazonaws.com/[key]
イメージとしては、URLに次のような感じでリクエストパラメータを含めると、ファイル名を差し替える(レスポンスヘッダをカスタマイズする)ことができます。(関連ドキュメントはこちら)
- http://[bucket].s3.amazonaws.com/[key]?response-content-disposition=[filename]
ただし、これだけだと権限エラーになります。
これについて、ドキュメントには以下のような注釈があります。
You must sign the request, either using an Authorization header or a Pre-signed URL, when using these parameters. They can not be used with an unsigned (anonymous) request.
どうやらanonymousユーザはリクエストパラメータを使えないらしく、これは対象のオブジェクトが一般公開されていたとしても変わりません。リクエストをSignする必要があります。
リクエストをSignする
というわけでリクエストをSignします。(関連ドキュメントはこちら)
Signするには、AWS SDKのgetObjectUrl()を使います。
$client = S3Client::factory(array(
'key' => [access_key],
'secret' => [secret_key]
));
$opt = array("ResponseContentDisposition" => 'attachment; filename="[filename]"');
$url = $client->getObjectUrl($bucket, '[key]', '[expiration]', $opt);
こうすると、次のようなURLが作られます。
- https://[bucket].s3.amazonaws.com/[key]?response-content-disposition=attachment%3B%20filename%3D%22hoge%22&AWSAccessKeyId=&Expires=1111&Signature=
このURLにアクセスすれば、ファイル名が差し替わった状態でダウンロードすることができます。
なお、URLにはAccessKeyIdが含まれています。これは、リクエストを事前にSign(Pre-Signed URL)した場合には必ず含まれるようですが、シークレットアクセスキーがなければ何もできないので公開しても問題ないようです。それでも「なんか気持ち悪い」と感じる場合は、何の権限ももたないか、あるいはread権限のみがあるIAMアカウントを作ってそれでSignしても良いかもしれません。
日本語のファイル名を扱う
Content-Dispositionは以下のようにして日本語のファイル名を扱うことができます。
Content-Disposition: attachment; filename*=UTF-8''[UTF-8のファイル名をURLエンコードしたもの]
ただし、こちらのテスト結果を見る限りではIE8はこの方法では対応できないようです。
IE8の場合のみShift-JISを使えば良さそうですが、そうすると今度はAmazon側が対応していないような気もします。
※このあたりはちょっとまだ検証しきれていません(すみません)