Cloudflare R2 で ActiveStorage のダイレクトアップロード機能を利用するには

2023/01/25に公開

以前、Cloudflare R2をActiveStorageで使う方法を紹介しました。
https://zenn.dev/takeyuwebinc/articles/e4d47ff381b530

オープンβ当時は署名付きURLに対応していなかったため、ダイレクトアップロードは利用できませんでした。

2022年9月の一般公開で署名付きURLに対応したため、ActiveStorageのダイレクトアップロード機能が使えるようになりました。

バケット全体へのアクセスを許可することなく、ユーザーがファイルをアップロードまたは共有できるように、署名付きURLを作成します。
https://blog.cloudflare.com/ja-jp/r2-ga-ja-jp/

本記事では実際に利用するための設定方法について紹介します。

Cloudflare R2をActiveStorageのバックエンドとして利用する

バケットの作成とActiveStorageの設定

https://zenn.dev/takeyuwebinc/articles/e4d47ff381b530#利用方法

ダイレクトアップロードの設定

@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日現在は利用できています。

サンプルコード

https://github.com/takeyuweb/rails-activestorage-r2

ダイレクトアップロードの模様

ダイレクトアップロードの際の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