前提
- 会員限定のダウンロード機能を作りたい。
- S3は非公開設定になっている。(つまり下記のようにS3にリダイレクトさせるような形は、ダウンロードリンクを共有されるで採用しない)
redirect_to @department.annual_report.url
課題
paperclipを使いPDFをS3にアップした際に、アップしたPDFのダウンロードができなかった。
[Date] INFO -- : Completed 500 Internal Server Error in 33ms
[Date] FATAL -- :
ActionController::MissingFile (Cannot read file /path/sample.pdf):
app/controllers/departments_controller.rb:xx:in `annual_report'
コードはこんな感じだった。
def download
unless user_signed_in?
redirect_to @department
else
send_file @department.annual_report.path, type: @department.annual_report.content_type
end
end
解決方法
Restricting Access to Objects Stored on Amazon S3
上記リンクに書いてるように expiring_url
を指定することで、:s3_permissions => :private
と指定されたS3に対して、指定時間内限定でアクセスできる一時的なURLを発行することができる。
def download
redirect_to @department.annual_report.expiring_url(1.minute)
end
上記コードのようにとても手軽に実装できる。
上の例だと1分間だけ認証が解除された固有のURLが生成される。
ただリダイレクトさせてしまうとアクセス先がS3になってしまい、且つアクセス可能時間を過ぎたら下記のようなエラー画面が出るので、サービスの選択肢としてはない。
send_dataと組み合わせる
よって上記URLをsend_dataで返すようにした。
これにより、ダウンロードボタンをクリックする度に指定時間の間アクセス可能なURLが発行され、そのURLに存在するファイルをバイナリで読み込み、クライアントに返すということができるようになった。
def download
unless user_signed_in?
redirect_to @department
else
download_url = @department.annual_report.expiring_url(1.minute)
file_name = @department.annual_report_file_name
content_type = @department.annual_report.content_type
open(download_url, 'rb') do |data|
send_data data.read, filename: file_name, type: content_type
end
end
end
- ログイン判定。
-
expiring_url(1.minute)
で1分間だけアクセス可能なURLを生成。(file_nameはダウンロード時のファイル名) - open-uriでURLをバイナリで読み込み、send_dataでクライアントに返す。