⛳
Cloudflare R2 で ActiveStorage のダイレクトアップロード機能を利用するには
以前、Cloudflare R2をActiveStorageで使う方法を紹介しました。
オープンβ当時は署名付きURLに対応していなかったため、ダイレクトアップロードは利用できませんでした。
2022年9月の一般公開で署名付きURLに対応したため、ActiveStorageのダイレクトアップロード機能が使えるようになりました。
バケット全体へのアクセスを許可することなく、ユーザーがファイルをアップロードまたは共有できるように、署名付きURLを作成します。
https://blog.cloudflare.com/ja-jp/r2-ga-ja-jp/
本記事では実際に利用するための設定方法について紹介します。
Cloudflare R2をActiveStorageのバックエンドとして利用する
バケットの作成とActiveStorageの設定
ダイレクトアップロードの設定
@rails/activestorage のインストール
@rails/activestorageを利用します。
私のサンプルでは importmap-rails を使っているので
$ bundle exec bin/importmap pin @rails/activestorage
app/javascripts/application.js
// 追記
import * as ActiveStorage from "@rails/activestorage"
ActiveStorage.start()
フォームテンプレートの修正
direct_upload: true
を指定します。
<%= form.file_field :image, direct_upload: true %>
R2 の CORS 設定
ブラウザからR2バケットへのPUTを許可するために、R2バケットのCORS設定が必要です。
現在のところR2の管理画面では設定できないようなので、AWS S3クライアントを使います。設定内容はRailsガイドの内容を参考にしました。
$ aws configure --profile <適当なプロファイル名>
AWS Access Key ID [None]: <R2の管理画面で作成したAPI TokenのAccess Key ID>
AWS Secret Access Key [None]: <R2の管理画面で作成したAPI TokenのSecret Access Key>
Default region name [None]:
Default output format [None]: json
$ aws s3api put-bucket-cors \
--bucket <対象のバケット名> \
--cors-configuration '{"CORSRules" : [{"AllowedHeaders":["*"],"AllowedMethods":["PUT"],"AllowedOrigins":["*"],"ExposeHeaders":["Origin", "Content-Type", "Content-MD5", "Content-Disposition"],"MaxAgeSeconds":3600}]}' \
--profile <適当なプロファイル名> \
--endpoint-url https://<R2の管理画面で確認できるAccount ID>.r2.cloudflarestorage.com
$ aws s3api get-bucket-cors \
--bucket <対象のバケット名> \
--profile <適当なプロファイル名> \
--endpoint-url https://<R2の管理画面で確認できるAccount ID>.r2.cloudflarestorage.com
{
"CORSRules": [
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"PUT"
],
"AllowedOrigins": [
"*"
],
"MaxAgeSeconds": 3600
}
]
}
R2ではAllowedHeadersのワイルドカードが指定できないという話を見かけたのですが、少なくとも2023年1月25日現在は利用できています。
サンプルコード
ダイレクトアップロードの際のRailsのログ
Started POST "/posts" for 172.20.0.1 at 2023-01-25 04:28:59 +0000
Cannot render console from 172.20.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by PostsController#create as TURBO_STREAM
Parameters: {"authenticity_token"=>"[FILTERED]", "post"=>{"name"=>"direct", "image"=>"eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBFZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7e3fa302263afa5130b7be1eb620a7941a2dce8b"}, "commit"=>"Create Post"}
ActiveStorage::Blob Load (0.1ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ? [["id", 13], ["LIMIT", 1]]
↳ app/controllers/posts_controller.rb:24:in `create'
TRANSACTION (0.0ms) begin transaction
↳ app/controllers/posts_controller.rb:27:in `block in create'
Post Create (0.2ms) INSERT INTO "posts" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "direct"], ["created_at", "2023-01-25 04:29:00.108684"], ["updated_at", "2023-01-25 04:29:00.108684"]]
↳ app/controllers/posts_controller.rb:27:in `block in create'
ActiveStorage::Blob Load (0.0ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" INNER JOIN "active_storage_attachments" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 6], ["record_type", "Post"], ["name", "image"], ["LIMIT", 1]]
↳ app/controllers/posts_controller.rb:27:in `block in create'
ActiveStorage::Attachment Load (0.0ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 6], ["record_type", "Post"], ["name", "image"], ["LIMIT", 1]]
↳ app/controllers/posts_controller.rb:27:in `block in create'
ActiveStorage::Blob Update (0.1ms) UPDATE "active_storage_blobs" SET "metadata" = ? WHERE "active_storage_blobs"."id" = ? [["metadata", "{\"identified\":true}"], ["id", 13]]
↳ app/controllers/posts_controller.rb:27:in `block in create'
ActiveStorage::Attachment Create (0.1ms) INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES (?, ?, ?, ?, ?) [["name", "image"], ["record_type", "Post"], ["record_id", 6], ["blob_id", 13], ["created_at", "2023-01-25 04:29:00.115555"]]
↳ app/controllers/posts_controller.rb:27:in `block in create'
Post Update (0.0ms) UPDATE "posts" SET "updated_at" = ? WHERE "posts"."id" = ? [["updated_at", "2023-01-25 04:29:00.116323"], ["id", 6]]
↳ app/controllers/posts_controller.rb:27:in `block in create'
TRANSACTION (3.4ms) commit transaction
↳ app/controllers/posts_controller.rb:27:in `block in create'
[ActiveJob] Enqueued ActiveStorage::AnalyzeJob (Job ID: cfdb04b6-d03e-4ffd-a57e-874f682086d9) to Async(default) with arguments: #<GlobalID:0x00007f1cf32a9a38 @uri=#<URI::GID gid://my-app/ActiveStorage::Blob/13>>
Redirected to http://localhost:3000/posts/6
Completed 302 Found in 189ms (ActiveRecord: 4.0ms | Allocations: 12309)
まとめ
R2バックエンドでもダイレクトアップロードが利用できるようになっていました。ダイレクトアップロードを利用することで、Railsサーバーを介さずにブラウザから直接アップロードできるので、サーバー負荷の軽減などに役立ちますね。
Discussion