実装したもの
view上のダウンロードボタンを押すと、S3にアップロード済みのログデータの署名付きURLを発行してリダイレクトを行いダウンロードさせる機能を作成しました。
今後似たような機能を作成するときに困らないように自分の言葉で備忘録として残しておくための記事となります。
必要な知識、参考にした記事
SDK for Ruby
RubyによってS3などのAWSサービスにアクセスし管理ができるようになるものです。SDKはソフトウェア開発キットと呼ばれるけどあんまりピンときてない。現状私はライブラリ、フレームワーク、APIを提供するパッケージというイメージを持っています。
なお、今回の記事についてはSDK for Rubyの導入については省略します。
AWS::S3::Client
SDK for Rubyに含まれる「クラス」でS3との低レベル(抽象度が低く詳細なことはできない)なAPI通信を行うことができます。
すでに用意されたメソッドによってS3に対してさまざまな操作を行うことができます。
例えば、
client.put_object(bucket: "bucket-01", key: "example_01.txt", body: "example")
clientインスタンスを用意して、put_object
メソッドによってexample_01.txt
を指定したバケットにアップロードすることができます。
基本的にはAWS::S3::Clientを継承したインスタンスを立てて、そこに色々なオリジナルのメソッドを持たせて開発を進めていきました。
公式
参考にした記事↓(会社の先輩の記事でした)
Aws::S3::Presigner
先ほどのClientのようにAWS側で用意されたクラス。こちらは署名付きURLの管理に関わるもの。
公式
実際の作業履歴
大まかな流れとしては、
- Clientインスタンスを作成する
- 署名付きURLを発行するインスタンスメソッドを作成する
- ダウンロードアクションを追加してそこでClientに対して2のメソッドを実行させる
- ブラウザ側で、コントローラーから受け取った署名付きURLにリダイレクトする
という流れでした
1 Clientインスタンスの初期化
SDKの導入等については割愛。
OriginalClientについては既に作成済みだったので、深くは関われませんでしたが自分なりにまとめてみます。
clientをインスタンスを生成するための独自のクラスを作成していました。
class OriginalClient
HOGE_BUCKET_NAME = "hoge-huga-#{Rails.env}".freeze
# freezeによって予期せぬ変更を防ぐ
# initialize:クラスのインスタンスが生成されたときに自動的に呼び出される特別なメソッドで、インスタンスの初期設定を行う。
# initializeを呼び出す際にclient引数を省略した場合、自動的に新しいAws::S3::Clientインスタンスを生成する。
def initialize(client: Aws::S3::Client.new)
@client = client
@presigner = Aws::S3::Presigner.new(client: @client) #clientとpresignerの初期化が完了
end
end
クラスにバケット名を定数として定めています。
バケット名の中にRailsの現在の実行環境名(development, test, production)を動的に挿入することによって、開発環境、テスト環境、本番環境それぞれで異なるS3バケットを指し示すことができます。(S3バケットは3つ作成している)
ここまでで、OriginalClientを作成すると、S3バケットと通信を行うための@client
と@presigner
を初期設定が完了しました。
2 用意したClientインスタンスに署名付きURLを発行するメソッドを追加
元々用意されているpresigned_url
メソッドを使用して署名付きURLを発行するオリジナルのメソッドを作成する
def generate_get_object_url(bucket, key, expires_in: 3600)
@presigner.presigned_url(:get_object, bucket:, key:, expires_in:)
end
presigned_url
はClass: Aws::S3::Presignerにて元々用意されているメソッド。引数に指定したバケットへの署名付きURLを作成する。
expires_inによって、URLの有効期限を指定できる。今回はデフォルトで1時間の有効期限としています。
ここまでで、OriginalClientのインスタンスを作成し、適切なバケット名を引数としてgenerate_get_object_url
を呼び出すことで署名付きURLを発行する準備が整いました。
3. コントローラーアクションによって、Clientインスタンスを生成しメソッドを呼び出す
4. ブラウザ側で署名付きURLを受け取ってリダイレクトする
ほぼ同時に実装することになりました
view側
<% logs.each do |log| %> # eachでlogを回していて、複数のダウンロードボタンを作成しています
<button class='btn btn-default download-button' data-log-id="<%= log.id %>"> #data-log-idでjsに渡す
<%= 'ダウンロード' %>
</button>
<% end %>
その後のリダイレクト処理のために、詳細についてはJavaScriptに持たせました
<script>
$(document).ready(function() {
$('.download-button').click(function(e) {
e.preventDefault();
const LogId = $(this).data('log-id'); //buttonの部分から受け取る
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch(`/hoge/~~~/aaaaaa/${LogId}/download`, { //この書き方でコントローラー側でparamsで受け取れるようになる
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
})
.then(response => {
if (!response.ok) {
console.error({ response });
}
return response.json(); //コントローラーからjson形式の署名付きURLを受け取ったあとの処理
})
.then(data => {
window.location.href = data.presigned_url; //コントローラーから返った署名付きURLにリダイレクトすることでダウンロードが実行される
})
.catch(error => {
console.error({ error: error.message });
});
});
});
</script>
ルーティング
resources :aaaaaa, only: [:index, ~~~], controller: 'hogege' do
member do
get 'download' #ダウンロードアクション自体ははgetメソッドで実装しました
end
end
downloadアクション(コントローラー)
def download
log_id = params[:id] # /hoge/~~~/aaaaaa/${LogId}/downloadの形のURLから${LogId}を受け取れる
log = @aaaaaa.logs.find(log_id) # 既に作成済みのlogと紐づける
file_name = log.file_name # file_nameについてはlogの保存先を作成するインスタンスメソッドを別で組んでいます。
presigned_url = OriginalClient.new.generate_get_object_url( #別途解説
OriginalClient::HOGE_BUCKET_NAME, file_name
)
render json: { presigned_url: } #作成した署名付きURLをjson形式でブラウザに返す
rescue StandardError
flash[:alert] = 'ダウンロードエラーが発生しました'
redirect_to '元々のページ'
end
重要なのはこの部分
presigned_url = OriginalClient.new.generate_get_object_url(
OriginalClient::HOGE_BUCKET_NAME, file_name
)
一番最初に作成したOriginalClient
クラスのインスタンスをnewして、自作したメソッドのgenerate_get_object_url
を呼び出しています。
それの引数として、バケット名とkey(ファイル名)を入れています。expires_in: 3600についてはデフォルトなので引数は必要無しです。
このような流れで、HOGE_BUCKET_NAMEというS3の中にある、file_nameというkeyのファイルへの署名付きURLを発行してダウンロードすることができるようになりました。