この記事は
- Rails5.2からActiveStorageという添付ファイル系のライブラリが入っています
- これがオフィシャルな割にCDN対応しておらず結構微妙です
- 性能的にCloudfrontで配信したかったので、若干試行錯誤したのでメモです
ActiveStorageの流れ
AWSと連携する場合、だいたい下記のような流れで利用されます。
ファイルアップロード
- ブラウザからアップロードファイルを受け付け
- AWSのAPI経由でS3にファイルを配置
- と同時に、そのファイルのパスだとかファイルタイプ、サイズだとかのメタ情報をRails内のテーブルに保持
ファイルダウンロード
- 格納しておいたメタ情報から、表示したいファイルのリンクを作成
- このリンクはパーマネントなもの
- Railsサーバを指している
- ブラウザからRailsへ画像表示のリクエストを投げる
- RailsはAWSのAPI経由で5分間限定のS3へアクセス可能なURLを払い出し、そこへのリダイレクトをブラウザへ返却
ActiveStorageの問題点
- 上の流れでまずいのは、ActiveStorageでアップロードしたファイルを参照するには都度Railsを経由する必要があるという点です
- つまり100枚の画像を表示する場合、瞬間的に100回Railsへアクセスが行くという事になります(あかん!)
- 5分ごとにリンクをexpiredさせたいような、非常にセンシティブな情報(電子書籍とかね)をアップロードするサービスであれば、この方が都合がいいのかもしれませんが、そうでもないシステムにはきつい仕様です
公式な対応
-
GithubのIssueでRails5.2のリリース前からもう2年近く議論されている ようなのですが、今の所公式な対応はありません
- このへんのPRを見るとだいぶ進んできているようなムードはありますが
- なので、自力で適当に逃げるしかないのが現状です
対応方針
で、対応内容ですがベタです
- (A)CloudfrontでS3バケットを参照できるようにします
- Cloudfrontのサブドメインが払い出されます
- (B)Rails上でS3上のファイル名を割り出します
-
User.has_one_attached :photo
だとすると、user.photo.blob.key
の値がファイル名です - ちなみにS3ではこのファイル名で、バケット直下にずらーーーーーっと並んでます(男らしい)
-
- そして(A)+(B)のURLに参照すればアクセス可能です
- cloudfrontのサブドメインが
xxx
で、ファイル名がXXXXXXX
であれば、https://xxx.cloudfront.net/XXXXXXX
- cloudfrontのサブドメインが
対応詳細
事前準備
- 事前にActiveStorageはaws連携で使えるようにしておいてください
- また、アップロード先のS3バケットをCloudfrontで公開状態にして、URLでアクセスできるようにしておいてください(詳細は割愛)
普通の画像表示
- 通常こんな感じでActiveStorage経由のURLを埋め込むと思います
- これの代替となるヘルパを作ります
erb
<img src="<%= rails_blob_path user.photo %>" />
ヘルパの準備
- ActiveStorageの設定ファイルはこんな感じとします
storage.yml
local:
service: Disk
root: <%= Rails.root.join("storage") %>
amazon:
service: S3
access_key_id: <%= ENV['S3_ACCESS_KEY'] %>
secret_access_key: <%= ENV['S3_SECRET_KEY'] %>
region: ap-northeast-1
bucket: <%= ENV['S3_BUCKET'] %>
- こんな感じのヘルパを追加します
- 上記storage.ymlのキーで分岐して、開発環境には影響が出ないようにしています
xxx_helper.rb
def cdn_ready_blob_path(attachment)
service = Rails.application.config.active_storage.service
if service == :local
# 元々のヘルパ
rails_blob_path(attachment)
elsif service == :amazon
# S3上でのファイル名を取得してURLを組み立てる
key = attachment&.blob&.key
"#{Settings.cloudfront.url}/#{key}"
end
end
- SettingsだとかでCloudfrontで発行されたドメインを環境別に定義しておきます
- ステージングとか環境でCloudfrontのdistributeを分けることができます
settings/production.yml
cloudfront:
url: 'https://xxx.cloudfront.net'
CDN対応した画像表示
- このヘルパを使ってさっきのビューを書き直します
erb
<img src="<%= cdn_ready_blob_path user.photo %>" />
これで
- Cloudfrontから直接ファイルが配信できるようになりました!
終わりに
- 今後も公式のライブラリとしてやってくのならオフィシャルにCDN対応してほしいですねー!
- ちなみにこの辺しっかりしてたライブラリのpaperclipはActiveStorageに譲ってdeprecatedという・・・