背景
最近、社内でS3に保存したメディアをHTML上で手元にダウンロードする機能を実装してほしいとの要望があった。
実際に作成していく中で、私の知識不足もあり、いろいろ嵌っていったので、共有と備忘録も込めて、この記事を記述する。
なんだ、簡単じゃん
調べてみると下記のようなコードで実装できるらしい!(やったね!)
<a href="https://image-test.s3.amazonaws.com/test.png" download>ダウンロード</a>
うっきうきで実装してみると...
違う、そうじゃない...
調べてみると、下記のような文言がありました。
この属性は同一オリジンのURLに限り動作する
今回、ダウンロードしたい画像はS3に置いてあるため、当然のことながら同一ドメインではない。よくリファレンスを読んでみる。
どうやらcontent-dispositionを設定しなければならないみたい。
というわけで、content-dispositionを付けてみよう!
準備
今回行ったことは以下の三つである。
- S3にメディアを保存するときに拡張子を指定する
- content-dispositionを設定する
- CORS対応を行う
画像はS3を保存しており、違うドメインのHTMLで画像をダウンロードさせるために上記の三つが必要となる。
1. S3にメディアを保存するときに拡張子を指定する
今回は簡単なスクリプトでrubyで記述する。
実際にはメディアをアップロードするだけでいいのだが、aws-cliでrubyから作ってみる
require 'aws-sdk'
require 'mime/types'
require 'httpclient'
file_name = "test.jpeg"
region = "ap-northeast-1"
bucket_name = image-test
file = File.read(file_name)
File.write("test", file)
s3 = Aws::S3::Resource.new(region: region)
obj = s3.bucket(bucket_name).object(file_name)
obj.put(
body: file,
content_type: 'png'
)
2.content-dispositionを設定する
そもそもcontent-dispositionってなんぞや?となっていた。
調べてみると、下記の通り。
コンテンツをwebページの一部として表示するかダウンロードするかを示します。
inline を指定すればwebページとして、attachment を指定すればダウンロードファイルとして、を表します。 また、filenameパラメータで、ファイルの初期名を指定できます。
なるほど、それでは、実際にメディアをgetする時に指定してみよう!
設定するaws-cliは以下の通り
require 'aws-sdk'
file_name = "test.jpeg"
region = "ap-northeast-1"
bucket_name = image-test
s3client = Aws::S3::Client.new(
region: region,
credentials: '~/.aws/config',
)
signer = Aws::S3::Presigner.new(Client: s3client)
puts signer.presigned_url(
:get_object,
bucket: bucket_name,
key: file_name,
expires_in: 3600,
response_content_disposition: "attachment; filename=#{file_name}"
)
3.CORSを設定する
今回、AWSのS3に保存しているメディアについて、presigned_urlを発行し、ダウンロードページに渡すことを想定する。
そのため、使用されるメディアのCORS対応を行わなければ、HTMLでダウンロードしようとしたときに引っかかるため、この設定を行う。
AWS S3では、バケットのアクセス許可のタブから下記の場所にCORSの設定を施すことができる。
exposeHeadersにContent-Dispositionをセットして準備完了!!
実行
まず、presigned_urlを発行する。
先ほどのコードを実行してS3にメディアをアップロードする。
ruby content_type.rb
今回は、拡張子pngで試してみよう。
次に、このメディアをダウンロードするためのpresigned_urlを発行する。
ruby presigned_url.rb
あとはこのURLをHTMLのボタンに組み込む。
本当はAPIを作成して都度presigned_urlができるようにしたいが、今回は直に入力する。
<a href="実行した結果のpresigned_url">ダウンロード</a>
すると...?
ボタンを押したらダウンロードできた!!
これで目的を達成!!
気を付ける嵌りポイント
メディアのcontent-typeを指定しないと...?
binary/octet-streamというcontent-typeになる。
これが厄介で、binary/octet-streamのファイル名をtest.pngとかにしてダウンロードすると、凡そ全てのブラウザでダウンロードできる、iPhone君だけはきちんとbinary/octet-streamのファイルとして処理されてしまう...
やはり、content-typeはしっかりと指定しよう!!
まとめ
content-dispositionという、メディアをダウンロードするかどうかをヘッダーに付与することで、メディアをダウンロードする機能を実行できます。
他にもやり方がたくさんあるかもしれないが、awsなど別ドメインを介してダウンロードする場合、参考になればと思います。
参考