💽
AWSのS3へアップロード&DL用の署名付きURLを発行する
概要
非同期処理でAWSのS3へ大容量のzipファイルをアップロードし、ダウンロード用の署名付きURLを発行する
使用技術
- Nuxt.js
- Rails APIモード
- Heroku
- Docker
背景
- 少し前にpdfを複数選択しzipファイルで一括ダウンロードできるような機能を開発した
- 色々あり非同期処理は使用しておらず、timeoutにならない程度にDL件数に制限をかけて当時は実装
- Herokuを使用していたが、Herokuの制限で30秒以内にレスポンスを返さなければtimeoutになるという条件があった
- 仕様変更によりzipファイル生成の処理時間が大幅に伸びtimeoutが頻発してしまいDLが失敗するケースが増加
以上の背景から、RailsのActiveJobを使い非同期処理でzipファイルを生成し、
S3を利用してアップロード&ダウンロードを行おう、となりました。
ゴール
ゴールはざっくりこんな感じです。
- ActiveJobで非同期処理を行いS3へ大容量ファイルをアップロード
- S3にアップしたファイルの署名付きURLを生成する
実際にはURL生成してからWebScoketを使用して進捗をクライアントに配信しつつ、
終わったらS3のURLを返すといった処理も実装しましたが今回は割愛します。
実装
zip_download_job.rb
require 'aws-sdk-s3' # AWS S3のSDKを利用するために必要
class ZipDownloadJob < ApplicationJob
queue_as :default
def perform(ids)
# zip化処理開始
# 自分の場合ではcontrollerからidsの配列を受け取ってpdf生成→zip化する処理をここに書きました
# zip化処理終了
object_content = binary_data # 上で生成したzipファイルのbinaryデータを格納
current_time = Time.current # ファイル名に使用、ランダムな文字列とかでもOKだと思います
# 環境によって条件分岐させる場合はこんな感じ
case Rails.env
when "development"
# 本記事ではローカルだけ書きます
# 後で使用するバケット名とかオブジェクトキーを定義 ※各環境ごとにセットしてください
bucket_name = Rails.application.credentials.aws[:development][:bucket]
object_key = "zip/test_#{current_time}.zip"
# S3へアップロード
object = object_uploaded?(s3_client, bucket_name, object_key, object_content)
# 一応object作られてるか確認
puts object
# S3からダウンロード(署名付きURLでダウンロードURL発行)
signer = Aws::S3::Presigner.new(client: s3_client)
url = signer.presigned_url(:get_object,
bucket: bucket_name, # S3のバケット名
key: object_key, # S3のオブジェクトキー
expires_in: 600, #urlの有効期限
secure: true, # trueにすることでhttpsにできる
response_content_type: 'application/force-download') # S3のファイルをブラウザで開くのではなく、押下したらダウンロードさせたい場合
when "production"
# 本番用の処理
# ステージングとかあれば適宜case文で分岐させる
end
end
private
def s3_client
region = Rails.application.credentials.aws[:development][:region]
access_key_id = Rails.application.credentials.aws[:development][:access_key_id]
secret_access_key = Rails.application.credentials.aws[:development][:secret_access_key]
Aws::S3::Client.new(
region: region,
access_key_id: access_key_id,
secret_access_key: secret_access_key
)
end
# S3へアップロード AWSのドキュメントほぼそのまま引用
def object_uploaded?(s3_client, bucket_name, object_key, object_content)
response = s3_client.put_object(
bucket: bucket_name, # [String] The name of the bucket
key: object_key, # [String] The name of the object
body: object_content # [String] The content to add to the object
)
if response.etag
return true
else
return false
end
rescue StandardError => e
puts "Error uploading object: #{e.message}"
return false
end
end
最後に
Zenn初投稿です。
まだまだ経験も浅く調べつつ実装したのでもっとスマートなやり方があれば是非コメントください!
Qiitaも投稿していますのでもしよかったら覗いてやってください。
参考資料
Discussion